Docker 部署
只想先看到登入畫面,用 安裝與環境 的最小 Compose 即可。本頁給兩套可進版控的堆疊:開發(含測信與備份)與正式(HTTPS)。同一套目錄、依客戶改 .env,避免每台機器從零拼容器。
- 開發(dev):Odoo 18、PostgreSQL、MailCatcher、每日資料庫備份,可選 VS Code Dev Container。
- 正式(prod):再加 Nginx 反向代理與 Let’s Encrypt。密碼與網域只放
.env。
目錄結構(建議)
odoo-stack/
├─ dev/ # 本機開發測試
│ ├─ compose-dev.yml
│ ├─ .env.example
│ ├─ odoo/
│ │ ├─ config/odoo.conf
│ │ ├─ extra-addons/
│ │ └─ filestore/
│ ├─ postgres/
│ └─ backups/
│
├─ prod/ # 正式上線(客戶機器)
│ ├─ compose-prod.yml
│ ├─ .env.example
│ ├─ nginx/
│ │ ├─ certs/ # acme-companion 自動管理
│ │ ├─ html/ # ACME 驗證 webroot
│ │ ├─ vhost.d/ # 針對單一網域覆寫
│ │ └─ conf.d/00-tuning.conf
│ ├─ odoo/
│ │ ├─ config/odoo.conf
│ │ ├─ extra-addons/
│ │ └─ filestore/
│ ├─ postgres/
│ └─ backups/
│
└─ .devcontainer/ # 可選:VS Code Remote Containers(對 dev/ 使用)
└─ devcontainer.json
本機開發與測試(dev)
1) dev/.env.example(複製成 dev/.env 後再修改)
# 基本
STACK_NAME=odoo18-dev
ODOO_VERSION=18.0
# Postgres
POSTGRES_DB=odoodb
POSTGRES_USER=odoo
POSTGRES_PASSWORD=dev_supersecret
PGDATA=/var/lib/postgresql/data/pgdata
# 備份
BACKUP_SCHEDULE=0 30 3 * * * # 每天 03:30
BACKUP_KEEP_DAYS=7
BACKUP_COMPRESS=true
# MailCatcher(Odoo 發信測試)
MAILCATCHER_HTTP_PORT=1080
MAILCATCHER_SMTP_PORT=1025
# 對外映射(本機使用)
ODOO_WEB_PORT=8069
2) dev/odoo/config/odoo.conf
[options]
; addons 路徑:官方 + 你的自訂模組
addons_path = /usr/lib/python3/dist-packages/odoo/addons,/mnt/extra-addons
data_dir = /var/lib/odoo
; 重要:開 dev/proxy 模式
proxy_mode = True
; DB 連線(與 compose 一致)
db_host = db
db_port = 5432
db_user = odoo
db_password = dev_supersecret
; 資源限制(本機可酌情放寬)
limit_time_cpu = 120
limit_time_real = 120
; E-mail(指向 MailCatcher 的 SMTP)
smtp_server = mailcatcher
smtp_port = 1025
smtp_ssl = False
3) dev/compose-dev.yml
version: "3.9"
services:
db:
image: postgres:16
container_name: ${STACK_NAME}-db
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- PGDATA=${PGDATA}
volumes:
- ./postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 10
restart: unless-stopped
odoo:
image: odoo:${ODOO_VERSION}
container_name: ${STACK_NAME}-odoo
depends_on:
db:
condition: service_healthy
ports:
- "${ODOO_WEB_PORT}:8069"
environment:
- TZ=Asia/Taipei
volumes:
- ./odoo/filestore:/var/lib/odoo
- ./odoo/extra-addons:/mnt/extra-addons
- ./odoo/config/odoo.conf:/etc/odoo/odoo.conf:ro
restart: unless-stopped
# 測試郵件:Web UI http://localhost:1080 ,SMTP host=mailcatcher port=1025
mailcatcher:
image: dockage/mailcatcher:0.9.0
container_name: ${STACK_NAME}-mailcatcher
ports:
- "${MAILCATCHER_HTTP_PORT}:1080"
- "${MAILCATCHER_SMTP_PORT}:1025"
restart: unless-stopped
# 每日自動備份(輸出到 ./backups)
pgbackups:
image: prodrigestivill/postgres-backup-local:16
container_name: ${STACK_NAME}-pgbackups
environment:
- POSTGRES_HOST=db
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- SCHEDULE=${BACKUP_SCHEDULE}
- BACKUP_KEEP_DAYS=${BACKUP_KEEP_DAYS}
- COMPRESS=${BACKUP_COMPRESS}
- BACKUP_DIR=/backups
volumes:
- ./backups:/backups
depends_on:
db:
condition: service_healthy
restart: unless-stopped
4) 一鍵啟動(dev)
- Odoo:http://localhost:8069
- MailCatcher(郵件測試):http://localhost:1080
- 備份輸出:
dev/backups/
5) (可選)VS Code Remote Containers
/.devcontainer/devcontainer.json
{
"name": "Odoo 18 Dev",
"dockerComposeFile": ["../dev/compose-dev.yml"],
"service": "odoo",
"workspaceFolder": "/workspace",
"shutdownAction": "stopCompose",
"postCreateCommand": "pip3 install --user black isort",
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"tamasfe.even-better-toml",
"oderwat.indent-rainbow",
"batisteo.vscode-django"
],
"settings": {
"python.defaultInterpreterPath": "/usr/bin/python3",
"python.analysis.typeCheckingMode": "basic",
"editor.formatOnSave": true
}
}
},
"remoteUser": "root",
"mounts": [
"source=${localWorkspaceFolder}/dev/odoo/extra-addons,target=/mnt/extra-addons,type=bind",
"source=${localWorkspaceFolder},target=/workspace,type=bind"
]
}
打開 VS Code →
Dev Containers: Reopen in Container,就能在 Odoo 容器內開發,/mnt/extra-addons放你的自製模組。
正式上線(prod)
1) prod/.env.example(複製成 prod/.env 後再修改)
# 堆疊與版本
STACK_NAME=odoo18
ODOO_VERSION=18.0
# 你的正式網域(A/AAAA 必須指向這台機器)
ODOO_DOMAIN=odoo.example.com
LETSENCRYPT_EMAIL=admin@example.com
# Postgres
POSTGRES_DB=odoodb
POSTGRES_USER=odoo
POSTGRES_PASSWORD=change_this_prod_secret
PGDATA=/var/lib/postgresql/data/pgdata
# 備份
BACKUP_SCHEDULE=0 15 3 * * * # 每天 03:15
BACKUP_KEEP_DAYS=14
BACKUP_COMPRESS=true
2) prod/odoo/config/odoo.conf
[options]
addons_path = /usr/lib/python3/dist-packages/odoo/addons,/mnt/extra-addons
data_dir = /var/lib/odoo
proxy_mode = True
db_host = db
db_port = 5432
db_user = odoo
db_password = change_this_prod_secret
limit_time_cpu = 120
limit_time_real = 120
; 上線環境的郵件請設你/客戶的 SMTP(這裡示意留白)
; smtp_server = smtp.example.com
; smtp_port = 587
; smtp_ssl = False
; smtp_user = ...
; smtp_password = ...
3) prod/nginx/conf.d/00-tuning.conf
client_max_body_size 64m;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
gzip on;
gzip_types text/plain text/css application/json application/javascript application/xml;
4) (可選)針對該網域的覆寫 prod/nginx/vhost.d/odoo.example.com
檔名請改成你的真實網域
proxy_buffering off;
# WebSocket
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
add_header Referrer-Policy strict-origin-when-cross-origin;
# 強制 HTTPS(評估影響再開)
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
5) prod/compose-prod.yml
version: "3.9"
services:
nginx-proxy:
image: nginxproxy/nginx-proxy:alpine
container_name: ${STACK_NAME}-nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/certs:/etc/nginx/certs:ro
- ./nginx/vhost.d:/etc/nginx/vhost.d
- ./nginx/html:/usr/share/nginx/html
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- /var/run/docker.sock:/tmp/docker.sock:ro
restart: unless-stopped
acme-companion:
image: nginxproxy/acme-companion
container_name: ${STACK_NAME}-acme
environment:
- DEFAULT_EMAIL=${LETSENCRYPT_EMAIL}
- NGINX_PROXY_CONTAINER=${STACK_NAME}-nginx-proxy
volumes:
- ./nginx/certs:/etc/nginx/certs
- ./nginx/vhost.d:/etc/nginx/vhost.d
- ./nginx/html:/usr/share/nginx/html
- /var/run/docker.sock:/var/run/docker.sock:ro
depends_on:
- nginx-proxy
restart: unless-stopped
db:
image: postgres:16
container_name: ${STACK_NAME}-db
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- PGDATA=${PGDATA}
volumes:
- ./postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 10
restart: unless-stopped
odoo:
image: odoo:${ODOO_VERSION}
container_name: ${STACK_NAME}-odoo
depends_on:
db:
condition: service_healthy
nginx-proxy:
condition: service_started
acme-companion:
condition: service_started
environment:
- TZ=Asia/Taipei
# 讓 nginx-proxy 反代 + acme 簽憑證
- VIRTUAL_HOST=${ODOO_DOMAIN}
- LETSENCRYPT_HOST=${ODOO_DOMAIN}
- VIRTUAL_PORT=8069
expose:
- "8069"
volumes:
- ./odoo/filestore:/var/lib/odoo
- ./odoo/extra-addons:/mnt/extra-addons
- ./odoo/config/odoo.conf:/etc/odoo/odoo.conf:ro
restart: unless-stopped
pgbackups:
image: prodrigestivill/postgres-backup-local:16
container_name: ${STACK_NAME}-pgbackups
environment:
- POSTGRES_HOST=db
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- SCHEDULE=${BACKUP_SCHEDULE}
- BACKUP_KEEP_DAYS=${BACKUP_KEEP_DAYS}
- COMPRESS=${BACKUP_COMPRESS}
- BACKUP_DIR=/backups
volumes:
- ./backups:/backups
depends_on:
db:
condition: service_healthy
restart: unless-stopped
6) 一鍵上線(prod)
- DNS 指向:把
ODOO_DOMAIN(例如odoo.example.com)的 A/AAAA 記錄指到該伺服器。 - 建立資料夾(照上面目錄),把
.env.example複製成.env並修改參數。 - 啟動:
https://你的網域。
設定說明(重點參數)
ODOO_VERSION=18.0:指定 Odoo 版本 18。ODOO_DOMAIN(prod):要對外服務的網域名稱;務必先設定好 DNS。LETSENCRYPT_EMAIL(prod):憑證註冊/續期通知信箱。POSTGRES_*:資料庫名稱、使用者、密碼;dev 與 prod 建議不同。BACKUP_*:備份排程、保留天數、壓縮。備份輸出在./backups/。PGDATA:PostgreSQL 的內部資料目錄(保持預設即可)。VIRTUAL_HOST/LETSENCRYPT_HOST(prod):由nginx-proxy/acme-companion使用,請設定為你的ODOO_DOMAIN。
常見情境與排錯(nginx / HTTPS)
- 80/443 未開或被防火牆擋住 → 憑證簽發會失敗。請放行 TCP 80、443。
- DNS 尚未生效 → 憑證挑戰過不了;稍等或確認 A/AAAA 是否正確。
- 附件上傳 413 → 調大
client_max_body_size(已預設 64m)。 - 報表或長查詢逾時 → 調整
proxy_read_timeout/send_timeout(預設 600 秒)。 - HSTS 啟用 → 改
vhost.d/<domain>檔加入 HSTS 標頭,注意影響子網域與測試環境。 - 多客戶/多站 → 同一台機器可以再加一個 Odoo 服務,只要給不同的
VIRTUAL_HOST/LETSENCRYPT_HOST與資料、備份目錄即可。
備份與還原
自動備份
- 每日產出檔案於
dev/backups/或prod/backups/。 - 你可再用
restic / rclone / rsync同步到 S3 / Backblaze / NAS 異地備援。
手動備份
# 在 dev 或 prod 目錄下
docker exec -it ${STACK_NAME}-db pg_dump -U ${POSTGRES_USER} -d ${POSTGRES_DB} > backups/manual.sql
還原(建議停 Odoo 再進行)
docker stop ${STACK_NAME}-odoo
# 砍 DB、重建(或用新 DB 名)
docker exec -it ${STACK_NAME}-db psql -U ${POSTGRES_USER} -d postgres -c "DROP DATABASE ${POSTGRES_DB};"
docker exec -it ${STACK_NAME}-db psql -U ${POSTGRES_USER} -d postgres -c "CREATE DATABASE ${POSTGRES_DB} OWNER ${POSTGRES_USER};"
# 匯入 .sql.gz
gunzip -c backups/odoodb_YYYYMMDD_HHMMSS.sql.gz | docker exec -i ${STACK_NAME}-db psql -U ${POSTGRES_USER} -d ${POSTGRES_DB}
docker start ${STACK_NAME}-odoo
可以直接拿這包幫客戶上線並管理嗎?
可以,且非常適合。建議再加:
- 系統監控:cAdvisor + Prometheus + Grafana(CPU/RAM/磁碟/網路)。
- 日誌集中:用 Loki/ELK 收集
nginx與odoo日誌。 - 安全性:只開 80/443/SSH;Postgres 僅限容器網路;定期更新容器版本。
- 異地備援:備份同步到 S3/NAS;定期做「備份還原演練」。
同一機要跑多套 Odoo 時,請給不同的 VIRTUAL_HOST、資料目錄與備份目錄,不要共用同一個 Postgres volume。資料庫 GUI 與 Ansible 自動化不在本頁範圍;本機看表可用 完整堆疊 裡的 pgAdmin。
正式寄信不要用 MailCatcher,改 郵件設定。