Features
Environments
Environment Lifecycle

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:

StateDescriptionAvailable Actions
pendingEnvironment created but not yet deployedDeploy, Destroy
deployingDeployment in progressNone (wait for completion)
runningEnvironment is active and accessibleStop, Pause, Restart, Redeploy, Destroy, Clone, Sync
stoppedEnvironment containers stoppedStart, Redeploy, Destroy, Clone, Sync
pausedEnvironment paused (state saved to disk)Resume, Stop, Destroy, Sync
cloningEnvironment being clonedNone (wait for completion)
errorDeployment or operation failedRedeploy, Destroy, Sync
deletedEnvironment permanently removedNone (terminal state)

State Transition Diagram

pending → deploying → running ⇄ stopped
                         ↓         ↓
                      paused → stopped

                      deleted

Creating Environments

API Endpoint

POST /api/v1/projects/{project_id}/environments

Required Fields

  • name (string, optional): Environment name. Auto-generated if not provided (e.g., my-project-development-1)
  • env_type (enum): Environment type - development, staging, or production

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:

  1. Environment Count: Total environments vs max_environments limit
  2. Resource Allocation: Per-environment limits (max_cpu_per_env, max_ram_per_env_mb, max_disk_per_env_gb)
  3. 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:

TypePurposeTypical ConfigurationProtection
developmentActive development, testingLow resources, debug enabledCan be deleted
stagingPre-production testingMedium resourcesCan be deleted
productionLive customer-facingHigh resources, optimizedCannot 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 storage

Custom 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:

  1. Organization Ownership: VM must belong to same organization
  2. Server Status: VM must be in RUNNING state
  3. Capacity: Resources must not exceed server physical capacity
  4. Environment Limit: Server must have capacity for additional environments (max_projects limit)

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:

  1. Calculates resource delta (difference from current allocation)
  2. Validates new limits against per-environment maximums
  3. Checks total organization quota with delta
  4. 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

  1. Server Assignment: Environment must have vm_id set
  2. Server Status: Target VM must be RUNNING
  3. Resource Quota: Organization must have available quota
  4. 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.conf with 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_at timestamp

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-stream

Event 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):

  1. Download backup from cloud storage during Initializing Database stage
  2. Extract and restore database dump
  3. Restore filestore archive
  4. Set migration_restore_completed = true to prevent re-restore on redeploy
  5. Subsequent redeployments skip restore unless force_restore=true is passed

Deployment Failure Handling

If deployment fails at any stage:

  1. Environment status set to error
  2. Deployment record marked as failed
  3. Error message stored in deployment logs
  4. Containers may be partially created (cleanup required)
  5. User can retry with Redeploy action

Common failure scenarios:

Failure PointCauseResolution
SSH connectionServer unreachable, wrong credentialsVerify server status and SSH config
PostgreSQL startupPort conflict, resource limitsCheck server capacity and port availability
Git cloneInvalid repository, authentication failureVerify Git connection and permissions
Image pullPrivate registry auth, network issuesCheck registry credentials and network
Container startResource limits, port conflictsCheck 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 SIGTERM to Odoo container
  • Waits for graceful shutdown (default: 10 seconds)
  • Forces SIGKILL if timeout exceeded
  • PostgreSQL container remains running
  • Data persisted in volumes
  • Status changes: runningstopped

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

AspectStopDestroy
DataPreservedDeleted
QuotaStill allocatedReleased
RecoveryFast (start container)Requires redeploy
Use caseTemporaryPermanent removal

Destroying Environments

Destroy Action

DELETE /api/v1/projects/{project_id}/environments/{env_id}?backup_action=keep_latest

Backup Handling Options

The backup_action query parameter determines what happens to existing backups:

OptionDescriptionUse Case
keep_allKeep all backups (become orphaned)Preserve full backup history
delete_allDelete all backups from storageComplete cleanup
keep_latestKeep only most recent backupBalance 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-info

Response:

{
  "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

  1. Validation

    • Verify environment exists and user has access
    • Check environment type (production cannot be deleted)
    • Verify backup_action if backups exist
  2. Backup Handling

    • Execute backup actions based on backup_action parameter
    • Delete from cloud storage if requested
    • Orphan remaining backups (remove environment_id link)
  3. Migration Unlinking

    • If environment linked to migration, reset migration status
    • Set migration.environment_id = null
    • Set migration.status = ANALYZED for reconfiguration
  4. 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
  5. Volume Cleanup

    • Remove data volumes
    • Remove configuration files
    • Remove addon clones
  6. Database Deletion

    • Hard delete environment record from database
    • Cascade delete to related records (domains, deployments, tasks)
  7. Quota Release

    • CRITICAL: Set is_active = False before deletion
    • Releases allocated CPU, RAM, disk from organization quota
    • Allows resources to be allocated to new environments

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:

  1. Change env_type to development or staging (requires permission)
  2. 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_latest

Response:

{
  "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-actions

Response:

{
  "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:

OperationPermissionResource Level
List environmentsorg.projects.viewOrganization
Create environmentorg.environments.createOrganization
View environmentorg.projects.viewOrganization
Edit environmentorg.environments.editOrganization
Deploy environmentorg.environments.deployOrganization
Stop environmentorg.environments.stopOrganization
Start environmentorg.environments.startOrganization
Destroy environmentorg.environments.deleteOrganization
Delete productionorg.production.deleteOrganization (special)

System Roles

Built-in system roles with environment permissions:

RolePermissions
portal_adminAll environment operations globally
org_ownerAll environment operations within organization
org_adminAll environment operations within organization
org_memberView, create, deploy, start, stop (no delete)
project_adminAll operations on project environments
project_memberView, 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_action parameter selection

Resource Quotas

Quota System Architecture

Resource quotas are enforced at organization level with two layers:

  1. Per-Environment Limits: Maximum resources for single environment
  2. 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 = True count 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}/quota

Response:

{
  "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:

  1. Check current usage: GET /api/v1/organizations/{org_id}/quota
  2. Destroy unused environments to release quota
  3. Contact support to increase quota limits
  4. Upgrade billing plan for higher quotas

Error: "VM is not running (current status: stopped)"

Cause: Target server is not running

Solution:

  1. Check server status: GET /api/v1/vms/{vm_id}
  2. Start server: POST /api/v1/vms/{vm_id}/start
  3. Wait for server to reach RUNNING state
  4. Retry deployment

Error: "Failed to connect to server via SSH"

Cause: SSH connection failure

Solution:

  1. Verify server is reachable: ping {server_ip}
  2. Check SSH credentials in server configuration
  3. Verify SSH port is open: telnet {server_ip} {ssh_port}
  4. Check server firewall rules
  5. Verify SSH key permissions if using key authentication

Error: "Failed to clone repository"

Cause: Git authentication or repository access failure

Solution:

  1. Verify repository URL is correct
  2. Check Git connection is configured: GET /api/v1/organizations/{org_id}/git-connections
  3. Verify Git connection has access to repository
  4. For private repositories, ensure OAuth token or SSH key is valid
  5. Test repository access manually

Error: "Container failed to start: port already in use"

Cause: Port conflict on server

Solution:

  1. Change environment port: PATCH /api/v1/projects/{project_id}/environments/{env_id} with {"port": 8070}
  2. 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:

  1. Check task status: GET /api/v1/tasks?environment_id={env_id}
  2. Check deployment logs: GET /api/v1/deployments/{deployment_id}/logs
  3. Sync environment status: POST /api/v1/environments/{env_id}/sync-status
  4. 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}/quota

Check 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-status

This 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:

  1. Check DNS provider configuration: GET /api/v1/organizations/{org_id}/dns-providers
  2. Manually setup DNS: POST /api/v1/environments/{env_id}/setup-dns
  3. Verify DNS record: dig {subdomain}.apps.oec.sh
  4. Wait for propagation (use nslookup or dig to 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:

  1. Verify DNS resolves correctly: dig {subdomain}.apps.oec.sh
  2. Check Traefik logs on server: docker logs traefik
  3. Retry after DNS propagation: Restart environment
  4. Check Let's Encrypt rate limits: https://letsencrypt.org/docs/rate-limits/ (opens in a new tab)

Best Practices

Development Workflow

  1. Create development environment with low resources
  2. Deploy with auto_deploy: true for automatic updates on git push
  3. Test changes in development environment
  4. Clone to staging environment for QA testing
  5. 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-main
  • acme-staging
  • acme-prod
  • acme-dev-feature-billing

Backup Before Destroy

Always create a manual backup before destroying environments:

POST /api/v1/environments/{env_id}/backups

Then 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}/quota

Set 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-status

Use Configuration Presets

Leverage Odoo configuration presets for consistency:

{
  "odoo_config": {
    "preset": "production",
    "custom_params": {
      "max_cron_threads": 4
    }
  }
}

Available presets:

  • minimal: Development with debug logging
  • standard: Staging with balanced settings
  • production: Optimized for production load
  • high_performance: High-traffic production

Related Documentation


Last updated: December 2025