Docker Odoo Deployment: Production-Ready Container Setup
Docker has become the standard deployment method for Odoo in production environments, with over 10 million Odoo Docker image pulls on Docker Hub. Containerized deployment provides reproducible environments, easy scaling, simplified backups, and clean separation between Odoo instances. This guide covers a complete production-ready Docker Odoo setup.
Key Takeaways
- Docker Compose orchestrates Odoo, PostgreSQL, and Nginx as connected services
- Persistent volumes ensure data survives container restarts and updates
- Nginx reverse proxy handles SSL termination, static file caching, and load balancing
- Automated backups protect against data loss with minimal manual intervention
Architecture Overview
A production Odoo Docker deployment consists of three core services:
- Odoo application server: The Odoo container running the web server and application logic
- PostgreSQL database: A dedicated database container storing all Odoo data
- Nginx reverse proxy: Handles SSL, serves static files, and proxies requests to Odoo
Optional services include Redis (for caching and session storage), a backup container (automated database dumps), and monitoring (Prometheus + Grafana).
Docker Compose Configuration
The foundation of the deployment is a docker-compose.yml file defining all services:
version: "3.8"
services:
odoo:
image: odoo:17.0
depends_on:
- db
ports:
- "8069:8069"
- "8072:8072"
volumes:
- odoo-data:/var/lib/odoo
- ./config:/etc/odoo
- ./addons:/mnt/extra-addons
environment:
- HOST=db
- PORT=5432
- USER=odoo
- PASSWORD=odoo_db_password
restart: unless-stopped
db:
image: postgres:16
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=odoo
- POSTGRES_PASSWORD=odoo_db_password
- PGDATA=/var/lib/postgresql/data/pgdata
volumes:
- postgres-data:/var/lib/postgresql/data/pgdata
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
depends_on:
- odoo
restart: unless-stopped
volumes:
odoo-data:
postgres-data:
Odoo Configuration File
Create config/odoo.conf with production-optimized settings:
[options]
addons_path = /mnt/extra-addons,/usr/lib/python3/dist-packages/odoo/addons
admin_passwd = your_strong_admin_password
db_host = db
db_port = 5432
db_user = odoo
db_password = odoo_db_password
db_name = production_db
dbfilter = ^production_db$
list_db = False
proxy_mode = True
workers = 4
max_cron_threads = 2
limit_memory_hard = 2684354560
limit_memory_soft = 2147483648
limit_time_cpu = 600
limit_time_real = 1200
log_level = warn
Key settings explained:
proxy_mode = True: Required when running behind Nginx to correctly handle forwarded headersworkers = 4: Number of worker processes (rule of thumb: 2 workers per CPU core)max_cron_threads = 2: Dedicated threads for scheduled actionslist_db = False: Prevents database listing on the login page (security)dbfilter: Restricts access to a single database
Nginx Reverse Proxy Configuration
upstream odoo {
server odoo:8069;
}
upstream odoochat {
server odoo:8072;
}
server {
listen 80;
server_name your-domain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
client_max_body_size 200m;
proxy_read_timeout 720s;
proxy_connect_timeout 720s;
proxy_send_timeout 720s;
location /websocket {
proxy_pass http://odoochat;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
proxy_pass http://odoo;
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;
}
location ~* /web/static/ {
proxy_cache_valid 200 90m;
proxy_buffering on;
expires 864000;
proxy_pass http://odoo;
}
}
Backup Strategy
Automated Database Backups
Create a backup script that runs daily via cron:
#!/bin/bash
BACKUP_DIR=/backups/odoo
DATE=$(date +%Y%m%d_%H%M%S)
docker exec odoo-db pg_dump -U odoo production_db | gzip > $BACKUP_DIR/odoo_$DATE.sql.gz
find $BACKUP_DIR -name "*.sql.gz" -mtime +30 -delete
Filestore Backup
The Odoo filestore (uploaded documents, attachments) lives in the odoo-data volume. Back it up alongside the database:
docker run --rm -v odoo-data:/data -v /backups:/backup alpine \
tar czf /backup/odoo-filestore_$DATE.tar.gz /data
Security Hardening
- Change default
admin_passwdin odoo.conf to a strong random password - Set
list_db = Falseto prevent database enumeration - Use
dbfilterto restrict database access to a single database - Run containers as non-root users where possible
- Keep Docker images updated with security patches
- Use Docker secrets or environment variable files (not inline passwords)
- Restrict container network access using Docker networks
Frequently Asked Questions
Q: How many concurrent users can a single Odoo container handle?
With 4 workers, an Odoo container handles approximately 20-30 concurrent users comfortably. For higher concurrency, increase workers (limited by CPU/RAM) or deploy multiple Odoo containers behind a load balancer.
Q: How do I update Odoo in Docker?
Pull the new image, stop the current container, run the new image with the existing volumes. The persistent volumes retain your data and filestore across image updates.
Q: Should I use the official Odoo Docker image or build my own?
The official image works for standard deployments. Build a custom image when you need specific Python packages, system dependencies for custom modules, or pre-installed community modules.
Q: How do I handle Odoo Enterprise in Docker?
Odoo Enterprise is not available as a public Docker image. Mount the enterprise addons directory as a volume and reference it in the addons_path configuration. You need a valid Odoo Enterprise subscription.
What Is Next
Docker deployment gives you a reproducible, scalable, and maintainable Odoo production environment. Start with this configuration and adjust worker counts, memory limits, and caching based on your usage patterns.
Contact ECOSIRE for Odoo deployment assistance, or explore our Odoo support services for managed hosting.
Published by ECOSIRE -- helping businesses scale with enterprise software solutions.
Written by
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
ECOSIRE
Transform Your Business with Odoo ERP
Expert Odoo implementation, customization, and support to streamline your operations.
Related Articles
How to Add a Custom Button to an Odoo Form View (2026)
Add custom action buttons to Odoo 19 form views: Python action method, view inheritance, conditional visibility, confirmation dialogs. Production-tested.
How to Add a Custom Field in Odoo Without Studio (2026)
Add custom fields via custom module in Odoo 19: model inheritance, view extension, computed fields, store/non-store decisions. Code-first, version-controlled.
How to Add a Custom Report in Odoo Using External Layout
Build a branded PDF report in Odoo 19 using web.external_layout: QWeb template, paperformat, action binding. With print logo + footer overrides.