Environment Lifecycle Management
Complete guide to managing the lifecycle of Odoo environments in OEC.SH - from creation through deployment, operation, and eventual destruction.
Overview
Environment lifecycle in OEC.SH follows a well-defined state machine with clear transitions between states. Understanding these states and transitions is essential for effective environment management.
Lifecycle States
Environments progress through the following states:
| State | Description | Available Actions |
|---|---|---|
| pending | Environment created but not yet deployed | Deploy, Destroy |
| deploying | Deployment in progress | None (wait for completion) |
| running | Environment is active and accessible | Stop, Pause, Restart, Redeploy, Destroy, Clone, Sync |
| stopped | Environment containers stopped | Start, Redeploy, Destroy, Clone, Sync |
| paused | Environment paused (state saved to disk) | Resume, Stop, Destroy, Sync |
| cloning | Environment being cloned | None (wait for completion) |
| error | Deployment or operation failed | Redeploy, Destroy, Sync |
| deleted | Environment permanently removed | None (terminal state) |
State Transition Diagram
pending → deploying → running ⇄ stopped
↓ ↓
paused → stopped
↓
deletedCreating Environments
API Endpoint
POST /api/v1/projects/{project_id}/environmentsRequired Fields
- name (string, optional): Environment name. Auto-generated if not provided (e.g.,
my-project-development-1) - env_type (enum): Environment type -
development,staging, orproduction
Optional Fields
- vm_id (UUID): Server to deploy to. Can be assigned later
- git_branch (string): Git branch to deploy (default:
main) - domain (string): Custom domain for the environment
- subdomain (string): Subdomain on platform domain (e.g.,
acme-prod.apps.oec.sh) - port (integer): Odoo HTTP port (default:
8069) - db_name (string): Database name (default: environment UUID)
- auto_deploy (boolean): Enable auto-deployment on git push (default:
false) - environment_vars (object): Environment variables for Docker container
- odoo_config (object): Odoo configuration overrides (see Environment Variables)
Resource Allocation
- cpu_cores (decimal): Total CPU cores (default:
1.0, range:0.25-32.0) - ram_mb (integer): Total RAM in MB (default:
2048, range:256-131072) - disk_gb (integer): Disk space in GB (default:
10, range:1-1000) - postgres_cpu_cores (decimal): PostgreSQL CPU limit (auto-calculated as 30% if not set)
- postgres_ram_mb (integer): PostgreSQL RAM limit (auto-calculated as 30% if not set)
PostgreSQL Optimization Settings
- postgres_version (string): PostgreSQL version override (e.g.,
"15","16") - enable_pgtune (boolean): Enable PGTune optimization (default:
true) - enable_pgbouncer (boolean): Enable PgBouncer connection pooling (default:
true) - enable_read_replica (boolean): Enable read replica for Odoo 18+ (default:
false)
Migration Support
- migration_id (UUID): Link environment to migration for automatic restore during deployment
Example: Create Development Environment
{
"name": "acme-dev",
"env_type": "development",
"vm_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"git_branch": "develop",
"cpu_cores": 1.0,
"ram_mb": 2048,
"disk_gb": 20,
"auto_deploy": true,
"environment_vars": {
"ODOO_DEBUG": "1"
},
"odoo_config": {
"preset": "minimal",
"development": {
"dev_mode": "reload,qweb"
}
}
}Response
{
"id": "e7a8b9c0-d1e2-3f4a-5b6c-7d8e9f0a1b2c",
"name": "acme-dev",
"env_type": "development",
"project_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"vm_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "pending",
"subdomain": "acme-dev",
"system_url": "https://acme-dev.apps.oec.sh",
"cpu_cores": 1.0,
"ram_mb": 2048,
"disk_gb": 20,
"postgres_cpu_cores": 0.3,
"postgres_ram_mb": 614,
"odoo_cpu_cores": 0.7,
"odoo_ram_mb": 1434,
"is_active": true,
"created_at": "2025-12-11T10:30:00Z"
}Name Auto-Generation
If name is not provided, the system automatically generates a name using the pattern:
{project-slug}-{env-type}-{sequence}Examples:
- First development environment:
my-project-development - Second development environment:
my-project-development-2 - First production environment:
my-project-production
Subdomain Auto-Generation
When subdomain auto-generation is enabled (platform setting), environments receive unique subdomains:
- Format:
{project-slug}-{env-type-short}-{random} - Example:
acme-prod-x7k2 - Full URL:
https://acme-prod-x7k2.apps.oec.sh
Quota Validation
Environment creation is validated against organization quotas:
- Environment Count: Total environments vs
max_environmentslimit - Resource Allocation: Per-environment limits (
max_cpu_per_env,max_ram_per_env_mb,max_disk_per_env_gb) - Total Resources: Organization-wide quotas (
total_cpu_quota,total_ram_quota_mb,total_disk_quota_gb)
If quota is exceeded, creation fails with 403 Forbidden:
{
"detail": "Quota exceeded: Total CPU quota would be exceeded (12.5/10.0 cores). Currently using 11.5 cores. Additional 1.0 cores requested."
}Configuring Environments
Odoo Version Selection
Odoo versions are managed by portal administrators via the OdooVersionModel. Supported versions typically include:
- Odoo 14.0
- Odoo 15.0
- Odoo 16.0
- Odoo 17.0
- Odoo 18.0
- Odoo 19.0
Each version includes:
- Docker image configuration
- Default PostgreSQL version
- Default addons path
- Registry authentication settings
Environment Types
Three environment types with distinct purposes:
| Type | Purpose | Typical Configuration | Protection |
|---|---|---|---|
| development | Active development, testing | Low resources, debug enabled | Can be deleted |
| staging | Pre-production testing | Medium resources | Can be deleted |
| production | Live customer-facing | High resources, optimized | Cannot be deleted via API |
Resource Allocation Strategy
Resources are split between Odoo and PostgreSQL containers:
Default Split (Auto-calculated)
- Odoo: 70% of total resources
- PostgreSQL: 30% of total resources
Example: 4 cores, 8GB RAM Environment
Total Resources:
├── CPU: 4.0 cores → Odoo: 2.8 cores, PostgreSQL: 1.2 cores
├── RAM: 8192 MB → Odoo: 5734 MB, PostgreSQL: 2458 MB
└── Disk: 50 GB → Shared storageCustom PostgreSQL Limits
You can override the auto-calculated PostgreSQL limits:
{
"cpu_cores": 4.0,
"ram_mb": 8192,
"disk_gb": 50,
"postgres_cpu_cores": 1.5,
"postgres_ram_mb": 3072
}Server Assignment
Environments must be assigned to a server (VM) before deployment. Validation checks:
- Organization Ownership: VM must belong to same organization
- Server Status: VM must be in
RUNNINGstate - Capacity: Resources must not exceed server physical capacity
- Environment Limit: Server must have capacity for additional environments (
max_projectslimit)
Example validation error:
{
"detail": "Requested RAM (16.0 GB) exceeds server capacity (8 GB)"
}Editing Environments
API Endpoint
PATCH /api/v1/projects/{project_id}/environments/{env_id}Editable Fields
Mutable Fields (Can Be Changed Anytime)
- name: Environment name
- git_branch: Git branch to deploy
- domain: Custom domain
- subdomain: Platform subdomain
- port: HTTP port
- auto_deploy: Auto-deployment toggle
- environment_vars: Environment variables
- odoo_config: Odoo configuration
- vm_id: Server assignment
- is_active: Active status
Resource Fields (Validated Against Quota)
- cpu_cores: CPU allocation
- ram_mb: RAM allocation
- disk_gb: Disk allocation
- postgres_cpu_cores: PostgreSQL CPU limit
- postgres_ram_mb: PostgreSQL RAM limit
Immutable Fields (Cannot Be Changed)
- id: Environment UUID
- project_id: Parent project
- env_type: Environment type
- container_id: Docker container ID
- container_name: Container name
- db_name: Database name
- created_at: Creation timestamp
Resource Scaling
When updating resource allocation, the system:
- Calculates resource delta (difference from current allocation)
- Validates new limits against per-environment maximums
- Checks total organization quota with delta
- Auto-recalculates PostgreSQL limits if not explicitly set (30% rule)
Scale Up Example
PATCH /api/v1/projects/a1b2c3d4.../environments/e7a8b9c0...
Content-Type: application/json
{
"cpu_cores": 2.0,
"ram_mb": 4096
}Response includes recalculated PostgreSQL limits:
{
"cpu_cores": 2.0,
"ram_mb": 4096,
"postgres_cpu_cores": 0.6,
"postgres_ram_mb": 1229,
"odoo_cpu_cores": 1.4,
"odoo_ram_mb": 2867
}Configuration Updates Without Redeployment
These changes take effect on next container restart:
- Environment variables
- Odoo configuration parameters
- Domain/subdomain changes
- Auto-deploy settings
These require redeployment to take effect:
- Resource limits (CPU, RAM)
- PostgreSQL optimization settings
- Git branch changes
- Server (VM) reassignment
Example: Update Configuration
PATCH /api/v1/projects/a1b2c3d4.../environments/e7a8b9c0...
Content-Type: application/json
{
"git_branch": "feature/new-module",
"environment_vars": {
"ODOO_LOG_LEVEL": "debug",
"ODOO_DEBUG": "1"
},
"odoo_config": {
"performance": {
"workers": 4
},
"logging": {
"log_level": "debug"
}
}
}Deploying Environments
Deployment Trigger
POST /api/v1/environments/{environment_id}/actions
Content-Type: application/json
{
"action": "deploy"
}Pre-Deployment Requirements
- Server Assignment: Environment must have
vm_idset - Server Status: Target VM must be
RUNNING - Resource Quota: Organization must have available quota
- Server Capacity: Resources must not exceed VM capacity
Container Architecture
Each environment deploys with isolated resources:
Environment UUID: e7a8b9c0-d1e2-3f4a-5b6c-7d8e9f0a1b2c
├── Docker Network: paasportal_net_e7a8b9c0-d1e2-3f4a-5b6c-7d8e9f0a1b2c
├── PostgreSQL Container: e7a8b9c0-d1e2-3f4a-5b6c-7d8e9f0a1b2c_db
│ ├── Image: postgres:15
│ ├── CPU Limit: 0.3 cores
│ ├── RAM Limit: 614 MB
│ └── Port: 5432 (internal)
│
├── PgBouncer Container: pgbouncer-e7a8b9c0... (if enabled)
│ ├── Port 6432: Primary PostgreSQL
│ └── Port 6433: Read replica (if deployed)
│
├── Odoo Container: e7a8b9c0-d1e2-3f4a-5b6c-7d8e9f0a1b2c_odoo
│ ├── Image: odoo:17.0 (or custom)
│ ├── CPU Limit: 0.7 cores
│ ├── RAM Limit: 1434 MB
│ ├── Port: 8069 (HTTP)
│ └── Port: 8072 (WebSocket, Odoo 18+)
│
├── Database: e7a8b9c0-d1e2-3f4a-5b6c-7d8e9f0a1b2c
│
└── Volumes:
├── /opt/paasportal/e7a8b9c0.../odoo.conf
├── /opt/paasportal/e7a8b9c0.../addons (→ /mnt/extra-addons)
├── /opt/paasportal/e7a8b9c0.../data (→ /var/lib/odoo)
└── /opt/paasportal/e7a8b9c0.../logs (→ /var/log/odoo)Deployment Stages
The deployment process progresses through these stages with real-time SSE updates:
1. Initializing
- Fetch deployment configuration
- Validate prerequisites
- Create deployment record
2. Connecting
- Establish SSH connection to target server
- Verify Docker availability
3. Creating Network
- Create isolated Docker network for environment
- Network name:
paasportal_net_{environment_uuid}
4. Creating PostgreSQL
- Deploy PostgreSQL container with resource limits
- Generate secure database password
- Wait for PostgreSQL to accept connections (30 retries with exponential backoff)
- Create Odoo database
5. Optimizing PostgreSQL
- Apply PGTune optimizations (if enabled)
- Configure PgBouncer connection pooling (if enabled)
- Deploy read replica (if enabled and Odoo 18+)
- Store optimization config in environment record
6. Cloning Platform Repositories
- Clone platform-level addon repositories
- Applied to all environments across platform
- Cloned to
/opt/paasportal/{env_uuid}/addons/{repo_slug}
7. Cloning Organization Repositories
- Clone organization-specific addon repositories
- Shared across all projects in organization
- Cloned to
/opt/paasportal/{env_uuid}/addons/{repo_slug}
8. Cloning Project Repository
- Clone project's primary Git repository (if configured)
- Checkout specified branch
- Capture commit information (SHA, message, author)
- Cloned to
/opt/paasportal/{env_uuid}/addons/{project_slug}
9. Pulling Image
- Pull Odoo Docker image
- Authenticate with private registry if required
- Cache image for faster future deployments
10. Generating Configuration
- Build
odoo.confwith all addon paths - Apply configuration presets and custom overrides
- Configure database connection (direct or via PgBouncer)
- Add read replica connection (Odoo 18+ if enabled)
11. Starting Container
- Create Odoo container with resource limits
- Mount configuration, addons, and data volumes
- Connect to internal network (for PostgreSQL access)
- Connect to Traefik network (for HTTP routing)
- Apply Traefik labels for routing and SSL
12. Installing Dependencies
- Process
apt.txt(system packages) - Process
requirements.txt(Python packages) - Run inside container as root user
13. Initializing Database
- Initialize Odoo database (if not restored from migration)
- Install base modules
- Run database migrations
14. Configuring DNS
- Create DNS A record for environment subdomain
- Points to server's public IP address
- Allows DNS propagation time before SSL provisioning
15. Configuring Traefik
- Verify Traefik routing configuration
- Apply SSL certificate (Let's Encrypt via Traefik)
- Configure HTTP to HTTPS redirect
16. Health Check
- Wait for Odoo to respond on HTTP port
- Verify database connectivity
- Check container logs for errors
17. Completed
- Update environment status to
running - Record deployment metadata (commit SHA, timestamp)
- Update
last_deployed_attimestamp
Real-Time Progress Updates
Deployments provide real-time progress via Server-Sent Events (SSE):
GET /api/v1/events?task_id={task_id}
Accept: text/event-streamEvent stream example:
event: deployment.step
data: {"step": "connecting", "message": "Connecting to 203.0.113.10..."}
event: deployment.step
data: {"step": "creating_postgres", "message": "Creating PostgreSQL container..."}
event: deployment.step
data: {"step": "cloning_repo", "message": "Cloning project repository..."}
event: deployment.progress
data: {"progress": 65, "message": "Starting Odoo container..."}
event: deployment.completed
data: {"success": true, "container_id": "a1b2c3d4...", "url": "https://acme-dev.apps.oec.sh"}Migration Restore During Deployment
When environment is linked to a migration (migration_id set):
- Download backup from cloud storage during Initializing Database stage
- Extract and restore database dump
- Restore filestore archive
- Set
migration_restore_completed = trueto prevent re-restore on redeploy - Subsequent redeployments skip restore unless
force_restore=trueis passed
Deployment Failure Handling
If deployment fails at any stage:
- Environment status set to
error - Deployment record marked as
failed - Error message stored in deployment logs
- Containers may be partially created (cleanup required)
- User can retry with Redeploy action
Common failure scenarios:
| Failure Point | Cause | Resolution |
|---|---|---|
| SSH connection | Server unreachable, wrong credentials | Verify server status and SSH config |
| PostgreSQL startup | Port conflict, resource limits | Check server capacity and port availability |
| Git clone | Invalid repository, authentication failure | Verify Git connection and permissions |
| Image pull | Private registry auth, network issues | Check registry credentials and network |
| Container start | Resource limits, port conflicts | Check resource allocation and port availability |
Stopping Environments
Stop Action
POST /api/v1/environments/{environment_id}/actions
Content-Type: application/json
{
"action": "stop",
"options": {
"timeout": 10
}
}Behavior
- Sends
SIGTERMto Odoo container - Waits for graceful shutdown (default: 10 seconds)
- Forces
SIGKILLif timeout exceeded - PostgreSQL container remains running
- Data persisted in volumes
- Status changes:
running→stopped
Resource Impact
- Quota: Resources remain allocated (quota not released)
- Billing: Environment still counts toward billing if on platform infrastructure
- Access: Environment URL returns 503 Service Unavailable
When to Stop
- Maintenance windows
- Resource conservation for inactive environments
- Before server maintenance
- Testing deployment procedures
Container vs Destroy
| Aspect | Stop | Destroy |
|---|---|---|
| Data | Preserved | Deleted |
| Quota | Still allocated | Released |
| Recovery | Fast (start container) | Requires redeploy |
| Use case | Temporary | Permanent removal |
Destroying Environments
Destroy Action
DELETE /api/v1/projects/{project_id}/environments/{env_id}?backup_action=keep_latestBackup Handling Options
The backup_action query parameter determines what happens to existing backups:
| Option | Description | Use Case |
|---|---|---|
keep_all | Keep all backups (become orphaned) | Preserve full backup history |
delete_all | Delete all backups from storage | Complete cleanup |
keep_latest | Keep only most recent backup | Balance storage and safety |
If environment has backups and backup_action is not specified, the API returns 409 Conflict:
{
"detail": {
"message": "Environment has 5 backup(s). Specify backup_action parameter.",
"backup_count": 5,
"options": ["keep_all", "delete_all", "keep_latest"]
}
}Pre-Destruction Check
Get backup information before destroying:
GET /api/v1/projects/{project_id}/environments/{env_id}/backup-infoResponse:
{
"environment_id": "e7a8b9c0-d1e2-3f4a-5b6c-7d8e9f0a1b2c",
"environment_name": "acme-dev",
"backup_count": 5,
"total_size_bytes": 2147483648,
"latest_backup_at": "2025-12-10T08:00:00Z",
"has_backups": true
}Destruction Process
-
Validation
- Verify environment exists and user has access
- Check environment type (production cannot be deleted)
- Verify backup_action if backups exist
-
Backup Handling
- Execute backup actions based on
backup_actionparameter - Delete from cloud storage if requested
- Orphan remaining backups (remove
environment_idlink)
- Execute backup actions based on
-
Migration Unlinking
- If environment linked to migration, reset migration status
- Set
migration.environment_id = null - Set
migration.status = ANALYZEDfor reconfiguration
-
Container Cleanup
- Stop and remove Odoo container
- Stop and remove PostgreSQL container
- Stop and remove PgBouncer container (if exists)
- Stop and remove read replica containers (if exist)
- Remove Docker network
-
Volume Cleanup
- Remove data volumes
- Remove configuration files
- Remove addon clones
-
Database Deletion
- Hard delete environment record from database
- Cascade delete to related records (domains, deployments, tasks)
-
Quota Release
- CRITICAL: Set
is_active = Falsebefore deletion - Releases allocated CPU, RAM, disk from organization quota
- Allows resources to be allocated to new environments
- CRITICAL: Set
Production Protection
Production environments have special protection:
DELETE /api/v1/projects/a1b2c3d4.../environments/e7a8b9c0...Response: 400 Bad Request
{
"detail": "Cannot delete production environment"
}To delete a production environment:
- Change
env_typetodevelopmentorstaging(requires permission) - Then delete via API
Or use portal admin tools with elevated permissions.
Example: Destroy with Backup Retention
DELETE /api/v1/projects/a1b2c3d4.../environments/e7a8b9c0...?backup_action=keep_latestResponse:
{
"message": "Environment deleted successfully (4 backup(s) deleted, 1 kept)"
}Environment States
State Machine Definition
Environment states follow a strict state machine pattern defined in environment_state_machine.py:
VALID_TRANSITIONS = {
EnvironmentStatus.PENDING: {
EnvironmentAction.DEPLOY: EnvironmentStatus.DEPLOYING,
EnvironmentAction.DESTROY: EnvironmentStatus.DELETED,
},
EnvironmentStatus.DEPLOYING: {
# No actions during deployment
},
EnvironmentStatus.RUNNING: {
EnvironmentAction.STOP: EnvironmentStatus.STOPPED,
EnvironmentAction.PAUSE: EnvironmentStatus.PAUSED,
EnvironmentAction.RESTART: EnvironmentStatus.RUNNING,
EnvironmentAction.REDEPLOY: EnvironmentStatus.DEPLOYING,
EnvironmentAction.DESTROY: EnvironmentStatus.DELETED,
EnvironmentAction.CLONE: EnvironmentStatus.RUNNING,
EnvironmentAction.SYNC: EnvironmentStatus.RUNNING,
},
EnvironmentStatus.STOPPED: {
EnvironmentAction.START: EnvironmentStatus.RUNNING,
EnvironmentAction.REDEPLOY: EnvironmentStatus.DEPLOYING,
EnvironmentAction.DESTROY: EnvironmentStatus.DELETED,
EnvironmentAction.CLONE: EnvironmentStatus.STOPPED,
EnvironmentAction.SYNC: EnvironmentStatus.STOPPED,
},
EnvironmentStatus.PAUSED: {
EnvironmentAction.RESUME: EnvironmentStatus.RUNNING,
EnvironmentAction.STOP: EnvironmentStatus.STOPPED,
EnvironmentAction.DESTROY: EnvironmentStatus.DELETED,
EnvironmentAction.SYNC: EnvironmentStatus.PAUSED,
},
EnvironmentStatus.ERROR: {
EnvironmentAction.REDEPLOY: EnvironmentStatus.DEPLOYING,
EnvironmentAction.DESTROY: EnvironmentStatus.DELETED,
EnvironmentAction.SYNC: EnvironmentStatus.ERROR,
},
}Get Available Actions
Query available actions for current environment state:
GET /api/v1/environments/{environment_id}/available-actionsResponse:
{
"environment_id": "e7a8b9c0-d1e2-3f4a-5b6c-7d8e9f0a1b2c",
"current_status": "running",
"available_actions": [
{
"action": "stop",
"label": "Stop",
"description": "Stop the running environment (data is preserved)",
"destructive": false,
"confirm_required": true,
"async_operation": false
},
{
"action": "pause",
"label": "Pause",
"description": "Pause the environment (saves state to disk)",
"destructive": false,
"confirm_required": false,
"async_operation": false
},
{
"action": "restart",
"label": "Restart",
"description": "Restart the environment (quick stop and start)",
"destructive": false,
"confirm_required": true,
"async_operation": false
},
{
"action": "redeploy",
"label": "Redeploy",
"description": "Rebuild and redeploy the environment with latest code",
"destructive": false,
"confirm_required": true,
"async_operation": true
},
{
"action": "destroy",
"label": "Destroy",
"description": "Permanently delete the environment and all its data",
"destructive": true,
"confirm_required": true,
"async_operation": true
},
{
"action": "clone",
"label": "Clone",
"description": "Create a copy of this environment",
"destructive": false,
"confirm_required": false,
"async_operation": true
},
{
"action": "sync",
"label": "Sync Status",
"description": "Synchronize environment status with actual container state",
"destructive": false,
"confirm_required": false,
"async_operation": false
}
],
"pending_changes": false,
"last_deployed_at": "2025-12-11T10:30:00Z"
}State Validation
Attempting invalid transitions results in 400 Bad Request:
POST /api/v1/environments/e7a8b9c0.../actions
Content-Type: application/json
{
"action": "stop"
}Response (if environment is in deploying state):
{
"detail": "Cannot perform 'stop' on environment in 'deploying' status"
}Transitioning States
Some states are transitioning states that block other actions:
- deploying: Deployment in progress
- cloning: Clone operation in progress
During transitioning states:
- No other actions can be performed
- Frontend should show progress indicators
- SSE events provide real-time updates
- Timeout detection handles stuck deployments
Terminal States
deleted is a terminal state with no valid transitions out. Once deleted, environments cannot be recovered.
Permissions & Security
Required Permissions (Permission Matrix System)
Environment lifecycle operations require specific permissions:
| Operation | Permission | Resource Level |
|---|---|---|
| List environments | org.projects.view | Organization |
| Create environment | org.environments.create | Organization |
| View environment | org.projects.view | Organization |
| Edit environment | org.environments.edit | Organization |
| Deploy environment | org.environments.deploy | Organization |
| Stop environment | org.environments.stop | Organization |
| Start environment | org.environments.start | Organization |
| Destroy environment | org.environments.delete | Organization |
| Delete production | org.production.delete | Organization (special) |
System Roles
Built-in system roles with environment permissions:
| Role | Permissions |
|---|---|
| portal_admin | All environment operations globally |
| org_owner | All environment operations within organization |
| org_admin | All environment operations within organization |
| org_member | View, create, deploy, start, stop (no delete) |
| project_admin | All operations on project environments |
| project_member | View, deploy, start, stop project environments |
Cross-Organization Protection
All environment operations validate organization ownership:
# Backend validation example
if environment.project.organization_id != user_organization_id:
raise HTTPException(403, "Cannot access environments from other organizations")This prevents:
- Cross-organization data access
- Unauthorized environment manipulation
- Resource quota bypass
Dangerous Operation Protection
Some operations have additional safeguards:
Production Deletion
if environment.environment_type == EnvironmentType.PRODUCTION:
raise HTTPException(400, "Cannot delete production environment")Destroy Confirmation
UI requires:
- Type environment name to confirm
- Checkbox acknowledgment: "I understand this is permanent"
backup_actionparameter selection
Resource Quotas
Quota System Architecture
Resource quotas are enforced at organization level with two layers:
- Per-Environment Limits: Maximum resources for single environment
- Total Organization Quotas: Sum of all active environments
Quota Calculation
Quotas include resources from:
- Primary environment containers (Odoo + PostgreSQL)
- Read replica containers (30% CPU/RAM, 100% disk of primary)
- CRITICAL: Only environments with
is_active = Truecount toward quota
Quota Check Service
The QuotaService handles all quota validation:
# Check before creating environment
quota_check = await quota_service.can_create_environment(
organization_id=org_id,
cpu_cores=1.0,
ram_mb=2048,
disk_gb=10
)
if not quota_check.allowed:
raise HTTPException(403, f"Quota exceeded: {quota_check.reason}")Get Current Quota Usage
GET /api/v1/organizations/{org_id}/quotaResponse:
{
"projects_used": 3,
"projects_limit": 10,
"environments_used": 8,
"environments_limit": 20,
"servers_used": 2,
"servers_limit": 5,
"members_used": 5,
"members_limit": 10,
"cpu_used": 12.5,
"cpu_limit": 16.0,
"ram_used_mb": 24576,
"ram_limit_mb": 32768,
"disk_used_gb": 250,
"disk_limit_gb": 500
}Quota Exceeded Scenarios
Scenario 1: Environment Count Limit
Current: 20 environments, Limit: 20
Action: Create new environment
Result: 403 Forbidden - "Environment limit reached (20)"Scenario 2: Total CPU Quota
Current: 15.5 cores used, Limit: 16.0 cores
Request: 1.0 core environment
Result: 403 Forbidden - "Total CPU quota would be exceeded (16.5/16.0 cores)"Scenario 3: Per-Environment Limit
Org max_cpu_per_env: 4.0 cores
Request: 6.0 core environment
Result: 403 Forbidden - "CPU allocation (6.0 cores) exceeds per-environment limit (4.0 cores)"Quota Release on Destroy
When environment is destroyed:
# CRITICAL: Set is_active = False to release quota
environment.is_active = False
await db.commit()
# Then delete
await db.delete(environment)
await db.commit()If is_active is not set to False, quota remains allocated even after deletion, creating "ghost quota" that blocks new environments.
Overprovisioning Warnings
System warns about server overprovisioning:
Server: 4 cores, 8GB RAM physical
Allocated: 12 cores, 24GB RAM across environments
Warning: Server overprovisioned (CPU: 300%, RAM: 300%)This is allowed (environments share resources dynamically) but triggers monitoring alerts.
API Reference
Environment Management Endpoints
List All Environments (Global)
GET /api/v1/environments
Authorization: Bearer {token}Returns all environments the user has access to across all organizations.
List Project Environments
GET /api/v1/projects/{project_id}/environments
Authorization: Bearer {token}Returns all environments for a specific project.
Get Environment Details
GET /api/v1/projects/{project_id}/environments/{env_id}
Authorization: Bearer {token}Returns detailed environment information including clone info if environment was cloned.
Create Environment
POST /api/v1/projects/{project_id}/environments
Authorization: Bearer {token}
Content-Type: application/json
{
"name": "staging-env",
"env_type": "staging",
"vm_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"cpu_cores": 2.0,
"ram_mb": 4096,
"disk_gb": 50
}Update Environment
PATCH /api/v1/projects/{project_id}/environments/{env_id}
Authorization: Bearer {token}
Content-Type: application/json
{
"git_branch": "main",
"cpu_cores": 4.0,
"ram_mb": 8192
}Delete Environment
DELETE /api/v1/projects/{project_id}/environments/{env_id}?backup_action=keep_latest
Authorization: Bearer {token}Get Backup Info
GET /api/v1/projects/{project_id}/environments/{env_id}/backup-info
Authorization: Bearer {token}Environment Actions Endpoints
Execute Action
POST /api/v1/environments/{environment_id}/actions
Authorization: Bearer {token}
Content-Type: application/json
{
"action": "deploy",
"options": {}
}Actions: deploy, redeploy, start, stop, restart, pause, resume, destroy, clone, sync
Get Available Actions
GET /api/v1/environments/{environment_id}/available-actions
Authorization: Bearer {token}Sync Environment Status
POST /api/v1/environments/{environment_id}/sync-status
Authorization: Bearer {token}Convenience endpoint that wraps the sync action.
Environment Size Endpoints
Get Environment Size
GET /api/v1/environments/{environment_id}/size
Authorization: Bearer {token}Returns cached size information.
Calculate Environment Size
POST /api/v1/environments/{environment_id}/size/calculate
Authorization: Bearer {token}Recalculates database, filestore, and addons sizes by connecting to server.
Get Clone Time Estimate
GET /api/v1/environments/{environment_id}/clone-estimate?include_filestore=true&include_addons=true
Authorization: Bearer {token}Estimates time required to clone environment based on size.
DNS Configuration Endpoint
Setup Environment DNS
POST /api/v1/environments/{environment_id}/setup-dns
Authorization: Bearer {token}Creates or updates DNS A record for environment subdomain.
Troubleshooting
Common Deployment Errors
Error: "Environment has no server assigned"
Cause: vm_id is null or invalid
Solution:
PATCH /api/v1/projects/{project_id}/environments/{env_id}
Content-Type: application/json
{
"vm_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}Error: "Quota exceeded: Total CPU quota would be exceeded"
Cause: Organization has insufficient quota
Solution:
- Check current usage:
GET /api/v1/organizations/{org_id}/quota - Destroy unused environments to release quota
- Contact support to increase quota limits
- Upgrade billing plan for higher quotas
Error: "VM is not running (current status: stopped)"
Cause: Target server is not running
Solution:
- Check server status:
GET /api/v1/vms/{vm_id} - Start server:
POST /api/v1/vms/{vm_id}/start - Wait for server to reach
RUNNINGstate - Retry deployment
Error: "Failed to connect to server via SSH"
Cause: SSH connection failure
Solution:
- Verify server is reachable:
ping {server_ip} - Check SSH credentials in server configuration
- Verify SSH port is open:
telnet {server_ip} {ssh_port} - Check server firewall rules
- Verify SSH key permissions if using key authentication
Error: "Failed to clone repository"
Cause: Git authentication or repository access failure
Solution:
- Verify repository URL is correct
- Check Git connection is configured:
GET /api/v1/organizations/{org_id}/git-connections - Verify Git connection has access to repository
- For private repositories, ensure OAuth token or SSH key is valid
- Test repository access manually
Error: "Container failed to start: port already in use"
Cause: Port conflict on server
Solution:
- Change environment port:
PATCH /api/v1/projects/{project_id}/environments/{env_id}with{"port": 8070} - Or identify and stop conflicting container on server
Environment Stuck in "deploying"
Symptoms: Environment stays in deploying status for extended period
Causes:
- Deployment task failed without updating status
- SSH connection timeout
- Server became unreachable during deployment
Solution:
- Check task status:
GET /api/v1/tasks?environment_id={env_id} - Check deployment logs:
GET /api/v1/deployments/{deployment_id}/logs - Sync environment status:
POST /api/v1/environments/{env_id}/sync-status - If stuck, manually update status or redeploy
Quota Not Released After Destroy
Symptoms: Destroyed environment still counts toward quota
Cause: Environment not properly marked as inactive before deletion
Diagnosis:
GET /api/v1/organizations/{org_id}/quotaCheck if cpu_used, ram_used_mb, disk_used_gb include destroyed environment resources.
Solution: Contact support to manually correct quota accounting. This is a critical bug that should not occur with proper deletion flow.
Environment Status Desync
Symptoms: Environment status in database doesn't match actual container state
Causes:
- Manual container manipulation outside platform
- Server restart without platform notification
- Docker daemon issues
Solution:
POST /api/v1/environments/{env_id}/sync-statusThis connects to server and synchronizes status with actual container state.
DNS Not Resolving
Symptoms: Environment subdomain doesn't resolve to server IP
Causes:
- DNS provider not configured for organization
- DNS propagation delay (up to 48 hours, typically 5-15 minutes)
- Incorrect DNS record
Solution:
- Check DNS provider configuration:
GET /api/v1/organizations/{org_id}/dns-providers - Manually setup DNS:
POST /api/v1/environments/{env_id}/setup-dns - Verify DNS record:
dig {subdomain}.apps.oec.sh - Wait for propagation (use
nslookupordigto check)
SSL Certificate Provisioning Failed
Symptoms: Environment URL returns SSL certificate error
Causes:
- DNS not yet propagated when Traefik attempted ACME challenge
- Let's Encrypt rate limits exceeded
- Firewall blocking port 80 (required for HTTP-01 challenge)
Solution:
- Verify DNS resolves correctly:
dig {subdomain}.apps.oec.sh - Check Traefik logs on server:
docker logs traefik - Retry after DNS propagation: Restart environment
- Check Let's Encrypt rate limits: https://letsencrypt.org/docs/rate-limits/ (opens in a new tab)
Best Practices
Development Workflow
- Create development environment with low resources
- Deploy with
auto_deploy: truefor automatic updates on git push - Test changes in development environment
- Clone to staging environment for QA testing
- Deploy to production after approval
Resource Planning
- Development: 1 CPU, 2GB RAM (minimal resources)
- Staging: 2 CPU, 4GB RAM (similar to production)
- Production: 4+ CPU, 8+ GB RAM (sized for load)
Environment Naming
Use clear, descriptive names that include:
- Project identifier
- Environment type
- Purpose or feature (if applicable)
Examples:
acme-dev-mainacme-stagingacme-prodacme-dev-feature-billing
Backup Before Destroy
Always create a manual backup before destroying environments:
POST /api/v1/environments/{env_id}/backupsThen destroy with backup_action=keep_all to preserve all backups.
Monitor Quota Usage
Regularly check quota usage to avoid deployment failures:
GET /api/v1/organizations/{org_id}/quotaSet up alerts when usage exceeds 80% of limits.
Sync Status Periodically
Run status sync weekly to detect environment state drift:
POST /api/v1/environments/{env_id}/sync-statusUse Configuration Presets
Leverage Odoo configuration presets for consistency:
{
"odoo_config": {
"preset": "production",
"custom_params": {
"max_cron_threads": 4
}
}
}Available presets:
minimal: Development with debug loggingstandard: Staging with balanced settingsproduction: Optimized for production loadhigh_performance: High-traffic production
Related Documentation
- Creating Environments - Detailed environment creation guide
- Deploying Environments - In-depth deployment documentation
- Cloning Environments - Environment cloning guide
- Environment Variables - Configuration management
- Backup & Restore - Backup strategies
- Resource Quotas - Quota management
- Permission Matrix - Access control
Last updated: December 2025