Odoo Docker Deployment: Production Guide
Deploy Odoo with Docker Compose for production environments. Complete setup with PostgreSQL, Nginx reverse proxy, SSL certificates, automated backups, and security best practices.
Quick Start: Odoo Docker in 5 Minutes
The fastest way to get Odoo running locally. This uses the official Odoo Docker image from Docker Hub, maintained by Odoo S.A.
One-Command Start
Run Odoo 19 with PostgreSQL using a single docker compose command. Create this file and run it:
services:
odoo:
image: odoo:19.0
depends_on: [db]
ports:
- "8069:8069"
environment:
- HOST=db
- USER=odoo
- PASSWORD=odoo
db:
image: postgres:16
environment:
- POSTGRES_USER=odoo
- POSTGRES_PASSWORD=odoo
- POSTGRES_DB=postgresdocker compose up -d
# Open http://localhost:8069 in your browserThat is it. Odoo is running at localhost:8069. Create your first database through the web interface.
Docker Hub Image Tags
Official Odoo images are tagged by version. Pick the version you need:
| Image Tag | Odoo Version | Status |
|---|---|---|
| odoo:19.0 | Odoo 19 | Latest stable |
| odoo:18.0 | Odoo 18 LTS | Long-term support |
| odoo:17.0 | Odoo 17 | Supported |
| odoo:16.0 | Odoo 16 | Maintenance only |
This minimal setup is for testing only. For production, you need persistent volumes, Nginx, SSL, backups, and security hardening. All of this is covered in the sections below.
Why Docker for Odoo?
Docker has become the standard way to deploy Odoo in production. The official Odoo Docker images are well-maintained and used by organizations of all sizes. If you are deploying Odoo from scratch or containerizing an existing installation, here is why Docker works well for Odoo:
Isolation and Consistency
Docker containers run your Odoo installation identically across development, staging, and production. No more 'works on my machine' issues.
Easy Version Management
Switch between Odoo versions by changing a single line in your docker-compose file. Run multiple versions side-by-side for testing upgrades.
Simplified Deployment
Deploy your entire Odoo stack with a single command. Docker Compose orchestrates Odoo, PostgreSQL, and Nginx together.
Horizontal Scaling
When demand increases, Docker lets you scale Odoo workers across multiple containers or servers using orchestration tools like Swarm or Kubernetes.
Prerequisites
Before starting your Odoo Docker deployment, make sure you have the following in place:
Domain Name
A registered domain pointing to your server IP
VPS or Server
Minimum 4GB RAM, 2 vCPUs, 40GB storage
Basic Linux Knowledge
Familiarity with command line and SSH
Project Structure
Create a well-organized directory structure for your Odoo Docker deployment. This structure separates configuration, custom modules, and data for maintainability:
odoo-docker/
├── docker-compose.yml # Main compose file
├── docker-compose.override.yml # Development overrides
├── .env # Environment variables
├── nginx/
│ └── odoo.conf # Nginx configuration
├── addons/
│ └── custom_modules/ # Your custom Odoo modules
├── config/
│ └── odoo.conf # Odoo configuration file
└── backups/
└── .gitkeep # Backup storage directoryCreate this structure with the following commands:
mkdir -p odoo-docker/{nginx,addons/custom_modules,config,backups}
cd odoo-docker
touch docker-compose.yml .env
touch nginx/odoo.conf config/odoo.confDocker Compose Configuration
The docker-compose.yml file defines your entire Odoo stack. This production-ready configuration includes Odoo, PostgreSQL, and proper networking:
version: '3.8'
services:
odoo:
image: odoo:19.0
container_name: odoo
depends_on:
- db
ports:
- "8069:8069"
- "8072:8072" # Longpolling port
volumes:
- odoo-data:/var/lib/odoo
- ./addons:/mnt/extra-addons
- ./config:/etc/odoo
environment:
- HOST=db
- USER=odoo
- PASSWORD=${POSTGRES_PASSWORD}
command: ["--proxy-mode"]
restart: unless-stopped
networks:
- odoo-network
db:
image: postgres:15
container_name: odoo-db
environment:
- POSTGRES_USER=odoo
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=postgres
- PGDATA=/var/lib/postgresql/data/pgdata
volumes:
- postgres-data:/var/lib/postgresql/data/pgdata
restart: unless-stopped
networks:
- odoo-network
# Uncomment for external access (not recommended for production)
# ports:
# - "5432:5432"
nginx:
image: nginx:alpine
container_name: odoo-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/odoo.conf:/etc/nginx/conf.d/default.conf:ro
- ./certbot/conf:/etc/letsencrypt:ro
- ./certbot/www:/var/www/certbot:ro
depends_on:
- odoo
restart: unless-stopped
networks:
- odoo-network
volumes:
odoo-data:
postgres-data:
networks:
odoo-network:
driver: bridgeKey Configuration Notes
- - The
--proxy-modeflag enables Odoo to work correctly behind a reverse proxy - - Port 8072 is used for longpolling (real-time features like chat)
- - Named volumes ensure data persists across container restarts
Odoo Docker: Community vs Enterprise
The official Docker images on Docker Hub are for Odoo Community Edition only. Enterprise requires a different approach.
Community Edition (Free)
- - Pull directly from Docker Hub:
docker pull odoo:19.0 - - Full source available, LGPL-3.0 license
- - Supports 1000+ OCA modules for added functionality
- - No Odoo subscription required
Enterprise Edition (Requires License)
- - Not available on public Docker Hub
- - Requires active Odoo Enterprise subscription
- - Build a custom image by adding Enterprise addons to the Community base image
- - Enterprise source available from Odoo's private GitHub repositories
FROM odoo:19.0
# Copy Enterprise addons (requires Odoo subscription)
COPY ./enterprise /mnt/enterprise-addons
# Add Enterprise addons to the addons path
ENV ODOO_EXTRA_ADDONS=/mnt/enterprise-addonsOEC.sh supports both editions. Deploy Community for free or bring your Enterprise license. OCA modules are supported out of the box. See our OCA modules guide.
Environment Variables
Store sensitive configuration in a .env file. Never commit this file to version control.
# Database Configuration
POSTGRES_PASSWORD=your_secure_password_here
# Odoo Configuration (optional overrides)
ODOO_DB_HOST=db
ODOO_DB_PORT=5432
ODOO_DB_USER=odoo
# Domain Configuration
DOMAIN=erp.yourcompany.com
# Email Configuration (optional)
SMTP_HOST=smtp.yourprovider.com
SMTP_PORT=587
SMTP_USER=your_email@yourcompany.com
SMTP_PASSWORD=your_smtp_passwordSecurity Warning
Use a strong, unique password for POSTGRES_PASSWORD. Add .env to your .gitignore file. In production, consider using Docker secrets or a secrets manager.
You can also create an Odoo configuration file for additional settings:
[options]
addons_path = /mnt/extra-addons
data_dir = /var/lib/odoo
# Database settings
db_host = db
db_port = 5432
db_user = odoo
db_password = False # Set via environment variable
# Performance tuning
workers = 4
max_cron_threads = 2
limit_memory_hard = 2684354560
limit_memory_soft = 2147483648
limit_time_cpu = 600
limit_time_real = 1200
# Proxy settings
proxy_mode = True
# Logging
logfile = /var/log/odoo/odoo.log
log_level = infoNginx Reverse Proxy
Nginx acts as a reverse proxy handling SSL termination, static file caching, and WebSocket connections for Odoo real-time features:
upstream odoo {
server odoo:8069;
}
upstream odoo-longpolling {
server odoo:8072;
}
# HTTP - redirect all requests to HTTPS
server {
listen 80;
server_name erp.yourcompany.com;
# Let's Encrypt challenge
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS
server {
listen 443 ssl http2;
server_name erp.yourcompany.com;
# SSL certificates
ssl_certificate /etc/letsencrypt/live/erp.yourcompany.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/erp.yourcompany.com/privkey.pem;
# SSL settings
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Proxy settings
proxy_read_timeout 720s;
proxy_connect_timeout 720s;
proxy_send_timeout 720s;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
# Gzip compression
gzip on;
gzip_types text/css text/plain text/xml application/xml application/javascript application/json;
gzip_min_length 1000;
# Longpolling (real-time features)
location /longpolling {
proxy_pass http://odoo-longpolling;
}
# WebSocket support
location /websocket {
proxy_pass http://odoo-longpolling;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Main Odoo application
location / {
proxy_pass http://odoo;
proxy_redirect off;
}
# Static files caching
location ~* /web/static/ {
proxy_pass http://odoo;
proxy_cache_valid 200 60m;
expires 24h;
add_header Cache-Control "public, immutable";
}
# File upload size limit
client_max_body_size 100M;
}SSL with Lets Encrypt
Set up free SSL certificates using Certbot. First, create a temporary Nginx config for the initial certificate request, then switch to the full configuration:
Step 1: Initial Setup
# Create directories for Certbot
mkdir -p certbot/conf certbot/www
# Start Nginx with a basic config first (HTTP only)
# Create a temporary nginx config that serves HTTP
cat > nginx/odoo.conf << 'EOF'
server {
listen 80;
server_name erp.yourcompany.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 200 'OK';
add_header Content-Type text/plain;
}
}
EOF
# Start Nginx
docker compose up -d nginxStep 2: Obtain Certificate
# Request SSL certificate
docker run -it --rm \
-v ./certbot/conf:/etc/letsencrypt \
-v ./certbot/www:/var/www/certbot \
certbot/certbot certonly \
--webroot \
--webroot-path=/var/www/certbot \
--email your@email.com \
--agree-tos \
--no-eff-email \
-d erp.yourcompany.comStep 3: Enable Full Configuration
# Replace nginx config with the full SSL version (shown above)
# Then restart Nginx
docker compose restart nginx
# Set up auto-renewal (add to crontab)
0 0 1 * * docker run --rm -v ./certbot/conf:/etc/letsencrypt -v ./certbot/www:/var/www/certbot certbot/certbot renew --quiet && docker compose restart nginxOdoo Docker for Development
Use Docker Compose overrides to create a development-friendly setup with hot-reloading and debugging tools, without touching your production configuration.
services:
odoo:
# Mount source code for live editing
volumes:
- ./custom-addons:/mnt/extra-addons
- ./odoo.conf:/etc/odoo/odoo.conf
# Enable developer mode with auto-reload
command: ["--dev=reload,qweb,xml", "--proxy-mode"]
# Expose debug port
ports:
- "8069:8069"
- "8072:8072"
# pgAdmin for database inspection
pgadmin:
image: dpage/pgadmin4
ports:
- "5050:80"
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: admin
depends_on: [db]Hot Reload
The --dev=reload flag watches Python files for changes and automatically restarts workers. XML and QWeb template changes apply without restart using --dev=qweb,xml.
Database Management
Access pgAdmin at localhost:5050 to inspect tables, run queries, and monitor database performance. Connect using the PostgreSQL container hostname (db) and the credentials from your .env file.
Docker Compose automatically merges docker-compose.override.yml with your main file. Run docker compose up and both files apply. Use docker compose -f docker-compose.yml up to skip the override for production-like testing.
Data Persistence
Docker volumes keep your data intact across container restarts and updates. Understanding volume management matters for any production deployment:
odoo-data Volume
Contains the Odoo filestore including attachments, session files, and uploaded documents.
/var/lib/odoopostgres-data Volume
Contains the PostgreSQL database files with all your business data.
/var/lib/postgresql/data# List Docker volumes
docker volume ls
# Inspect a volume
docker volume inspect odoo-docker_odoo-data
# Find volume location on host
docker volume inspect odoo-docker_postgres-data --format '{{ .Mountpoint }}'Backup Strategy
You need a reliable backup strategy for any production Odoo deployment. Back up both the database and filestore regularly. For more backup strategies including automated off-site storage, see our Odoo backup and recovery guide:
Database Backup Script
#!/bin/bash
# Configuration
BACKUP_DIR="./backups"
DATE=$(date +%Y%m%d_%H%M%S)
DB_CONTAINER="odoo-db"
DB_USER="odoo"
RETENTION_DAYS=30
# Create backup directory
mkdir -p $BACKUP_DIR
# Backup database
echo "Backing up database..."
docker exec $DB_CONTAINER pg_dumpall -U $DB_USER | gzip > "$BACKUP_DIR/db_backup_$DATE.sql.gz"
# Backup Odoo filestore
echo "Backing up filestore..."
docker run --rm \
-v odoo-docker_odoo-data:/data:ro \
-v $(pwd)/$BACKUP_DIR:/backup \
alpine tar czf /backup/filestore_backup_$DATE.tar.gz -C /data .
# Remove old backups
echo "Removing backups older than $RETENTION_DAYS days..."
find $BACKUP_DIR -name "*.gz" -mtime +$RETENTION_DAYS -delete
echo "Backup completed: $DATE"
echo "Files:"
ls -lh $BACKUP_DIR/*_$DATE.*Restore from Backup
# Restore database
gunzip -c backups/db_backup_20241220_030000.sql.gz | docker exec -i odoo-db psql -U odoo
# Restore filestore
docker run --rm \
-v odoo-docker_odoo-data:/data \
-v $(pwd)/backups:/backup:ro \
alpine tar xzf /backup/filestore_backup_20241220_030000.tar.gz -C /dataOff-site Backup with S3
# Install AWS CLI and configure credentials
# Then add to your backup script:
# Upload to S3
aws s3 cp $BACKUP_DIR/db_backup_$DATE.sql.gz s3://your-bucket/odoo-backups/
aws s3 cp $BACKUP_DIR/filestore_backup_$DATE.tar.gz s3://your-bucket/odoo-backups/
# Or use rclone for other providers (Backblaze B2, Cloudflare R2, etc.)
rclone copy $BACKUP_DIR remote:odoo-backups/Backup Best Practices
- - Automate backups using cron (daily minimum)
- - Store backups off-site (different location/provider)
- - Test restore procedures regularly
- - Encrypt sensitive backups before upload
Production Hardening
A basic Docker setup works for testing. Production deployments need security hardening, resource controls, and monitoring.
Run as Non-Root User
The official Odoo image already runs as the odoo user (UID 101). Verify this is maintained in your compose file and never override with user: root.
Resource Limits
Prevent runaway containers from consuming all server resources:
services:
odoo:
deploy:
resources:
limits:
cpus: '4.0'
memory: 4G
reservations:
cpus: '1.0'
memory: 1G
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8069/web/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "5"
db:
deploy:
resources:
limits:
memory: 2GNetwork Isolation
Only Nginx should be exposed to the internet. Odoo and PostgreSQL should communicate on an internal Docker network:
- - Never expose port 5432 (PostgreSQL) to the host
- - Never expose port 8069 directly in production; route through Nginx
- - Use Docker network policies to restrict container-to-container traffic
Restart Policies and Health Checks
Use restart: unless-stopped for all production services. The health check above lets Docker detect unresponsive Odoo instances and automatically restart them. Set start_period high enough for Odoo to initialize (60s minimum, longer for large databases).
Scaling Considerations
As your Odoo deployment grows, you will need to scale resources. For detailed performance optimization techniques, here are considerations for production scaling:
Horizontal Scaling with Workers
Odoo supports multiple worker processes to handle concurrent requests. Configure workers based on CPU cores:
# In odoo.conf - Rule of thumb: (CPU cores * 2) + 1
workers = 4
max_cron_threads = 2
limit_memory_hard = 2684354560
limit_memory_soft = 2147483648Load Balancing
For high-availability deployments, use multiple Odoo containers behind a load balancer. Enable sticky sessions for session consistency:
upstream odoo_cluster {
ip_hash; # Sticky sessions
server odoo1:8069;
server odoo2:8069;
server odoo3:8069;
}Redis for Session Storage
When scaling horizontally, use Redis for shared session storage across Odoo instances:
services:
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
- redis-data:/data
networks:
- odoo-networkTroubleshooting Odoo Docker
Common issues and how to fix them.
Container won't start: "database connection refused"
The Odoo container starts before PostgreSQL is ready. Add a depends_on with a health check condition:
services:
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U odoo"]
interval: 5s
timeout: 5s
retries: 5
odoo:
depends_on:
db:
condition: service_healthy"Permission denied" on volume mounts
The Odoo container runs as UID 101 (odoo user). Your host-mounted directories must be owned by this UID. Fix with: sudo chown -R 101:101 ./addons ./config. Named volumes (like odoo-data:) are managed by Docker and don't have this issue.
Worker timeout: "Worker (PID) exceeded max time"
This happens when a request exceeds the configured timeout. Common causes: large report generation, heavy data imports, or not enough RAM. Increase limit_time_real in odoo.conf (default 120s) and check that your container has enough memory allocated.
Slow performance with high database query times
Default PostgreSQL settings are conservative. Tune shared_buffers (25% of RAM), work_mem (64MB), and effective_cache_size (75% of RAM). Mount a custom postgresql.conf into the container. See our PostgreSQL tuning for Odoo guide.
Custom addons not loading
Check that your addons are in the correct path and that addons_path in odoo.conf includes your mount point. After adding new modules, restart Odoo and update the apps list from Settings > Apps > Update Apps List. Check container logs with docker compose logs odoo for path errors.
Odoo Docker on Cloud Providers
This guide works on any Linux server with Docker installed. Here are provider-specific guides for deploying your Odoo Docker stack:
When to Use Managed Hosting
Self-managing Docker gives you full control but adds real operational overhead. Consider managed hosting when:
Self-Managed Docker
- Full control over infrastructure
- Potentially lower hosting costs
- Requires DevOps expertise
- Manual backup management
- Security updates on you
Managed Hosting (OEC.sh)
- Automated backups and SSL
- Choose from 8+ cloud providers
- Git integration for deployments
- Monitoring and alerting built-in
- Focus on your business, not servers
Managing Docker infrastructure is not your core business?
OEC.sh handles all the complexity of Odoo Docker deployments while giving you full control. Deploy to your preferred cloud provider in minutes, not hours.
- Free tier available
- No credit card required
- 5-minute setup
Frequently Asked Questions
Is Docker good for Odoo production?
Yes, Docker works well for production Odoo when configured properly. The official Odoo Docker images are maintained by Odoo S.A. and used by thousands of organizations. The key is proper configuration of persistent volumes, backups, security settings, and resource limits. For production deployments where you don't want to manage the Docker stack yourself, OEC.sh handles the operational side while you keep full control of your infrastructure.
What's the best Docker image for Odoo?
The official Odoo Docker images from Docker Hub (odoo:17.0, odoo:16.0, etc.) are the best choice for most deployments. These images are maintained by Odoo S.A., regularly updated, and optimized for production use. They include all necessary dependencies and follow Docker best practices. For Enterprise, you'll need to build a custom image extending the official Community image. Avoid unofficial third-party images as they may have security vulnerabilities or lack proper maintenance.
How do I backup Odoo in Docker?
Backup both the PostgreSQL database and Odoo filestore. For the database, use 'docker exec db pg_dump -U odoo postgres > backup.sql'. For filestore, backup the mounted odoo-data volume using 'docker run --rm -v odoo-data:/data -v ./backups:/backup alpine tar czf /backup/filestore.tar.gz /data'. Automate with cron and store backups off-site using S3, Backblaze B2, or Cloudflare R2. OEC.sh provides automated daily backups to 7+ storage providers with one-click restore. For a complete guide, see our Odoo backup and recovery documentation.
Should I use Docker Compose for Odoo?
Yes, Docker Compose is the standard way to run Odoo in Docker. It handles orchestration of multiple containers (Odoo, PostgreSQL, Nginx) and manages networking, volumes, and environment variables in a single file. You can version control your entire infrastructure config this way. For large-scale production, consider Kubernetes or managed platforms like OEC.sh, but Docker Compose works great for small to medium deployments and development environments.
How many workers should I configure in Docker?
For production Odoo in Docker, use the formula: workers = (CPU cores * 2) + 1. For example, 4 CPU cores = 9 workers. Each worker consumes 150-300MB RAM, so set your container memory limits accordingly. Configure this in your odoo.conf file mounted into the container. Also set max_cron_threads (typically 2), limit_memory_hard (2.5GB per worker), and limit_memory_soft (2GB per worker). Monitor resource usage and adjust based on actual workload. For detailed performance tuning, see our Odoo performance optimization guide.
How to scale Odoo with Docker?
Scale Odoo horizontally by running multiple Odoo containers behind a load balancer (Nginx or HAProxy) with sticky sessions enabled. Share the PostgreSQL database and filestore volume across instances, or use Redis for session storage. Configure your docker-compose.yml to run multiple replicas: 'docker compose up --scale odoo=3'. For high availability, use Docker Swarm or Kubernetes. Scaling Docker manually does require ops expertise. OEC.sh handles horizontal scaling automatically if you'd rather not manage it yourself.
How do I update Odoo in Docker?
To update Odoo in Docker: (1) Backup your database and filestore first, (2) Pull the new image with 'docker pull odoo:17.0', (3) Stop the current container with 'docker compose down', (4) Update the image tag in docker-compose.yml, (5) Start with 'docker compose up -d'. For major version upgrades (e.g., 16.0 to 17.0), use Odoo's database migration tools or OpenUpgrade first. Always test upgrades in a staging environment before applying to production.
Can I run multiple Odoo instances in Docker?
Yes, run multiple Odoo instances by creating separate docker-compose files in different directories, or use different service names and ports. Each instance needs its own database (can share the PostgreSQL server). Use Nginx virtual hosts to route traffic based on domain names. This is useful for running multiple companies, staging environments, or different Odoo versions side-by-side. For complex multi-tenant setups, consider OEC.sh which manages multiple instances with isolated environments.
Need Help with Your Odoo Docker Deployment?
Running Docker yourself or using a managed platform? Either way, we can help you get Odoo running in production.
Related Resources
Docker Compose for Odoo
Production-ready Docker Compose setup with PostgreSQL and Nginx.
Odoo Docker Hub Reference
Official image tags, versions, and environment variables.
Enterprise vs Community Docker
Run Enterprise or Community Edition with Docker.
Odoo Ubuntu Install Guide
Install Odoo natively on Ubuntu without Docker.
Nginx Reverse Proxy Config
Production Nginx config with SSL and websocket support.
Docker Compose Generator
Generate production-ready Docker Compose files visually.
Deploy Odoo Guide
Step-by-step guide to deploy Odoo on any cloud provider.
Server Requirements Calculator
Calculate the right server size for your Odoo workload.
OEC.sh Pricing
Free tier available. Deploy Odoo on any cloud provider.