docker-odoo is a Docker Compose project for building and running Odoo from source with custom addons. It uses a two-stage image build, PostgreSQL, Redis-backed sessions, internal kwkhtmltopdf, and Caddy as the public entrypoint. Odoo core is installed in the base/ image, extra addon repositories are pulled during the addons/ image build, local modules are mounted from local-addons/, and runtime configuration is generated from .env into /etc/odoo.conf.
- Odoo
- PostgreSQL
- Docker / Docker Compose
- Caddy reverse proxy
- Redis for Odoo sessions
kwkhtmltopdffor report rendering- Remote addons from
addons/addons.yml - Local custom addons from
local-addons/
- Docker Engine with Compose v2
- free host ports
80and443 - a hostname for
CADDY_DOMAINthat resolves to the current machine or server - outbound network access during image build to GitHub, GitLab, apt, npm, and image registries
- write access to
backups/ - read access to
local-addons/
Required env values before first real use:
ADMIN_PASSWORDDB_PASSWORDREDIS_PASSWORDCADDY_DOMAINCADDY_EMAILODOO_BASE_URL
Optional but often needed:
GITHUB_TOKEN,GITLAB_TOKEN,GIT_TOKENfor private addon repositoriesODOO_EE_GIT_TOKENfor Odoo Enterprise builds
Copy the example env file:
cp .env.example .envFor a local run, the minimum useful values are:
ODOO_VERSION=19.0
ODOO_EDITION=ce
DATABASE_NAME=odoo
DB_NAME=${DATABASE_NAME}
DB_USER=odoo
DB_PASSWORD=CHANGE_ME_STRONG_RANDOM_DB_PASSWORD
ADMIN_PASSWORD=CHANGE_ME_STRONG_RANDOM_ADMIN_PASSWORD
REDIS_PASSWORD=CHANGE_ME_STRONG_RANDOM_REDIS_PASSWORD
WORKERS=2
LIST_DB=False
DBFILTER=^odoo$
CADDY_DOMAIN=localhost
CADDY_EMAIL=admin@example.com
ODOO_BASE_URL=https://localhost
ODOO_REPORT_URL=http://odoo:8069
LOAD=web,session_redisNotes:
CADDY_DOMAIN=localhostis fine for local usage.ODOO_BASE_URLshould match the public URL you will actually open in the browser.ODOO_REPORT_URLshould stay internal ashttp://odoo:8069in this stack.LOAD=web,session_redisis the current default because Redis session storage is part of this project.
If you need raw Odoo config entries that are not explicitly exposed as separate env vars, use ODOO_EXTRA_OPTS. Its content is appended verbatim to the generated odoo.conf. This is useful for addons that require extra config blocks in odoo.conf, for example OCA queue_job.
Build images and start the stack:
make init
make up
make psOpen Odoo in the browser:
- local:
https://localhost - server:
https://<CADDY_DOMAIN>
If the database does not exist yet:
- open
https://<CADDY_DOMAIN>/web/database/manager - create a database with the same name as
DB_NAME - use
ADMIN_PASSWORDas the master password
If you want raw Compose commands instead of make:
docker compose --env-file .env --profile build build odoo-core
docker compose --env-file .env build odoo
docker compose --env-file .env up -d
docker compose --env-file .env psFor the normal workflow:
make initbuilds the reusable base image and then the project Odoo image with remote addons.make upstarts PostgreSQL, Redis,kwkhtmltopdf, Odoo, and Caddy in the background.make psshows whether the containers are up and healthy.make logstails logs from the full stack when you want to see startup or runtime errors.
For day-to-day work:
make restartrestarts services after changing.envor after updating local modules.make stopstops containers but keeps them available for a latermake up.make downremoves containers and networks but keeps named volumes and data.make shopens a shell inside the Odoo container.make odoo-shellopensodoo shell --no-httpinside the running Odoo container.make psqlopens a PostgreSQL shell against the configured database.
For diagnostics:
make log-odoofollows only Odoo logs.make log-dbfollows only PostgreSQL logs.make configprints the final Compose configuration after env interpolation.
For maintenance:
make backupcreates a database and filestore backup underbackups/.make restore BACKUP=backups/<name>restores a previously created backup.
Use a different env file or project name when needed:
make up ENV_FILE=.env.staging COMPOSE_PROJECT_NAME=docker-odoo-stagingThis project supports both Odoo Community Edition and Odoo Enterprise Edition.
For Community Edition:
ODOO_EDITION=ce
ODOO_VERSION=19.0For Enterprise Edition:
ODOO_EDITION=ee
ODOO_VERSION=19.0
ODOO_ENTERPRISE_REPO=https://github.com/odoo/enterprise.git
ODOO_EE_GIT_TOKEN=your_token
ODOO_EE_GIT_USER=x-access-token
ODOO_EE_GIT_HOST=github.comThen rebuild the images:
make init
make upNotes:
ODOO_EDITION=eeenables the Enterprise build path inbase/Dockerfile.- Enterprise addons are installed into
${ODOO_EE_ADDONS_DIR}. - The runtime automatically appends
${ODOO_EE_ADDONS_DIR}toaddons_path. - If you want reproducible Enterprise builds, also set
ODOO_ENTERPRISE_REF. - If your Enterprise repository is hosted outside GitHub, set matching
ODOO_EE_GIT_HOST,ODOO_EE_GIT_USER, andODOO_EE_GIT_TOKEN.
- core Odoo addons:
/opt/odoo/addons - remote addons baked into the image:
/opt/extra-addons - local project addons:
/opt/local-addons
Default runtime addons_path:
/opt/odoo/addons,/opt/extra-addons,/opt/local-addons
If ODOO_EDITION=ee, the runtime also appends ${ODOO_EE_ADDONS_DIR} automatically.
Put the module in local-addons/:
local-addons/
my_module/
__init__.py
__manifest__.py
...
Then restart Odoo:
make restartIf the module has Python dependencies, add them to addons/requirements.txt and rebuild the odoo image:
make build-addons
make upEdit addons/addons.yml and add another repo entry in the same format already used there. This file is processed by git-aggregator, so if you need the full manifest format, merge syntax, or advanced options, use the upstream documentation: git-aggregator.
Then rebuild the addons image:
make build-addons
make upUpdate one module from CLI:
docker compose exec odoo bash -lc 'odoo -c /etc/odoo.conf -d "$DB_NAME" -u module_name --stop-after-init'After that restart Odoo:
make restartThis section contains the project internals and lower-level runtime details.
.envis mounted into the container as/run/odoo/.env/etc/odoo.confis rendered frombase/config/odoo.conf.tpl- unresolved or empty config lines are dropped during rendering
ODOO_EXTRA_OPTSis appended at the end of the generated config
That means you can inject extra Odoo config without modifying the template.
- host ports:
80:80443:443443:443/udp
- internal-only services:
- Odoo HTTP:
8069 - Odoo XML-RPCS:
8071 - Odoo gevent/websocket:
8072 - PostgreSQL:
5432 - Redis:
6379 kwkhtmltopdf:8080
- Odoo HTTP:
db-data->/var/lib/postgresql/dataodoo-data->/var/lib/odooredis-data->/datacaddy-data->/datacaddy-config->/config${LOCAL_ADDONS_DIR}->/opt/local-addons
- PostgreSQL data:
db-data - Odoo filestore:
/var/lib/odoo/filestore/${DB_NAME} - Redis persistence:
redis-data - Caddy certificates and state:
caddy-data,caddy-config
addons/addons.ymlis processed bygit-aggregator- module directories are copied into
/opt/extra-addons - Python and Debian dependencies are resolved from addon metadata during build
addons/requirements.txtis installed into the image during the addons build stage
- default
LOADincludessession_redis - Redis is private to Docker Compose and not published to the host
- Odoo uses
REDIS_PASSWORD - old filesystem sessions are cleaned up by the entrypoint when Redis session storage is enabled
- public traffic goes to Caddy, not directly to Odoo
caddy/Caddyfileproxies:- normal HTTP traffic to
odoo:8069 /longpolling/*and/websockettoodoo:8072
- normal HTTP traffic to
PROXY_MODE=Trueis expected for this topology
This repository does not have a separate dev compose file. Local, staging, and production separation is done through:
- different
.envfiles - different
COMPOSE_PROJECT_NAME
Examples:
make up ENV_FILE=.env.local COMPOSE_PROJECT_NAME=docker-odoo-local
make up ENV_FILE=.env.staging COMPOSE_PROJECT_NAME=docker-odoo-staging
make up ENV_FILE=.env.prod COMPOSE_PROJECT_NAME=docker-odoo-prodThere is no docker-compose.prod.yml in this repository.
What is already production-friendly in the current project:
- PostgreSQL is not published to the host
- Redis is not published to the host
- Odoo is not published directly to the host
- named volumes persist database, filestore, Redis, and Caddy state
- reverse proxy handling is already configured
What you still need to set correctly per environment:
CADDY_DOMAINCADDY_EMAILODOO_BASE_URLADMIN_PASSWORDDB_PASSWORDREDIS_PASSWORDWORKERSLIST_DBDBFILTER
Changing DB_PASSWORD for an already initialized PostgreSQL volume is not just an env change. The existing db-data volume keeps the original PostgreSQL credentials, so password rotation requires an intentional PostgreSQL-side change or volume re-creation.
Use the built-in scripts:
make backup
make backup NAME=before-upgradeDirect usage:
./scripts/backup.sh
./scripts/backup.sh before-upgradeThe backup creates backups/<timestamp-or-name>/ with:
db.dumpfilestore.tar.gzmanifest.txt
What is backed up:
- PostgreSQL database
${DB_NAME} - Odoo filestore from
${DATA_DIR}/filestore/${DB_NAME}
Restore overwrites the current database and filestore for ${DB_NAME}.
Restore a backup:
make restore BACKUP=backups/2026-05-05_12-00-00Direct usage:
./scripts/restore.sh backups/2026-05-05_12-00-00Restore flow:
- stop
odoo - restore
db.dumpinto PostgreSQL - delete
${DATA_DIR}/filestore/${DB_NAME} - extract
filestore.tar.gz - start
odoo
- do not commit
.env - replace all
CHANGE_ME_*values before real use - keep PostgreSQL private
- keep Redis private
- run public deployments behind Caddy or another reverse proxy
- do not store backups in Git
- keep
ADMIN_PASSWORDstrong