Features
Environments
Environment Variables

Environment Variables Management

Feature ID: ENV-004 Category: Environments Required Permission: project.environments.update API Endpoint: PATCH /api/v1/projects/{project_id}/environments/{env_id}


Overview

Environment variables allow you to configure runtime behavior of your Odoo deployment without modifying code. Variables are injected into the Docker container at startup and can be accessed by Odoo and custom addons.

Use environment variables to:

  • Configure application-specific settings (API keys, feature flags)
  • Override Odoo configuration parameters
  • Set up third-party integrations (payment gateways, email providers)
  • Define environment-specific behavior (development vs production)
  • Manage secrets securely (database passwords, API tokens)

Key Features:

  • Key-Value Storage: Store string, number, boolean, or JSON values
  • Docker Injection: Variables automatically injected into Odoo container
  • Secure Handling: Sensitive values encrypted at rest
  • Environment Isolation: Each environment has independent variables
  • No Restart Required: Changes applied on next deployment

Understanding Environment Variables

Variable Types

Environment variables in OEC.SH support multiple data types:

TypeExample KeyExample ValueUse Case
StringAPI_BASE_URLhttps://api.example.comURLs, text configuration
NumberMAX_CONNECTIONS100Numeric limits, thresholds
BooleanDEBUG_MODEtrueFeature flags, toggles
JSONSERVICE_CONFIG{"host": "...", "port": 8080}Complex configuration objects

Storage Format: All variables are stored as strings in the database (environment_vars JSONB field) and passed to Docker using the -e KEY="VALUE" format.

Variable Scope

Environment variables in OEC.SH exist at the environment level only:

  • Environment-Level: Variables defined in ProjectEnvironment.environment_vars
    • Specific to one environment (e.g., acme-staging)
    • Isolated from other environments in the same project
    • Most common and recommended approach

No Inheritance: There is no project-level or organization-level variable inheritance. Each environment manages its own variables independently.

Built-in vs Custom Variables

Built-in Variables (System-Provided):

  • Automatically set by OEC.SH deployment system
  • Cannot be overridden through the UI
  • Used for internal Odoo configuration
  • Examples: Database connection details, file paths

Custom Variables (User-Defined):

  • Defined by you through the UI or API
  • Accessible in Odoo code via os.environ.get('KEY')
  • Used for application-specific configuration
  • Examples: API keys, feature flags, integration settings

How to Manage Environment Variables

Add Environment Variables

Method 1: During Environment Creation

  1. Navigate to DashboardProjects → Select project
  2. Click Add Environment button
  3. Scroll to Environment Variables section (expandable)
  4. Click Add Variable
  5. Enter:
    • Key: Variable name (uppercase, underscores, alphanumeric)
      • ✅ Valid: API_KEY, SMTP_HOST, MAX_WORKERS
      • ❌ Invalid: api-key, smtp.host, max workers
    • Value: Variable value (string, will be quoted in Docker)
  6. Click Add to confirm
  7. Repeat for additional variables
  8. Complete environment creation form
  9. Variables are applied on first deployment

Method 2: Edit Existing Environment

  1. Navigate to environment detail page
  2. Click Settings tab
  3. Scroll to Environment Variables section
  4. Click Add Variable button
  5. Fill in Key and Value fields
  6. Click Save Changes button (bottom of form)
  7. Redeploy environment to apply changes

Method 3: API Request

PATCH /api/v1/projects/{project_id}/environments/{env_id}
Content-Type: application/json
Authorization: Bearer {jwt_token}

Request Body:

{
  "environment_vars": {
    "API_KEY": "sk_live_abc123",
    "SMTP_HOST": "smtp.gmail.com",
    "SMTP_PORT": "587",
    "ENABLE_FEATURE_X": "true",
    "MAX_UPLOAD_SIZE": "10485760"
  }
}

Response (200 OK):

{
  "id": "env-uuid",
  "name": "acme-staging",
  "environment_vars": {
    "API_KEY": "sk_live_abc123",
    "SMTP_HOST": "smtp.gmail.com",
    "SMTP_PORT": "587",
    "ENABLE_FEATURE_X": "true",
    "MAX_UPLOAD_SIZE": "10485760"
  },
  "updated_at": "2024-12-11T15:30:00Z"
}

Edit Environment Variables

Update Existing Variable

  1. Navigate to environment Settings tab
  2. Find the variable in Environment Variables list
  3. Click Edit icon (pencil) next to the variable
  4. Modify the Value field (key cannot be changed)
  5. Click Save to confirm
  6. Click Save Changes at bottom of form
  7. Redeploy environment to apply

Note: To change a variable key, delete the old variable and create a new one.

API Update (Partial)

To update specific variables without replacing all:

{
  "environment_vars": {
    "API_KEY": "sk_live_new_key_789",
    "NEW_VARIABLE": "value"
  }
}

Important: The API replaces the entire environment_vars object. To preserve existing variables, include them in the request.


Delete Environment Variables

Method 1: UI

  1. Navigate to environment Settings tab
  2. Find variable in Environment Variables section
  3. Click Delete icon (trash can) next to variable
  4. Confirm deletion in modal dialog
  5. Click Save Changes at bottom of form
  6. Redeploy to remove from container

Method 2: API

Send PATCH request with variable removed from environment_vars object:

{
  "environment_vars": {
    "SMTP_HOST": "smtp.gmail.com",
    "SMTP_PORT": "587"
  }
}

Result: API_KEY is removed (not included in the update).


Variable Injection into Docker

How Variables Are Injected

During deployment, OEC.SH builds the Docker run command with environment variables:

docker run -d \
  --name {container_name} \
  --network {network_name} \
  -e API_KEY="sk_live_abc123" \
  -e SMTP_HOST="smtp.gmail.com" \
  -e SMTP_PORT="587" \
  -e ENABLE_FEATURE_X="true" \
  {odoo_image}

Backend Code Reference (backend/services/deployment/container_deployer.py:2608-2609):

env_vars = dict(config.environment_vars)
env_string = " ".join([f'-e {k}="{v}"' for k, v in env_vars.items()])

Accessing Variables in Odoo Code

In your Odoo Python code:

import os
 
# Read environment variable
api_key = os.environ.get('API_KEY', 'default_value')
smtp_host = os.environ.get('SMTP_HOST')
max_upload = int(os.environ.get('MAX_UPLOAD_SIZE', '5242880'))
debug_mode = os.environ.get('DEBUG_MODE', 'false').lower() == 'true'
 
# Check if variable exists
if 'FEATURE_FLAG' in os.environ:
    # Feature is enabled
    pass

Variable Availability

  • Runtime: Variables available immediately when Odoo starts
  • Scope: Available to all Odoo processes (workers, cron)
  • Persistence: Variables persist across container restarts
  • Updates: Changes require redeployment to take effect

Common Use Cases

API Keys and Secrets

Store third-party API credentials:

{
  "STRIPE_API_KEY": "sk_live_...",
  "SENDGRID_API_KEY": "SG.abcd1234...",
  "AWS_ACCESS_KEY_ID": "AKIAIOSFODNN7EXAMPLE",
  "AWS_SECRET_ACCESS_KEY": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}

Python Usage:

stripe.api_key = os.environ.get('STRIPE_API_KEY')

Feature Flags

Enable/disable features per environment:

{
  "ENABLE_BETA_FEATURES": "true",
  "ENABLE_DEBUG_TOOLBAR": "false",
  "MAINTENANCE_MODE": "false"
}

Python Usage:

if os.environ.get('ENABLE_BETA_FEATURES') == 'true':
    # Show beta features
    pass

SMTP Configuration

Configure outbound email:

{
  "SMTP_HOST": "smtp.gmail.com",
  "SMTP_PORT": "587",
  "SMTP_USER": "noreply@example.com",
  "SMTP_PASSWORD": "app_password_here",
  "SMTP_SSL": "true",
  "EMAIL_FROM": "ERP System <noreply@example.com>"
}

Alternative: Use Odoo's built-in SMTP configuration via odoo_config instead.

Database Connection Overrides

Override database settings (not recommended - use built-in system variables):

{
  "DB_MAXCONN": "100",
  "DB_TEMPLATE": "template0"
}

Third-Party Integrations

Configure external services:

{
  "PAYMENT_GATEWAY_URL": "https://api.payment.com/v1",
  "PAYMENT_GATEWAY_KEY": "pk_live_...",
  "WEBHOOK_SECRET": "whsec_...",
  "SENTRY_DSN": "https://...@sentry.io/..."
}

JSON Configuration Objects

Store complex configuration as JSON strings:

{
  "REDIS_CONFIG": "{\"host\": \"redis.example.com\", \"port\": 6379, \"db\": 0}",
  "FEATURE_LIMITS": "{\"max_users\": 100, \"max_storage_gb\": 50}"
}

Python Usage:

import json
redis_config = json.loads(os.environ.get('REDIS_CONFIG', '{}'))
host = redis_config.get('host')

Odoo Configuration vs Environment Variables

When to Use odoo_config

Use the odoo_config field for Odoo-specific configuration that goes into odoo.conf:

  • workers, max_cron_threads
  • limit_memory_hard, limit_time_cpu
  • log_level, log_handler
  • admin_passwd, dbfilter
  • smtp_server, smtp_port

Example:

{
  "odoo_config": {
    "performance": {
      "workers": 4,
      "limit_memory_hard": 2684354560
    },
    "logging": {
      "log_level": "info"
    }
  }
}

Result: Parameters written to /etc/odoo/odoo.conf inside container.

When to Use environment_vars

Use environment_vars for custom application configuration not part of Odoo core:

  • Third-party API keys
  • Feature flags
  • Custom addon settings
  • Integration credentials
  • Application-specific URLs

Example:

{
  "environment_vars": {
    "STRIPE_API_KEY": "sk_live_...",
    "ENABLE_CUSTOM_FEATURE": "true"
  }
}

Result: Variables injected as Docker environment variables (accessible via os.environ).

Comparison Table

Aspectodoo_configenvironment_vars
PurposeOdoo core configurationCustom app configuration
FileGenerates /etc/odoo/odoo.confInjected as ENV in Docker
AccessOdoo reads from config fileAccessible via os.environ
Examplesworkers, log_level, db_filterAPI_KEY, FEATURE_FLAG
ValidationSchema-validated by OdooConfigFree-form key-value pairs

System-Provided Variables

OEC.SH automatically sets these variables during deployment (cannot be overridden):

Database Connection

VariableExample ValueDescription
DB_HOST{uuid}_db or pgbouncer-primary-{uuid}PostgreSQL container hostname
DB_PORT5432 or 6432Database port (5432 for direct, 6432 for PgBouncer)
DB_NAME{environment_uuid}Database name (same as environment ID)
DB_USERodooPostgreSQL username
DB_PASSWORDauto_generated_24_charsSecurely generated password

Note: Odoo reads these from odoo.conf, not environment variables directly.

File Paths

VariableValueDescription
DATA_DIR/var/lib/odooOdoo data directory (filestore, sessions)
ADDONS_PATH/mnt/extra-addonsCustom addons mount path
CONFIG_PATH/etc/odoo/odoo.confOdoo configuration file

Container Metadata

VariableExample ValueDescription
CONTAINER_NAME{uuid}_odooOdoo container name
NETWORK_NAMEpaasportal_net_{uuid}Docker network name
ENVIRONMENT_TYPEstagingEnvironment type (development/staging/production)

Security Best Practices

Secure Secrets Management

DO:

  • ✅ Use environment variables for sensitive data (API keys, passwords)
  • ✅ Store secrets at environment level (not in code or Git)
  • ✅ Use descriptive names: STRIPE_SECRET_KEY not KEY1
  • ✅ Rotate secrets regularly
  • ✅ Use different secrets per environment (dev vs prod)

DON'T:

  • ❌ Commit secrets to Git repositories
  • ❌ Share secrets between environments
  • ❌ Log sensitive variables in application code
  • ❌ Use weak or default secrets in production

Sensitive Variable Examples

Variables that should ALWAYS be environment-specific:

{
  "ADMIN_PASSWORD": "strong_unique_password",
  "JWT_SECRET_KEY": "random_64_char_string",
  "DATABASE_ENCRYPTION_KEY": "base64_encoded_key",
  "OAUTH_CLIENT_SECRET": "provider_secret",
  "WEBHOOK_SIGNING_SECRET": "whsec_..."
}

Non-Sensitive Variable Examples

Variables that can be shared or public:

{
  "API_BASE_URL": "https://api.example.com",
  "SMTP_HOST": "smtp.gmail.com",
  "SMTP_PORT": "587",
  "LOG_LEVEL": "info",
  "TIMEZONE": "UTC"
}

Placeholder Values in Documentation

When documenting variable requirements, use placeholders:

{
  "STRIPE_API_KEY": "sk_live_XXXXXXXXXXXXXXXX",
  "DATABASE_URL": "postgresql://user:***@host:5432/db",
  "AWS_SECRET_ACCESS_KEY": "REDACTED"
}

Variable Validation

Key Validation Rules

Environment variable keys must follow these rules:

  • Allowed Characters: Uppercase letters, numbers, underscores
  • Pattern: ^[A-Z][A-Z0-9_]*$
  • Examples:
    • API_KEY, SMTP_HOST, MAX_WORKERS_2
    • api_key (lowercase), SMTP-HOST (hyphen), MAX WORKERS (space)

Frontend Validation: Key format checked on input before save.

Backend Validation: No strict validation - any key accepted in JSONB field.

Value Validation

  • Format: Stored as strings in database
  • Length: Unlimited (JSONB field, typically up to 1MB)
  • Special Characters: All characters allowed (will be quoted in Docker)
  • Escaping: OEC.SH handles escaping for shell injection prevention

Quote Handling Example:

{
  "MESSAGE": "Hello \"World\"",
  "PATH": "/path/with spaces/here"
}

Docker Command:

-e MESSAGE="Hello \"World\"" \
-e PATH="/path/with spaces/here"

Variable Persistence and Updates

Update Lifecycle

  1. User Updates Variables: Via UI or API
  2. Database Updated: ProjectEnvironment.environment_vars modified
  3. Timestamp Updated: write_date set to current time
  4. Deployment Required: Changes NOT applied to running container
  5. Redeploy Triggered: User initiates deployment
  6. New Container Created: Variables injected into new container
  7. Old Container Stopped: Previous container removed
  8. Updates Active: New variables available in Odoo

Timeline:

Variable Update (Instant) → Database Save (< 1s) → Redeploy (2-5 min) → Active (Container Running)

Restart Behavior

Container Restart (via Docker):

docker restart {container_name}
  • ✅ Variables PRESERVED (defined at container creation)
  • ✅ No redeployment needed
  • ⚠️ New variable values NOT applied (requires full redeploy)

Full Redeployment:

  • ✅ Variables UPDATED (container recreated)
  • ✅ Latest values from database injected
  • ✅ Changes take effect

Variable Synchronization

Single Source of Truth: ProjectEnvironment.environment_vars in PostgreSQL

Synchronization Points:

  1. UI Form ↔ Database (instant sync via API)
  2. DatabaseDocker Container (sync on deployment only)
  3. ContainerOdoo Runtime (immediate via os.environ)

No Auto-Sync: Changes to database do NOT automatically propagate to running containers.


Bulk Import/Export

Export Variables (API)

Get environment details including all variables:

GET /api/v1/projects/{project_id}/environments/{env_id}
Authorization: Bearer {jwt_token}

Response (200 OK):

{
  "id": "env-uuid",
  "name": "acme-staging",
  "environment_vars": {
    "API_KEY": "sk_live_abc123",
    "SMTP_HOST": "smtp.gmail.com",
    "ENABLE_FEATURE_X": "true"
  }
}

Export to JSON File:

curl -H "Authorization: Bearer $TOKEN" \
  https://api.oec.sh/api/v1/projects/$PROJECT_ID/environments/$ENV_ID \
  | jq '.environment_vars' > env_vars.json

Import Variables (API)

Replace all variables with new set:

curl -X PATCH \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d @env_vars.json \
  https://api.oec.sh/api/v1/projects/$PROJECT_ID/environments/$ENV_ID

env_vars.json:

{
  "environment_vars": {
    "API_KEY": "sk_live_new_key",
    "SMTP_HOST": "smtp.sendgrid.net",
    "NEW_VARIABLE": "value"
  }
}

Important: This replaces ALL variables. To preserve existing ones, include them in the request.

Copy Variables Between Environments

  1. Export from source environment:

    curl -H "Authorization: Bearer $TOKEN" \
      https://api.oec.sh/api/v1/projects/$PROJECT_ID/environments/$SOURCE_ENV_ID \
      | jq '.environment_vars' > source_vars.json
  2. Modify variables for target environment:

    • Edit source_vars.json
    • Update environment-specific values (keys, URLs, etc.)
  3. Import to target environment:

    curl -X PATCH \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d "{\"environment_vars\": $(cat source_vars.json)}" \
      https://api.oec.sh/api/v1/projects/$PROJECT_ID/environments/$TARGET_ENV_ID
  4. Redeploy target environment to apply changes


Variable History and Audit

Tracking Changes

Environment variable changes are NOT explicitly tracked in a separate history table. However, changes can be tracked via:

  1. Update Timestamp: ProjectEnvironment.write_date shows last modification
  2. User Tracking: ProjectEnvironment.write_by shows user who made last change
  3. Deployment Logs: Each deployment captures the variables used

View Last Modified

GET /api/v1/projects/{project_id}/environments/{env_id}

Response includes:

{
  "environment_vars": { ... },
  "updated_at": "2024-12-11T15:30:00Z",
  "write_by": "user-uuid-here"
}

Audit Best Practices

  • Document Changes: Keep changelog of variable additions/removals
  • Version Control: Store variable templates in Git (without sensitive values)
  • Review Deployments: Check deployment logs for variable injection
  • Monitor Access: Track who has project.environments.update permission

Troubleshooting

Problem: Variable Not Available in Odoo

Symptoms:

  • os.environ.get('MY_VAR') returns None
  • Variable defined in UI but not accessible in Python code

Cause: Variable was added but environment not redeployed

Solution:

  1. Navigate to environment page
  2. Click Deploy button
  3. Wait for deployment to complete (2-5 minutes)
  4. Variable will be available after deployment

Problem: Variable Value Contains Quotes

Symptoms:

  • Value like Hello "World" appears incorrectly in container

Cause: Shell escaping issues

Solution:

  • OEC.SH automatically handles escaping
  • If issues persist, use single quotes in value: Hello 'World'
  • For complex values with mixed quotes, use JSON encoding

Problem: Special Characters Break Variable

Symptoms:

  • Variable with $, !, or other special chars doesn't work
  • Shell expansion or interpretation occurs

Cause: Docker environment variable interpretation

Solution:

  1. Avoid shell metacharacters: $, !, `, \
  2. If needed, use URL encoding or Base64 encoding
  3. Example: KEY=$(echo 'my$ecret' | base64)KEY=bXkkZWNyZXQ=

Problem: Cannot Change Variable Key

Symptoms:

  • UI only allows editing value, not key

Cause: By design - key is immutable once created

Solution:

  1. Delete the existing variable
  2. Create new variable with desired key name
  3. Save changes
  4. Redeploy environment

Problem: Variables Not Preserved After Clone

Symptoms:

  • Cloned environment missing original environment's variables

Cause: Environment variables are environment-specific, not cloned

Solution:

  1. Export variables from source environment (see Bulk Export above)
  2. Import variables to cloned environment
  3. Modify environment-specific values (secrets, URLs)
  4. Redeploy cloned environment

API Reference

Get Environment (with Variables)

GET /api/v1/projects/{project_id}/environments/{env_id}
Authorization: Bearer {jwt_token}

Response (200 OK):

{
  "id": "env-uuid",
  "name": "acme-staging",
  "project_id": "proj-uuid",
  "environment_vars": {
    "API_KEY": "sk_live_...",
    "SMTP_HOST": "smtp.gmail.com"
  },
  "updated_at": "2024-12-11T15:30:00Z"
}

Update Environment Variables

PATCH /api/v1/projects/{project_id}/environments/{env_id}
Content-Type: application/json
Authorization: Bearer {jwt_token}

Request Body (Replace All):

{
  "environment_vars": {
    "API_KEY": "sk_live_new_key",
    "SMTP_HOST": "smtp.sendgrid.net",
    "NEW_VAR": "value"
  }
}

Response (200 OK):

{
  "id": "env-uuid",
  "environment_vars": {
    "API_KEY": "sk_live_new_key",
    "SMTP_HOST": "smtp.sendgrid.net",
    "NEW_VAR": "value"
  },
  "updated_at": "2024-12-11T16:00:00Z"
}

Error Responses

StatusError CodeDescription
400INVALID_REQUESTInvalid JSON format or missing fields
403PERMISSION_DENIEDMissing project.environments.update permission
404ENVIRONMENT_NOT_FOUNDEnvironment doesn't exist
409DEPLOYMENT_IN_PROGRESSCannot update during active deployment

Related Features


Best Practices

Naming Conventions

Use clear, descriptive variable names:

{
  "✅ Good Examples": {
    "STRIPE_SECRET_KEY": "...",
    "SMTP_HOST": "smtp.gmail.com",
    "MAX_UPLOAD_SIZE_MB": "10",
    "ENABLE_DEBUG_TOOLBAR": "false"
  },
  "❌ Bad Examples": {
    "key": "...",
    "host": "smtp.gmail.com",
    "max": "10",
    "debug": "false"
  }
}

Best Practices:

  • Use UPPER_SNAKE_CASE for keys
  • Prefix related variables: STRIPE_*, AWS_*, SMTP_*
  • Include units in name: TIMEOUT_SECONDS, SIZE_MB
  • Use descriptive names: FEATURE_FLAG not FLAG1

Environment-Specific Values

Use different values per environment:

Development:

{
  "API_BASE_URL": "https://api-dev.example.com",
  "DEBUG_MODE": "true",
  "LOG_LEVEL": "debug",
  "STRIPE_API_KEY": "sk_test_..."
}

Staging:

{
  "API_BASE_URL": "https://api-staging.example.com",
  "DEBUG_MODE": "false",
  "LOG_LEVEL": "info",
  "STRIPE_API_KEY": "sk_test_..."
}

Production:

{
  "API_BASE_URL": "https://api.example.com",
  "DEBUG_MODE": "false",
  "LOG_LEVEL": "warning",
  "STRIPE_API_KEY": "sk_live_..."
}

Secret Rotation

Regularly rotate sensitive variables:

  1. Generate new secret (API key, password, token)
  2. Add new variable with temporary name: API_KEY_NEW
  3. Deploy to test new secret
  4. Update code to use new variable
  5. Deploy again to apply code changes
  6. Delete old variable: API_KEY
  7. Rename: API_KEY_NEWAPI_KEY
  8. Final deploy to clean up

Documentation

Document variables in project README:

## Environment Variables
 
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `STRIPE_API_KEY` | Yes | - | Stripe secret key for payments |
| `SMTP_HOST` | Yes | - | SMTP server hostname |
| `DEBUG_MODE` | No | `false` | Enable debug toolbar |
| `MAX_UPLOAD_SIZE` | No | `5242880` | Max upload size in bytes (5MB) |

Next Steps


Last Updated: December 11, 2025 Applies to: OEC.SH v2.0+ Related Sprint: Sprint 2E41 - Documentation System