Production-ready Docker Compose stack for Odoo 18. Built as part of the much. Consulting SRE challenge.
Includes Nginx reverse proxy, PostgreSQL 18, automated backups, and metrics exporters for centralised observability.
- Odoo 18 + PostgreSQL 18 (Alpine)
- Nginx 1.30 — TLS termination, security headers, rate limiting
- Backup container — daily pg_dump + filestore archive, 30-day retention
- node-exporter + postgres-exporter — metrics for central Prometheus scraping
# 1. Clone the repo
git clone <repo-url> && cd odoo-infra
# 2. Generate secrets
mkdir -p secrets
openssl rand -base64 32 > secrets/pg_password.txt
openssl rand -base64 32 > secrets/odoo_admin_passwd.txt
chmod 600 secrets/*.txt
# ⚠️ After generating secrets, update the password values manually in:
# - docker-compose.yml (POSTGRES_PASSWORD, PASSWORD, DATA_SOURCE_NAME)
# - config/odoo.conf (db_password)
# See secrets/README.md for details
mkdir -p config/nginx/ssl
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout config/nginx/ssl/server.key \
-out config/nginx/ssl/server.crt \
-subj "/CN=localhost"
# 4. Bring up the stack
docker compose up -dOdoo requires a one-time manual database initialisation on first run. The official image does not auto-initialise when using a custom odoo.conf.
# Shell into a temporary Odoo container
docker compose run --rm odoo bash
# Run initialisation (takes 2-3 minutes)
/usr/bin/odoo -i base \
--db_host=db \
--db_user=odoo \
--db_password=odoo \
--database=odoo \
--without-demo=all \
--stop-after-init
# Exit and bring stack up normally
exit
docker compose up -dThis is only needed once. Data persists in the pg_data volume across
all restarts. Only docker compose down -v would require re-initialisation.
For this deployment credentials are set as plain environment variables in docker-compose.yml. This is intentional for local/demo use.
Production path: inject via CI/CD from HashiCorp Vault or AWS Secrets Manager at deploy time — never stored in the image or repository.
Note: Docker native secrets (
POSTGRES_PASSWORD_FILE) work for PostgreSQL but Odoo 18 has no native_FILEenv var support. A custom entrypoint wrapper was evaluated and rejected due to fragility.
A separate docker-compose.monitoring.yml runs Prometheus + Grafana locally,
communicating with the main stack via a shared Docker network.
docker compose -f docker-compose.yml -f docker-compose.monitoring.yml up -dGrafana → http://localhost:3000 (admin / admin)
Prometheus → http://localhost:9090
For production with a central Prometheus server, uncomment the ports: lines
in the exporter services and replace 100.x.x.x with your VPN IP.
| Service | Access | Notes |
|---|---|---|
| Odoo | https://localhost | Main application |
| Nginx | :80 / :443 | Reverse proxy |
| PostgreSQL | internal only | Not published to host |
| node-exporter | MONITORING_IP:9100 | Host metrics |
| postgres-exporter | MONITORING_IP:9187 | DB metrics |
Backups run daily at 02:00 UTC. Files stored in the backup_data volume.
# Trigger manual backup
docker exec odoo-backup /bin/sh /backup.sh
# Restore database
docker exec -i odoo-db psql -U odoo odoo < backup_YYYY-MM-DD.sql
# Restore filestore
docker run --rm \
-v odoo_data:/var/lib/odoo \
-v $(pwd)/backups:/backups alpine \
tar -xzf /backups/filestore_YYYY-MM-DD.tar.gz -C /var/lib/odoo