Odoo Docker Compose: Install Odoo 19 with PostgreSQL

A working Odoo docker compose setup for Odoo 19 and PostgreSQL 16: compose file, odoo.conf, .env, daily commands, backup, restore and upgrades.

Updated . 7 min read

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 old docker-compose v1 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 odoo and postgres images 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

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-level version key is obsolete. Current Compose ignores it and prints a warning.
  • db runs PostgreSQL 16. POSTGRES_DB: postgres matches the official Odoo image example. Odoo creates its own databases later from the web interface.
  • healthcheck runs pg_isready until PostgreSQL accepts connections.
  • depends_on with condition: service_healthy holds Odoo back until that healthcheck passes. A plain depends_on: [db] only waits for the container to start, not for the database to be ready. restart: true restarts Odoo when Compose restarts the database.
  • HOST, USER, PASSWORD are the variables the Odoo image's entrypoint uses to connect to PostgreSQL. They default to db, odoo and odoo, 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-data holds the database and odoo-web-data holds the Odoo filestore (attachments, images, sessions). They survive docker compose down and image updates. Volumes are covered in more depth in Odoo Docker volumes.
  • ./config replaces the image's /etc/odoo, so your odoo.conf is the one Odoo reads.
  • ./addons is mounted at /mnt/extra-addons, the image's custom addons path.

odoo.conf and .env

config/odoo.conf
[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 = 0

addons_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.

.env
ODOO_VERSION=19.0
POSTGRES_PASSWORD=replace-with-a-strong-password

Compose 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

TaskCommand
Follow Odoo's logdocker compose logs -f web
Last 100 lines, with timestampsdocker compose logs --tail 100 -t web
Restart Odoo after changing Python code or odoo.confdocker compose restart web
Stop without removing anythingdocker compose stop
Start againdocker compose start
Remove containers and network, keep datadocker compose down
Open a shell in the Odoo containerdocker compose exec web bash
Open a psql promptdocker 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=3 fails on the fixed host ports, and fails outright if you set container_name. Even with those removed, you would need a load balancer and a shared filestore. On one server, raise workers in odoo.conf instead. 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 -v deletes your database and filestore. The -v flag removes named volumes. Use plain down.
  • Changing ODOO_VERSION from 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 postgres superuser for Odoo. Odoo refuses to start with that database user. Keep a dedicated odoo role 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 ./addons must be readable by the container's odoo user, 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 = True and list_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.

Rather not run the server yourself?

Get managed Odoo hosting with backups, SSL, monitoring and updates handled for you. Free for 10 days.