This Odoo Docker Compose guide gives you a complete, working setup for Odoo 19 and PostgreSQL 16: one docker-compose.yml, an odoo.conf, a .env file for the password, and a folder for your own addons. It also covers the commands you will use every day, how to back up and restore the database and filestore, how to update the image, and the mistakes that cost people data. The same files install Odoo 18 with Docker if you change one line.
What you need
- Docker Engine with the Compose plugin. Commands in this guide use
docker compose(two words). The olddocker-composev1 binary is end of life. - 2 GB of RAM or more for a test install. Odoo plus PostgreSQL runs on less, but slowly.
- The official
odooandpostgresimages from Docker Hub. You do not need to build anything.
Project layout
Create a folder with this structure:
odoo-docker/
├── docker-compose.yml
├── .env
├── config/
│ └── odoo.conf
└── addons/ # your custom modules go here
Docker Compose reads docker-compose.yml and starts every service in it with one command. It also creates a private network where the containers reach each other by service name, which is why Odoo can connect to the host db.
The Odoo docker-compose.yml
services:
db:
image: postgres:16
environment:
POSTGRES_DB: postgres
POSTGRES_USER: odoo
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
volumes:
- odoo-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U odoo -d postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
web:
image: odoo:${ODOO_VERSION:-19.0}
depends_on:
db:
condition: service_healthy
restart: true
ports:
- "127.0.0.1:8069:8069"
- "127.0.0.1:8072:8072"
environment:
HOST: db
USER: odoo
PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
volumes:
- odoo-web-data:/var/lib/odoo
- ./config:/etc/odoo
- ./addons:/mnt/extra-addons
restart: unless-stopped
volumes:
odoo-db-data:
odoo-web-data:What each part does:
- No
version:line. The top-levelversionkey is obsolete. Current Compose ignores it and prints a warning. dbruns PostgreSQL 16.POSTGRES_DB: postgresmatches the official Odoo image example. Odoo creates its own databases later from the web interface.healthcheckrunspg_isreadyuntil PostgreSQL accepts connections.depends_onwithcondition: service_healthyholds Odoo back until that healthcheck passes. A plaindepends_on: [db]only waits for the container to start, not for the database to be ready.restart: truerestarts Odoo when Compose restarts the database.HOST,USER,PASSWORDare the variables the Odoo image's entrypoint uses to connect to PostgreSQL. They default todb,odooandodoo, so always set the password.- Ports are bound to
127.0.0.1, so only the machine itself can reach Odoo. Port 8069 is the web interface. Port 8072 is the websocket port that live chat and notifications use once you turn on workers. PostgreSQL publishes no port at all. - Named volumes:
odoo-db-dataholds the database andodoo-web-dataholds the Odoo filestore (attachments, images, sessions). They survivedocker compose downand image updates. Volumes are covered in more depth in Odoo Docker volumes. ./configreplaces the image's/etc/odoo, so yourodoo.confis the one Odoo reads../addonsis mounted at/mnt/extra-addons, the image's custom addons path.
odoo.conf and .env
[options]
addons_path = /mnt/extra-addons
data_dir = /var/lib/odoo
admin_passwd = replace-with-a-long-random-string
list_db = True
proxy_mode = False
workers = 0addons_path and data_dir must stay as shown. They match the paths the image is built around. admin_passwd is the master password that protects the database manager. Leave the database connection settings out of this file: if db_host or db_password appear in odoo.conf, the entrypoint uses them instead of the environment variables.
ODOO_VERSION=19.0
POSTGRES_PASSWORD=replace-with-a-strong-passwordCompose reads .env automatically and substitutes the values into docker-compose.yml. Keep it out of Git. The :? syntax stops Compose with an error if the password is missing, instead of silently starting with an empty one.
Install Odoo 18 with Docker instead
Set ODOO_VERSION=18.0 in .env before the first start. Everything else stays the same. Pick the version before you create a database: switching the tag on an existing database is a migration, not an update (see the pitfalls below).
Start Odoo
docker compose up -d --wait
docker compose ps
--wait returns once the containers are running and the database is healthy. Open http://localhost:8069, fill in the database creation form with your master password, and Odoo installs the base modules. The first start takes a minute or two.
Daily commands
| Task | Command |
|---|---|
| Follow Odoo's log | docker compose logs -f web |
| Last 100 lines, with timestamps | docker compose logs --tail 100 -t web |
Restart Odoo after changing Python code or odoo.conf | docker compose restart web |
| Stop without removing anything | docker compose stop |
| Start again | docker compose start |
| Remove containers and network, keep data | docker compose down |
| Open a shell in the Odoo container | docker compose exec web bash |
| Open a psql prompt | docker compose exec db psql -U odoo -d postgres |
To install or update a module from the command line, stop the web service and run a one-off container. Replace mydb and my_module with your names:
docker compose stop web
docker compose run --rm web odoo -d mydb -u my_module --stop-after-init
docker compose start web
Use -i instead of -u to install a module for the first time. The entrypoint adds the database connection arguments for you.
Backup and restore
An Odoo backup has two parts: the PostgreSQL database and the filestore folder of the same name. A database dump without the filestore loses every attachment. Run these from the project folder; the -T flag turns off the TTY so the output is not corrupted.
Back up
docker compose exec -T db pg_dump -U odoo -Fc mydb > mydb.dump
docker compose exec -T web tar -C /var/lib/odoo -czf - filestore/mydb > mydb-filestore.tar.gz
Restore
docker compose stop web
docker compose exec -T db createdb -U odoo mydb
docker compose exec -T db pg_restore -U odoo -d mydb --no-owner < mydb.dump
docker compose run --rm --no-deps -T web tar -C /var/lib/odoo -xzf - < mydb-filestore.tar.gz
docker compose start web
The database must not exist when you restore. Drop it first with dropdb if you are overwriting. The Odoo database manager at /web/database/manager can also download and restore a zip with both parts, which is fine for small databases. Copy backups off the server: a backup on the same disk does not survive a disk failure.
Update the Odoo image
The official image is republished from time to time with a newer Odoo build of the same version. To pick up those fixes within the same major version:
docker compose pull web
docker compose up -d web
Compose recreates the container from the new image and keeps both volumes. If an update changes module data, run the module update shown above with -u all. Take a backup first.
When you need a Dockerfile
The compose file decides how containers run. A Dockerfile decides what is inside an image. Mounting ./addons is enough while you develop, but once your modules need extra Python packages or you want a single versioned image to deploy, build your own image on top of odoo:19.0. See building a custom Odoo Docker image.
Pitfalls
- Do not scale Odoo with
--scale.docker compose up --scale web=3fails on the fixed host ports, and fails outright if you setcontainer_name. Even with those removed, you would need a load balancer and a shared filestore. On one server, raiseworkersinodoo.confinstead. A common starting point is (CPU cores × 2) + 1. With workers above 0, put a reverse proxy in front that sends/websocket/to port 8072. Odoo server configuration covers workers, memory limits and the proxy. docker compose down -vdeletes your database and filestore. The-vflag removes named volumes. Use plaindown.- Changing
ODOO_VERSIONfrom 18.0 to 19.0 on an existing database will not work. A major version needs a database migration (Odoo's upgrade service for Enterprise, OpenUpgrade for Community). Test it on a copy. - Do not use the
postgressuperuser for Odoo. Odoo refuses to start with that database user. Keep a dedicatedodoorole as shown. - Do not publish port 5432 unless you need outside access to the database, and never to the public internet.
- Addon permissions. Files in
./addonsmust be readable by the container'sodoouser, or the modules will not appear in the Apps list. - Going to production? Add a reverse proxy with TLS (Nginx or Traefik v3), set
proxy_mode = Trueandlist_db = False, and schedule off-server backups.
Skip the server maintenance
Docker Compose is a good way to run Odoo yourself. You also take on the proxy, TLS, backups, updates and monitoring. If you would rather not, compare the hosting options, or try Easy Instance: managed Odoo 17 to 20, Community or Enterprise, with daily backups and GitHub deploys included and staging copies on demand, at $30 per worker a month. The 10-day free trial needs no credit card.