Docker provides a reproducible, isolated environment for running Odoo. Instead of installing Python, PostgreSQL, and system dependencies directly on a host machine, containers package each service into a self-contained unit. This guide covers Docker Compose setup for both development and production, including PostgreSQL configuration, persistent volumes, and reverse proxy setup.
Docker solves several problems that are common in traditional Odoo deployments:
docker compose plugin, not the legacy docker-compose binary)A minimal Odoo development setup with Docker Compose requires just two services: Odoo and PostgreSQL. Here is a working docker-compose.yml for development:
version: "3.8"
services:
odoo:
image: odoo:17
depends_on:
- db
ports:
- "8069:8069"
volumes:
- odoo-data:/var/lib/odoo
- ./addons:/mnt/extra-addons
environment:
- HOST=db
- USER=odoo
- PASSWORD=odoo
restart: unless-stopped
db:
image: postgres:15
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=odoo
- POSTGRES_PASSWORD=odoo
volumes:
- db-data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
odoo-data:
db-data:Key points about this configuration:
odoo-data and db-data) persist data across container restarts. Without them, all data is lost when containers are removed../addons bind mount lets you develop custom modules on the host and see changes reflected inside the container immediately.HOST, USER, PASSWORD) tell Odoo how to connect to PostgreSQL.Start the stack with docker compose up -d. Odoo will be accessible at http://localhost:8069. The first startup takes a few minutes while Odoo initializes the database and installs base modules.
A production Docker setup needs additional configuration beyond the development stack. Here is a production-ready example with health checks, resource limits, and proper security:
version: "3.8"
services:
odoo:
image: odoo:17
depends_on:
db:
condition: service_healthy
volumes:
- odoo-data:/var/lib/odoo
- ./addons:/mnt/extra-addons
- ./config:/etc/odoo
environment:
- HOST=db
- USER=odoo
- PASSWORD=${DB_PASSWORD}
deploy:
resources:
limits:
cpus: "2"
memory: 2G
reservations:
cpus: "1"
memory: 1G
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8069/web/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
restart: always
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
- odoo-net
db:
image: postgres:15
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=odoo
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- db-data:/var/lib/postgresql/data
deploy:
resources:
limits:
cpus: "1"
memory: 1G
healthcheck:
test: ["CMD-SHELL", "pg_isready -U odoo"]
interval: 10s
timeout: 5s
retries: 5
restart: always
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
- odoo-net
volumes:
odoo-data:
db-data:
networks:
odoo-net:
driver: bridgeImportant production practices:
depends_on condition ensures Odoo waits for PostgreSQL to be ready before starting..env file, not in the docker-compose.yml. Never commit the .env file to version control.Odoo should never be exposed directly on port 8069 in production. An Nginx reverse proxy handles SSL termination, static file caching, WebSocket support for live chat, and security headers.
server {
listen 80;
server_name odoo.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name odoo.example.com;
ssl_certificate /etc/letsencrypt/live/odoo.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/odoo.example.com/privkey.pem;
proxy_buffers 16 64k;
proxy_buffer_size 128k;
location / {
proxy_pass http://127.0.0.1:8069;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /websocket {
proxy_pass http://127.0.0.1:8069;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location ~* /web/static/ {
proxy_pass http://127.0.0.1:8069;
proxy_cache_valid 200 90m;
proxy_buffering on;
expires 864000;
}
client_max_body_size 200m;
}The configuration above handles HTTP-to-HTTPS redirection, WebSocket proxying for Odoo's live chat module, static file caching to reduce load on the Odoo container, and a generous body size limit for file uploads.
Use Certbot to obtain and auto-renew SSL certificates:
sudo apt install certbot python3-certbot-nginxsudo certbot --nginx -d odoo.example.comsudo certbot renew --dry-runCertbot installs a systemd timer that automatically renews certificates before they expire. No manual intervention is needed after initial setup.
Backing up a Docker-based Odoo setup involves two components: the PostgreSQL database and the filestore (attachments, documents, and binary fields).
#!/bin/bash
# Database backup
docker compose exec -T db pg_dump -U odoo odoo_db > backup_$(date +%Y%m%d_%H%M%S).sql
# Filestore backup
docker run --rm -v odoo-sh-front_odoo-data:/data -v $(pwd):/backup \
alpine tar czf /backup/filestore_$(date +%Y%m%d_%H%M%S).tar.gz /dataAutomate these scripts with cron to run daily. Store backups off-site — a named volume on the same server does not protect against disk failure.
One of Docker's advantages is that data lives in volumes, not containers. Updating Odoo is straightforward:
docker compose pull odoodocker compose up -ddocker compose exec odoo odoo -u all -d odoo_db --stop-after-initThe named volumes remain intact throughout the process. Only the container (the running process) is replaced.
Running Odoo in Docker gives you control over the stack. These hosting options build on that control with managed infrastructure and developer-friendly tooling.