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:
| Type | Example Key | Example Value | Use Case |
|---|---|---|---|
| String | API_BASE_URL | https://api.example.com | URLs, text configuration |
| Number | MAX_CONNECTIONS | 100 | Numeric limits, thresholds |
| Boolean | DEBUG_MODE | true | Feature flags, toggles |
| JSON | SERVICE_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
- Specific to one environment (e.g.,
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
- Navigate to Dashboard → Projects → Select project
- Click Add Environment button
- Scroll to Environment Variables section (expandable)
- Click Add Variable
- Enter:
- Key: Variable name (uppercase, underscores, alphanumeric)
- ✅ Valid:
API_KEY,SMTP_HOST,MAX_WORKERS - ❌ Invalid:
api-key,smtp.host,max workers
- ✅ Valid:
- Value: Variable value (string, will be quoted in Docker)
- Key: Variable name (uppercase, underscores, alphanumeric)
- Click Add to confirm
- Repeat for additional variables
- Complete environment creation form
- Variables are applied on first deployment
Method 2: Edit Existing Environment
- Navigate to environment detail page
- Click Settings tab
- Scroll to Environment Variables section
- Click Add Variable button
- Fill in Key and Value fields
- Click Save Changes button (bottom of form)
- 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
- Navigate to environment Settings tab
- Find the variable in Environment Variables list
- Click Edit icon (pencil) next to the variable
- Modify the Value field (key cannot be changed)
- Click Save to confirm
- Click Save Changes at bottom of form
- 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
- Navigate to environment Settings tab
- Find variable in Environment Variables section
- Click Delete icon (trash can) next to variable
- Confirm deletion in modal dialog
- Click Save Changes at bottom of form
- 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
passVariable 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
passSMTP 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_threadslimit_memory_hard,limit_time_cpulog_level,log_handleradmin_passwd,dbfiltersmtp_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
| Aspect | odoo_config | environment_vars |
|---|---|---|
| Purpose | Odoo core configuration | Custom app configuration |
| File | Generates /etc/odoo/odoo.conf | Injected as ENV in Docker |
| Access | Odoo reads from config file | Accessible via os.environ |
| Examples | workers, log_level, db_filter | API_KEY, FEATURE_FLAG |
| Validation | Schema-validated by OdooConfig | Free-form key-value pairs |
System-Provided Variables
OEC.SH automatically sets these variables during deployment (cannot be overridden):
Database Connection
| Variable | Example Value | Description |
|---|---|---|
DB_HOST | {uuid}_db or pgbouncer-primary-{uuid} | PostgreSQL container hostname |
DB_PORT | 5432 or 6432 | Database port (5432 for direct, 6432 for PgBouncer) |
DB_NAME | {environment_uuid} | Database name (same as environment ID) |
DB_USER | odoo | PostgreSQL username |
DB_PASSWORD | auto_generated_24_chars | Securely generated password |
Note: Odoo reads these from odoo.conf, not environment variables directly.
File Paths
| Variable | Value | Description |
|---|---|---|
DATA_DIR | /var/lib/odoo | Odoo data directory (filestore, sessions) |
ADDONS_PATH | /mnt/extra-addons | Custom addons mount path |
CONFIG_PATH | /etc/odoo/odoo.conf | Odoo configuration file |
Container Metadata
| Variable | Example Value | Description |
|---|---|---|
CONTAINER_NAME | {uuid}_odoo | Odoo container name |
NETWORK_NAME | paasportal_net_{uuid} | Docker network name |
ENVIRONMENT_TYPE | staging | Environment 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_KEYnotKEY1 - ✅ 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
- User Updates Variables: Via UI or API
- Database Updated:
ProjectEnvironment.environment_varsmodified - Timestamp Updated:
write_dateset to current time - Deployment Required: Changes NOT applied to running container
- Redeploy Triggered: User initiates deployment
- New Container Created: Variables injected into new container
- Old Container Stopped: Previous container removed
- 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:
- UI Form ↔ Database (instant sync via API)
- Database → Docker Container (sync on deployment only)
- Container ↔ Odoo 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.jsonImport 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_IDenv_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
-
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 -
Modify variables for target environment:
- Edit
source_vars.json - Update environment-specific values (keys, URLs, etc.)
- Edit
-
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 -
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:
- Update Timestamp:
ProjectEnvironment.write_dateshows last modification - User Tracking:
ProjectEnvironment.write_byshows user who made last change - 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.updatepermission
Troubleshooting
Problem: Variable Not Available in Odoo
Symptoms:
os.environ.get('MY_VAR')returnsNone- Variable defined in UI but not accessible in Python code
Cause: Variable was added but environment not redeployed
Solution:
- Navigate to environment page
- Click Deploy button
- Wait for deployment to complete (2-5 minutes)
- 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:
- Avoid shell metacharacters:
$,!,`,\ - If needed, use URL encoding or Base64 encoding
- 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:
- Delete the existing variable
- Create new variable with desired key name
- Save changes
- 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:
- Export variables from source environment (see Bulk Export above)
- Import variables to cloned environment
- Modify environment-specific values (secrets, URLs)
- 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
| Status | Error Code | Description |
|---|---|---|
| 400 | INVALID_REQUEST | Invalid JSON format or missing fields |
| 403 | PERMISSION_DENIED | Missing project.environments.update permission |
| 404 | ENVIRONMENT_NOT_FOUND | Environment doesn't exist |
| 409 | DEPLOYMENT_IN_PROGRESS | Cannot update during active deployment |
Related Features
- Edit Environment - Modify environment configuration including variables
- Deploy Environment - Apply variable changes by redeploying
- Odoo Configuration - Configure Odoo-specific parameters (odoo.conf)
- Clone Environment - Duplicate environments (variables not cloned)
- Deployment Logs - View deployment logs to verify variable injection
- Permissions Management - Control who can update environment variables
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_CASEfor keys - Prefix related variables:
STRIPE_*,AWS_*,SMTP_* - Include units in name:
TIMEOUT_SECONDS,SIZE_MB - Use descriptive names:
FEATURE_FLAGnotFLAG1
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:
- Generate new secret (API key, password, token)
- Add new variable with temporary name:
API_KEY_NEW - Deploy to test new secret
- Update code to use new variable
- Deploy again to apply code changes
- Delete old variable:
API_KEY - Rename:
API_KEY_NEW→API_KEY - 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
- Deploy Environment to apply variable changes
- Configure Odoo Settings for Odoo-specific parameters
- View Deployment Logs to verify variable injection
- Setup Backup Policy to protect your configuration
Last Updated: December 11, 2025 Applies to: OEC.SH v2.0+ Related Sprint: Sprint 2E41 - Documentation System