Running Odoo in Docker gives you a clean, reproducible environment that works the same on your laptop, a staging server, or a production VPS. Docker isolates Odoo and PostgreSQL from the rest of your system, makes upgrades safer, and lets you spin up multiple Odoo instances on a single server without conflicts. This guide walks through a complete Docker Compose setup for Odoo, covers common pitfalls, and explains how to keep your data safe when updating.
Why Use Docker for Odoo?
Before diving into configuration, it's worth understanding why Docker is a good fit for Odoo specifically.
- Isolation: Odoo depends on specific Python versions, system libraries, and PostgreSQL versions. Docker containers encapsulate all of these, so two Odoo instances on the same host can run different Python or Postgres versions without conflict.
- Reproducibility: A
docker-compose.ymlfile is a complete, version-controlled description of your stack. You can recreate the exact same environment on any machine. - Easier upgrades: Upgrading Odoo means pulling a new image tag and restarting the container. Your database and filestore live in volumes and are untouched by the image swap.
- Multi-instance hosting: Need three separate Odoo installations on one server? Run three Compose stacks on different ports, each with its own database container.
The trade-off is complexity. You need to manage volumes, networking, and container lifecycle. The sections below make this straightforward.
Prerequisites
Before you start, you need:
- A Linux server (Ubuntu 22.04 LTS recommended) or a local machine with Docker Desktop
- Docker Engine 24+ and Docker Compose v2 installed
- At least 2 GB RAM and 20 GB disk for a small Odoo instance
- A domain name pointed at your server (for production SSL)
For server requirements, see our guide on Odoo server requirements.
Installing Docker on Ubuntu
If Docker isn't installed yet, the official convenience script is the fastest path:
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
Verify the installation:
docker --version
docker compose version
Production-Ready docker-compose.yml
Create a directory for your Odoo project and add the following docker-compose.yml. This is a battle-tested configuration suitable for a single-instance production deployment.
version: "3.9"
services:
db:
image: postgres:15
restart: unless-stopped
environment:
POSTGRES_DB: odoo
POSTGRES_USER: odoo
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
networks:
- odoo_net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U odoo"]
interval: 10s
timeout: 5s
retries: 5
odoo:
image: odoo:17
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
HOST: db
PORT: 5432
USER: odoo
PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- odoo_filestore:/var/lib/odoo
- ./addons:/mnt/extra-addons
- ./config/odoo.conf:/etc/odoo/odoo.conf:ro
ports:
- "8069:8069"
networks:
- odoo_net
volumes:
db_data:
odoo_filestore:
networks:
odoo_net:
Create a .env file in the same directory to store secrets:
POSTGRES_PASSWORD=change_this_to_a_strong_password
Never commit .env to version control. Add it to .gitignore.
Odoo Configuration File
Create config/odoo.conf:
[options]
addons_path = /mnt/extra-addons,/usr/lib/python3/dist-packages/odoo/addons
data_dir = /var/lib/odoo
admin_passwd = change_this_master_password
db_host = db
db_port = 5432
db_user = odoo
db_password = change_this_to_a_strong_password
workers = 2
max_cron_threads = 1
limit_memory_hard = 2684354560
limit_memory_soft = 2147483648
limit_request = 8192
limit_time_cpu = 60
limit_time_real = 120
proxy_mode = True
Set workers = 0 during initial setup if you run into issues — it disables multi-process mode which is easier to debug.
Volume Mounts Explained
Understanding your volume mounts is critical for keeping data safe.
odoo_filestore:/var/lib/odoo— Named Docker volume. Stores Odoo's filestore (uploaded attachments, reports). This is separate from the database and must be backed up independently.db_data:/var/lib/postgresql/data— Named Docker volume for PostgreSQL data files../addons:/mnt/extra-addons— Bind mount from your host directory. Put custom or third-party modules here. Changes on the host are immediately visible to the container../config/odoo.conf:/etc/odoo/odoo.conf:ro— Bind mount for the config file, read-only inside the container.
Key rule: Named volumes (db_data, odoo_filestore) persist across container restarts and image updates. Bind mounts (./addons) live on the host filesystem and are not managed by Docker.
Starting the Stack
docker compose up -d
On first run, Odoo will wait for PostgreSQL to be healthy (the healthcheck ensures this), then start. Access Odoo at http://your-server-ip:8069 and create your first database through the database manager.
Follow logs in real time:
docker compose logs -f odoo
Custom Addons and Git Integration
The ./addons bind mount is where your custom modules go. If you use Git for your addons:
git clone https://github.com/your-org/your-odoo-addons.git addons
To pull updates:
git -C addons pull origin main
docker compose restart odoo
For module upgrades after code changes, use the Odoo CLI inside the container:
docker compose exec odoo odoo -d your_database -u your_module --stop-after-init
Common Issues and Fixes
Permission problems with the filestore
Odoo runs as UID 101 inside the official image. If you mount a host directory for the filestore instead of a named volume, you may hit permission errors. The fix:
sudo chown -R 101:101 ./filestore
Using named volumes avoids this entirely — Docker manages ownership automatically.
PostgreSQL version compatibility
Odoo 17 and 18 support PostgreSQL 12 through 16. Odoo 19 requires PostgreSQL 14+. Always match the PostgreSQL image tag to a supported version. Avoid postgres:latest — pin to a specific major version like postgres:15 to prevent surprise upgrades.
Container exits immediately
Check logs: docker compose logs odoo. Common causes are a wrong password in .env, a missing or malformed odoo.conf, or the database container not being healthy yet. The healthcheck in the example above prevents this last case.
Odoo is slow
In production, make sure workers is set (typically 2-4 depending on CPU cores) and proxy_mode = True if you're behind nginx. Without workers, Odoo runs in single-threaded mode and can only handle one request at a time.
Updating Odoo Without Data Loss
Updating Odoo in Docker is safe because your data lives in volumes, not the container image. The process:
- Back up first. Always dump your database before any update:
docker compose exec db pg_dump -U odoo odoo > backup_$(date +%Y%m%d).sql - Pull the new image. Edit
docker-compose.ymlto use the new tag (e.g.,odoo:17.0.20240301) and rundocker compose pull. - Stop and restart.
docker compose up -dwill recreate the odoo container with the new image while leaving volumes intact. - Run the upgrade. If the update includes database schema changes, run:
docker compose exec odoo odoo -d odoo -u all --stop-after-init
For major version upgrades (e.g., Odoo 16 to 17), the process is more involved and requires Odoo's official migration scripts. Stick to minor patch updates via this method.
Adding an Nginx Reverse Proxy
For production, never expose Odoo directly on port 8069. Add nginx as a reverse proxy with SSL termination. A minimal nginx config for Odoo:
server {
listen 80;
server_name odoo.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
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;
location / {
proxy_pass http://localhost: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 /longpolling {
proxy_pass http://localhost:8072;
}
}
With proxy_mode = True in your Odoo config, Odoo will trust the X-Forwarded-Proto header and generate correct HTTPS URLs.
The Easy Way: Use DeployMonkey
Setting up Docker, nginx, SSL, backups, and monitoring correctly takes hours of work — and that's before you've written a line of business logic. DeployMonkey automates the entire Docker stack for you. Connect your VPS via SSH, and DeployMonkey installs Docker, creates the Compose configuration, sets up nginx with automatic SSL renewal, and configures automated S3 backups — all in one click.
You keep full control of your server. DeployMonkey is a BYOS (Bring Your Own Server) platform, so you connect any VPS from Hetzner, DigitalOcean, Vultr, or AWS and DeployMonkey handles the Odoo infrastructure layer. There's no lock-in and you can SSH into your server anytime.
The free plan lets you manage one server and one Odoo instance at no cost. If you want to skip the Docker configuration entirely and get to running Odoo in minutes, create a free account and connect your server.
Summary
Docker is the best way to run Odoo on a self-managed server. The key points: use Docker Compose with named volumes for data, bind mounts for custom addons, pin your PostgreSQL version, set workers in production, and always back up before updates. With the configuration in this guide, you'll have a solid, reproducible Odoo stack you can maintain and upgrade confidently.
Ready to skip the setup work? Sign up for DeployMonkey free and deploy Odoo in minutes.