How to install Odoo 20 Community with Docker Compose and HTTPS
Install Odoo 20 Community from the official Docker image with PostgreSQL on Ubuntu or Debian, then add Caddy HTTPS, websocket routing, workers and backups.
- Intermediate
- 45 min read
- Updated
Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12, Debian 13
This guide is not available in your language yet, so it is shown in English.
On this page
- Prerequisites
- Step 1 — Create the project folder and secrets
- Step 2 — Write odoo.conf
- Step 3 — Write the Compose file
- Step 4 — Create the database role and start Odoo
- Step 5 — Create the database, then close the database manager
- Step 6 — Publish Odoo over HTTPS with websocket routing
- Step 7 — Install apps and custom modules
- Back up and restore
- Update Odoo
- Troubleshooting
- Odoo exits with a message that the postgres user is a security risk
- Discuss, notifications or live chat do not update in real time
- The login page shows no database or a database not found error
- Requests time out or workers are restarted with memory limit messages
- Links in emails use http:// or an internal address
- Next steps
Odoo is a suite of open-source business applications: CRM, sales, invoicing, inventory, projects, website, e-commerce and many more, all sharing one PostgreSQL database. This guide installs Odoo 20.0 Community Edition, the current stable release (Odoo's nightly server lists it as released in September 2026), with the official odoo Docker image and a PostgreSQL 17 container. You configure odoo.conf for production with workers, proxy mode, a master password and a database filter, publish Odoo through Caddy with HTTPS and correct websocket routing, close the database manager, and set up backups and updates.
Prerequisites
- A server running Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12 or Debian 13 with Docker Engine and the Compose plugin: Docker on Ubuntu or Docker on Debian. The commands assume your user is in the
dockergroup; otherwise prefix them withsudo. - A non-root user with
sudorights and SSH key login: Secure a new Linux server and Set up SSH keys. - A domain name such as
odoo.example.comwith an A record (and optionally an AAAA record) pointing at the server, and Caddy from Caddy reverse proxy.
Odoo does not publish minimum hardware requirements. Its deployment guide offers a sizing method instead: at most (number of CPUs × 2) + 1 workers, about six concurrent users per worker, and RAM of roughly workers × (0.8 × 150 MB + 0.2 × 1 GB), assuming 80% light and 20% heavy requests. The figures below are a conservative starting point for a small team, not official numbers:
| Resource | Minimum (official) | Suggested starting point |
|---|---|---|
| CPU | Not published | 2 vCPU |
| RAM | Not published | 4 GB |
| Disk | Not published | 40 GB plus your attachments |
On the software side, Odoo 20 requires PostgreSQL 16 or newer. The examples on the image's Docker Hub page still use postgres:15, so this guide pins postgres:17 instead.
Step 1 — Create the project folder and secrets
Keep the stack in /opt/odoo, with a config folder for odoo.conf and an addons folder for custom modules. Because odoo.conf will hold the master password, close the project folder to other users:
sudo mkdir -p /opt/odoo/config /opt/odoo/addons
sudo chown -R $USER:$USER /opt/odoo
chmod 700 /opt/odoo
cd /opt/odooGenerate three different secrets: one for the PostgreSQL superuser, one for the odoo database role, and one for Odoo's master password:
openssl rand -hex 24
openssl rand -hex 24
openssl rand -hex 24Create /opt/odoo/.env with nano .env and paste the first two values:
POSTGRES_PASSWORD=paste-the-first-value
ODOO_DB_PASSWORD=paste-the-second-valuechmod 600 .envStep 2 — Write odoo.conf
Create /opt/odoo/config/odoo.conf with nano config/odoo.conf. Paste the third secret as admin_passwd:
[options]
addons_path = /mnt/extra-addons
data_dir = /var/lib/odoo
admin_passwd = paste-the-third-value
proxy_mode = True
dbfilter = ^odoo$
list_db = True
workers = 4
max_cron_threads = 1
limit_memory_soft = 629145600
limit_memory_hard = 1677721600
limit_time_cpu = 600
limit_time_real = 1200
limit_request = 8192What these settings do:
admin_passwdis the master password that protects the database management screens. Odoo's documentation says to replace the default with a randomly generated value.proxy_mode = Truemakes Odoo read theX-Forwarded-*headers from Caddy, so it knows requests arrive over HTTPS. Odoo warns never to enable it without a reverse proxy in front.dbfilter = ^odoo$serves only a database namedodoo, whatever hostname is used.list_db = Trueis temporary. You need the database manager once in Step 5 and then turn it off.workers = 4switches Odoo from the threaded development server to the multi-processing server meant for production. With workers enabled, Odoo also starts a separate websocket process on port 8072 (the--gevent-portdefault) for live chat and real-time notifications.- The
limit_values come from the sample configuration in Odoo's deployment guide. Workers abovelimit_memory_softare recycled after the current request; workers abovelimit_memory_hardare killed at once.
Adjust workers to your server with the formula from the Prerequisites, and leave room for PostgreSQL.
Step 3 — Write the Compose file
Create /opt/odoo/compose.yaml:
services:
db:
image: postgres:17
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- odoo-db-data:/var/lib/postgresql/data
restart: unless-stopped
odoo:
image: odoo:20.0
depends_on:
- db
environment:
HOST: db
USER: odoo
PASSWORD: ${ODOO_DB_PASSWORD}
ports:
- "127.0.0.1:8069:8069"
- "127.0.0.1:8072:8072"
volumes:
- odoo-web-data:/var/lib/odoo
- ./config:/etc/odoo
- ./addons:/mnt/extra-addons
restart: unless-stopped
volumes:
odoo-db-data:
odoo-web-data:The image reads /etc/odoo/odoo.conf, stores the filestore and sessions in /var/lib/odoo, and loads custom modules from /mnt/extra-addons. Its entrypoint passes HOST, USER and PASSWORD to Odoo as the database connection. PostgreSQL runs as its own superuser postgres; Odoo connects as a separate odoo role, because Odoo refuses to connect as postgres and its deployment guide says the database user should not be a superuser. Both ports are bound to 127.0.0.1, since Docker-published ports bypass ufw.
The 20.0 tag follows rebuilds of the 20.0 series. Docker Hub also lists dated tags such as 20.0-20260926 if you want to pin one exact build.
Step 4 — Create the database role and start Odoo
Start PostgreSQL alone, then create the odoo role with the flags Odoo's documentation uses: allowed to create databases, but not a superuser and not allowed to create roles. Paste ODOO_DB_PASSWORD from .env twice when prompted:
docker compose up -d db
docker compose exec db createuser -U postgres --createdb --no-createrole --no-superuser --pwprompt odooIf createuser reports that it cannot connect, PostgreSQL is still initialising; wait a few seconds and run it again. Then start Odoo and follow its log:
docker compose up -d
docker compose ps
docker compose logs -f odooStop following with Ctrl+C once the log shows the HTTP service running on port 8069. Check that the database manager answers locally:
curl -I http://127.0.0.1:8069/web/database/managerYou should get HTTP/1.1 200 OK.
Step 5 — Create the database, then close the database manager
Create the database through an SSH tunnel, so the database manager is never exposed to the internet. On your own computer, open a tunnel to the server:
ssh -L 8069:127.0.0.1:8069 admin@203.0.113.10Keep that session open and browse to http://localhost:8069/web/database/manager. Choose Create Database and enter:
- Master Password: the
admin_passwdvalue fromodoo.conf. - Database Name:
odoo, so it matchesdbfilter. - Email and Password: the login of your first administrator. Use a long, unique password.
- Your language and country. Leave Demo data unticked on a production database.
When Odoo logs you in, the database exists. Back on the server, switch the database manager off and restart Odoo:
cd /opt/odoo
sed -i 's/^list_db = True/list_db = False/' config/odoo.conf
grep list_db config/odoo.conf
docker compose restart odoogrep should print list_db = False. The database selection and management screens are now blocked, which is what Odoo's documentation recommends for internet-facing systems. Close the SSH tunnel.
Step 6 — Publish Odoo over HTTPS with websocket routing
With workers enabled, normal HTTP traffic goes to port 8069 and websocket connections on /websocket go to port 8072. Odoo's deployment guide shows this split for Nginx; the Caddy equivalent uses two handle blocks. Add this site to /etc/caddy/Caddyfile:
odoo.example.com {
encode zstd gzip
handle /websocket* {
reverse_proxy 127.0.0.1:8072
}
handle {
reverse_proxy 127.0.0.1:8069
}
}Caddy upgrades websocket connections and sets X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host on its own, which is what proxy_mode expects. Reload Caddy and allow only SSH and web traffic:
sudo systemctl reload caddy
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enableOpen https://odoo.example.com and log in with the administrator account from Step 5. To check the websocket route, open your browser's developer tools on the Network tab: a request to /websocket should show status 101. If you prefer Nginx, follow the sample configuration in Odoo's deployment guide together with Nginx with Certbot.
Odoo emails user invitations, password resets, notifications and documents such as quotations and invoices through the outgoing mail server you add, with developer mode active, under Settings → Technical → Email: Outgoing Mail Servers (host smtp.example.com, port 587, TLS (STARTTLS) encryption, your SMTP user and password; use Test Connection before saving).
Step 7 — Install apps and custom modules
Install Odoo's own apps from the Apps menu. Third-party or in-house modules go into /opt/odoo/addons, one folder per module, readable by the container. After copying a module, restart Odoo:
docker compose restart odooThen activate developer mode in Settings, choose Apps → Update Apps List, and install the module. Only install modules from sources you trust; a module runs with full access to your data.
Back up and restore
Odoo's state is split in two: the PostgreSQL database and the filestore (attachments and other files) under /var/lib/odoo/filestore/odoo. A backup needs both, taken at the same time, plus your configuration. The database manager's backup button produces such a bundle, but you switched it off in Step 5; the commands below give you the same content from the shell:
sudo mkdir -p /opt/backups
sudo chown $USER:$USER /opt/backups
chmod 700 /opt/backups
cd /opt/odoo
docker compose exec -T db pg_dump -U postgres -Fc odoo > /opt/backups/odoo-db-$(date +%F).dump
docker compose exec -T odoo tar czf - -C /var/lib/odoo filestore/odoo > /opt/backups/odoo-filestore-$(date +%F).tar.gz
tar czf /opt/backups/odoo-config-$(date +%F).tar.gz -C /opt/odoo compose.yaml .env config addonspg_dump takes a consistent snapshot while Odoo keeps running. To restore, stop Odoo, recreate the database, load the dump and unpack the filestore into the volume. Replace the dates with those of your backup:
cd /opt/odoo
docker compose stop odoo
docker compose exec -T db dropdb -U postgres --if-exists --force odoo
docker compose exec -T db createdb -U postgres --owner=odoo odoo
docker compose exec -T db pg_restore -U postgres -d odoo < /opt/backups/odoo-db-2026-10-09.dump
docker compose run --rm --no-deps -T odoo tar xzf - -C /var/lib/odoo < /opt/backups/odoo-filestore-2026-10-09.tar.gz
docker compose up -dOn a new server, unpack the configuration archive into /opt/odoo first, then run Step 4 to start PostgreSQL and create the odoo role, and only then run the restore commands. Copy every backup off the server and test a restore regularly.
Update Odoo
Updates within 20.0 bring bug and security fixes. Back up first, then pull the rebuilt image and recreate the container:
cd /opt/odoo
docker compose pull
docker compose up -d
docker compose logs -f odooIf a fix changes module data and you see errors about missing fields or views afterwards, update all modules of the database once. Odoo is stopped while this runs:
docker compose stop odoo
docker compose run --rm odoo odoo -d odoo -u all --stop-after-init
docker compose up -dMajor upgrades, such as 19.0 to 20.0 or 20.0 to a later release, are a different job. Odoo's Docker Hub page warns that moving between major versions needs elaborate migration scripts. Plan the database migration with Odoo's upgrade documentation, port your custom modules, and test the whole process on a copy of the database before you change the image tag. Never change odoo:20.0 to a new major and simply restart. Keep the PostgreSQL major version in postgres:17 unchanged as well; a new PostgreSQL major needs a dump and restore.
Troubleshooting
Odoo exits with a message that the postgres user is a security risk
Odoo is configured to connect as postgres, which it refuses. Check that USER: odoo is set in compose.yaml, that the role exists (docker compose exec db psql -U postgres -c "\du"), and that odoo.conf has no db_user = postgres line.
Discuss, notifications or live chat do not update in real time
Websocket requests are not reaching the websocket process. Check that Caddy sends /websocket* to 127.0.0.1:8072, that port 8072 is published in compose.yaml, that workers is above 0, and that proxy_mode = True is set. Reload Caddy and restart Odoo after each change.
The login page shows no database or a database not found error
dbfilter does not match the database name. With dbfilter = ^odoo$ the database must be called exactly odoo. Fix the filter in odoo.conf or recreate the database with the matching name, then restart Odoo.
Requests time out or workers are restarted with memory limit messages
A worker exceeded limit_time_cpu, limit_time_real or the memory limits. Read the details with docker compose logs odoo, raise the specific limit if long reports or imports legitimately need it, and reduce workers or add RAM if the server itself runs out of memory.
Links in emails use http:// or an internal address
Odoo builds links from the web.base.url system parameter. Make sure proxy_mode = True is set, then log in as an administrator through https://odoo.example.com and check the parameter under Settings → Technical → System Parameters in developer mode.
Next steps
- Add marketing automation alongside your CRM: How to install Mautic.
- Brush up on the Compose file format in Docker Compose basics.
- See server options for Odoo on the Odoo hosting page, or for other CRM tools on CRM hosting.
- Read Odoo's system configuration guide for fail2ban rules, static file serving and advanced worker tuning.
Frequently asked questions
Where does Odoo keep attachments and uploaded files?
In the filestore, /var/lib/odoo/filestore/odoo inside the odoo-web-data volume, not in PostgreSQL. A backup or a move to another server needs the database dump and the filestore archive taken at the same time, as shown in the backup section.
Why does this guide use Docker instead of the deb packages?
Odoo's packaging page says the Odoo 20 deb package currently supports Ubuntu 24.04 only, and wkhtmltopdf 0.12.6 must then be installed by hand for PDF headers and footers. The official image, maintained by Odoo, runs on every Docker-supported release and already contains wkhtmltopdf 0.12.6.1.
How many workers should I configure?
Odoo's deployment guide gives (number of CPUs x 2) + 1 as a rule of thumb for the maximum, estimates about six concurrent users per worker, and suggests budgeting roughly 150 MB of RAM for light requests and 1 GB for heavy ones. Start lower on small servers and leave memory for PostgreSQL.
Can I upgrade from Odoo 19 to 20 by changing the image tag?
No. A major version change needs a database migration, and custom modules must be ported to the new version as well. Plan it with Odoo's upgrade documentation and always test the upgrade on a copy of the database first.
Should I keep the database manager enabled?
No. Odoo's documentation strongly advises disabling it on internet-facing systems. Set list_db = False once your database exists and use pg_dump plus a filestore archive for backups instead of the web backup button.
Which HyperDC servers can run Odoo?
Any HyperDC Linux VPS, VDS or dedicated server with root access where Docker Engine is supported. Odoo does not publish minimum hardware, so size the server with the worker formula in this guide and grow it as your users and modules grow.
Sources
- nightly.odoo.com
- odoo.com/documentation/20.0/administration/on_premise/packages.html
- odoo.com/documentation/20.0/administration/on_premise/deploy.html
- odoo.com/documentation/20.0/administration/on_premise/source.html
- odoo.com/documentation/20.0/developer/reference/cli.html
- odoo.com/documentation/20.0/administration/upgrade.html
- hub.docker.com/_/odoo
- github.com/odoo/docker/blob/master/20.0/Dockerfile
- raw.githubusercontent.com/odoo/docker/master/20.0/entrypoint.sh
- raw.githubusercontent.com/odoo/docker/master/20.0/odoo.conf
- caddyserver.com/docs/caddyfile/directives/reverse_proxy
- postgresql.org/docs/current/app-pgdump.html