Odoo 20 Docker Compose 部署实战
Odoo 20 社区版 Docker Compose 全流程实测:官方 odoo/postgres 编排、容器化启动三大排障实录(http_interface 默认 127.0.0.1、数据目录属主、db 连接注入)、建库初始化、安装 CRM/库存、备份恢复与持久化验证。
Odoo 20 Docker Compose 部署实战
> 目标:在一台 Ubuntu 服务器上用官方 odoo/postgres 镜像以 Docker Compose 完成 Odoo 20 社区版的全流程部署——起栈、修复容器化启动的三个典型报错、Web 初始化建库(演示库与空库)、登录验证、命令行安装 CRM 与库存应用、备份与恢复、重启后数据持久化验证,全部命令与回显均来自 2026-10-27 的真实实测。产品选型层面的结论见 《Odoo 介绍与选型指南》。
一、环境信息
- 操作系统:Ubuntu 22.04(x86_64,真实服务器视角)
- Docker:Docker 29.9.2(
docker version实测回显) - Docker Compose:v5.6.1(
docker compose version实测回显) - 应用镜像:
odoo:20(Docker Official Image,实测启动日志显示内部版本 Odoo Server 20.0-20261015,Python 3.12.3) - 数据库镜像:
postgres:17 - 服务端口:宿主机 8133 → 应用 8069(Odoo 默认 HTTP 端口)
- 专用网络:
odoo-net(自定义 bridge,显式网段 10.89.64.0/24) - 项目目录(服务器侧约定为
/opt/odoo/,下文所有相对路径基于该目录):/opt/odoo/ ├── docker-compose.yml # 编排文件 ├── config/odoo.conf # Odoo 配置文件(挂载进应用) ├── extra-addons/ # 自定义模块目录(挂载为 /mnt/extra-addons) ├── odoo-data/ # Odoo filestore/session(挂载为 /var/lib/odoo) └── pgdata/ # PostgreSQL 数据(挂载为 PGDATA) - 实测账号:数据库管理员
odoo/odoo20pg;Odoo 应用管理员admin/q1w2e3r4(演示口令)。
二、镜像选型说明
Odoo 官方在 Docker Hub 维护 Docker Official Image(维护方 github.com/odoo/docker),选型取证于 2026-10-27(来源:hub.docker.com/_/odoo 页面):
| 项目 | 官方事实 |
|---|---|
| 当前标签 | 20.0-20260926、20.0、20、latest 四个标签同指向 20.0 最新构建;19.0/19、18.0/18 为上一代标签 |
| 构建节奏 | 镜像随官方每晚构建更新(nightly.odoo.com),拉取到的 20.0 内部版本为 20.0-20261015 |
| 架构 | amd64、arm64v8、ppc64le |
| 镜像体量 | 压缩层约 760.8 MB(Hub 页面标注) |
| 数据库要求 | 镜像要求一个运行中的 PostgreSQL 服务(官方页原文 'This image requires a running PostgreSQL server') |
选型结论(关键差异解读):版本标签用 odoo:20 固定大版本,避免用 latest 导致年度大版本升级不可控;数据库官方 compose 示例沿用 postgres:15,本篇实测采用 postgres:17(2026 年现役 LTS 期版本,与 Odoo 20 兼容性经本篇全流程实测验证);不建议选 postgres:18 以外的更高版本盲升——大版本跨越以官方发布说明为准。
三、前置条件
-
服务器已安装 Docker 29.x 与 Docker Compose v2+ 插件,且当前用户在 docker 用户组(或使用 sudo)。
-
端口规划:宿主机选一个未占用端口映射 8069(本篇用 8133,检查命令与预期输出):
ss -ltn | grep 8133 || echo '端口空闲'实测回显(空闲时):
端口空闲 -
拉取镜像(Docker 守护进程已配代理时直连官方源即可;本实测官方源拉取正常):
docker pull postgres:17 && docker pull odoo:20实测回显(末行):
Status: Downloaded newer image for postgres:17 Status: Image is up to date for odoo:20 -
创建目录骨架并预置配置文件:
mkdir -p /opt/odoo/{config,extra-addons,odoo-data,pgdata}
⚠️ 注意:
odoo-data与extra-addons两个目录必须在启动前把属主改为容器内 odoo 用户的 uid/gid(实测为100:101,见docker exec odoo-20-app id odoo回显uid=100(odoo) gid=101(odoo))。否则会命中第四章排障实录 2 的权限报错。
四、部署步骤
4.1 编写 docker-compose.yml
compose 项目名显式定为 odoo-20-stack,两个服务放入同一自定义网络(显式网段避免与宿主其他容器网段冲突):
name: odoo-20-stack
services:
db:
image: postgres:17
container_name: odoo-20-db
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=odoo
- POSTGRES_PASSWORD=odoo20pg
- PGDATA=/var/lib/postgresql/data/pgdata
volumes:
- ./pgdata:/var/lib/postgresql/data/pgdata
networks:
- odoo-net
odoo:
image: odoo:20
container_name: odoo-20-app
depends_on:
- db
ports:
- "8133:8069"
volumes:
- ./odoo-data:/var/lib/odoo
- ./config:/etc/odoo
- ./extra-addons:/mnt/extra-addons
environment:
- HOST=db
- USER=odoo
- PASSWORD=odoo20pg
networks:
- odoo-net
networks:
odoo-net:
driver: bridge
ipam:
config:
- subnet: 10.89.64.0/24
逐项说明:db 服务用官方 postgres 镜像并显式设置 PGDATA 子目录(官方 compose 示例同款做法,便于数据目录整体挂载);odoo 服务通过 HOST/USER/PASSWORD 环境变量告知数据库地址与凭据(官方镜像环境变量约定,见第六章全表);三个数据卷分别持久化 filestore/session、配置文件与自定义模块。
4.2 编写最小配置文件 config/odoo.conf
[options]
addons_path = /mnt/extra-addons
data_dir = /var/lib/odoo
http_interface = 0.0.0.0
db_host = db
db_port = 5432
db_user = odoo
db_password = odoo20pg
⚠️ Odoo 20 容器化部署的两个关键点(实测结论,与旧版本差异大):
http_interface = 0.0.0.0必须显式写。Odoo 20 把 HTTP 监听接口默认值改为127.0.0.1(配置源码odoo/tools/config.py中--http-interface的my_default='127.0.0.1',且配置留空时代码会强制兜底为127.0.0.1),不写这行容器就只能本机自访问。- 数据库连接四项直接写入配置文件比只依赖环境变量更可靠:官方 entrypoint 只在启动命令形如
odoo -- <参数>时才把HOST/USER/PASSWORD环境变量转成命令行参数附加,直接执行docker exec odoo-20-app odoo -d xxx ...这类命令时不会附加,实测报database: default@default:default连接失败(排障实录 3)。写入 odoo.conf 后任何方式启动都能正确连接。
4.3 修复数据目录属主并启动
# 预修属主:容器内 odoo 用户为 uid=100 gid=101
chown -R 100:101 /opt/odoo/odoo-data /opt/odoo/extra-addons
# 启动整个栈
cd /opt/odoo && docker compose up -d
实测回显:
Container odoo-20-db Created
Container odoo-20-app Created
Container odoo-20-db Starting
Container odoo-20-db Started
Container odoo-20-app Starting
Container odoo-20-app Started
4.4 启动验证
docker compose ps
实测回显:
NAME STATUS PORTS
odoo-20-app Up 20 seconds 8071-8072/tcp, 0.0.0.0:8133->8069/tcp, [::]:8133->8069/tcp
odoo-20-db Up 20 seconds 5432/tcp
应用日志(首启关键行,docker logs odoo-20-app 节选):
2026-10-27 11:38:12,812 1 INFO ? odoo: Odoo version 20.0-20261015
2026-10-27 11:38:13,022 1 INFO ? odoo.service.server: HTTP service running on 0.0.0.0:8069
外部可达性验证(配置 http_interface 前该请求为 500/超时,修复后 200):
curl -sI -m 8 http://127.0.0.1:8133/web/database/selector | head -2
实测回显:
HTTP/1.1 200 OK
date: Tue, 27 Oct 2026 11:38:43 GMT
浏览器访问 http://<服务器IP>:8133 即进入数据库选择/创建向导(数据库管理器路径为 /web/database/manager)。
4.5 部署排障实录(三处必踩报错与定位过程)
以下三条均为本次实测真实发生、逐一定位的记录,读者按现象对号入座:
排障 1:服务日志显示 HTTP service running on 127.0.0.1:8069,外部端口不通
- 现象:容器 Up、端口已映射,但
curl http://127.0.0.1:8133无响应;容器内/proc/net/tcp仅见0100007F:1F85(=127.0.0.1:8069)。 - 定位:
grep -n "http_interface" /usr/lib/python3/dist-packages/odoo/tools/config.py,实测回显:289: group.add_option("--http-interface", dest="http_interface", my_default='127.0.0.1', 768: # ensure default http_interface is set 769: if not self['http_interface']: 770: self._runtime_options['http_interface'] = '127.0.0.1' - 解决:odoo.conf 增加
http_interface = 0.0.0.0,docker compose restart odoo后日志变为HTTP service running on 0.0.0.0:8069,外部访问恢复。
排障 2:页面 500,日志报 PermissionError: [Errno 13] Permission denied: '/var/lib/odoo/sessions'
- 现象:端口通了但访问返回
HTTP/1.1 500 INTERNAL SERVER ERROR,日志出现完整 traceback,末行:PermissionError: [Errno 13] Permission denied: '/var/lib/odoo/sessions' - 定位:bind mount 的宿主目录由 Docker 以 root 自动创建(
ls -ldn odoo-data显示0 0属主),而容器内进程以odoo用户(uid=100/gid=101)运行。 - 解决:
chown -R 100:101 odoo-data extra-addons后立即恢复 200。 - 替代方案:改用 named volume(
odoo-web-data:/var/lib/odoo)可绕过属主问题,代价是数据不在工作目录直观可见。
排障 3:docker exec ... odoo -d <库> -i <模块> 报连接失败 database: default@default:default
- 现象:在运行中的应用容器里直接执行 odoo 命令做模块安装,秒退并报
odoo.sql_db: Connection to the database failed。 - 定位:日志行
odoo: database: default@default:default表明 db_host/db_user/db_password 均取了空默认值——官方 entrypoint 的数据库环境变量注入只作用于odoo --形式的启动命令,不作用于 exec 直跑。 - 解决:把
db_host = db、db_port = 5432、db_user = odoo、db_password = odoo20pg写入/etc/odoo/odoo.conf(4.2 配置同款),任何启动方式都生效。 - 顺带现象:odoo.conf 指向的
/mnt/extra-addons为空目录时,启动日志会出现两行WARNING ... invalid addons directory '/mnt/extra-addons', skipped,不影响服务;放入有效模块后消失。
4.6 建库与初始化(演示库 + 空库)
浏览器向导之外,用 curl 对数据库管理器接口做等价实测(两种方式结果一致)。先建空库 odoo20main(不装演示数据):
curl -s -X POST http://127.0.0.1:8133/web/database/create \
-d 'master_pwd=admin&name=odoo20main&login=admin&password=q1w2e3r4&phone=&lang=zh_CN&country_code=CN&demo=false' \
-o /dev/null -w 'HTTP %{http_code}\n'
实测回显(time 计时):
HTTP 303
real 0m9.327s
再建演示库 odoo20demo(demo=true 载入演示数据):
curl -s -X POST http://127.0.0.1:8133/web/database/create \
-d 'master_pwd=admin&name=odoo20demo&login=admin&password=q1w2e3r4&phone=&lang=zh_CN&country_code=CN&demo=true' \
-o /dev/null -w 'HTTP %{http_code}\n'
实测回显:
HTTP 303
real 0m10.122s
说明:
master_pwd=admin是数据库管理器的主控口令(Odoo 默认admin_passwd为admin,生产环境必须在 odoo.conf 用admin_passwd改掉,见第七章)。演示库是否建好可用数据库侧核对:docker exec odoo-20-db psql -U odoo -d postgres -Atc "SELECT datname FROM pg_database WHERE datistemplate=false",实测回显:postgres odoo20main odoo20demo演示数据生效证据:odoo20demo 库
res_users中存在demo/light/portal等演示账号(psql 查询实测)。
4.7 登录验证与安装业务应用
登录(会话 cookie 落盘后访问后台):
curl -s -c cookies.txt -X POST http://127.0.0.1:8133/web/login \
-d 'db=odoo20demo&login=admin&password=q1w2e3r4' \
-o /dev/null -w 'HTTP %{http_code}\n'
实测回显:
HTTP 303
命令行安装 CRM 与库存两个应用(odoo -i 安装,--stop-after-init 执行完即退):
docker exec odoo-20-app odoo -d odoo20demo -i crm,stock --stop-after-init
实测回显(尾部关键行):
2026-10-27 11:42:29,287 17 INFO odoo20demo odoo.registry: Registry changed, signaling through the database
2026-10-27 11:42:29,288 17 INFO odoo20demo odoo.registry: Registry loaded in 20.900s
2026-10-27 11:42:29,288 17 INFO odoo20demo odoo.service.server.ThreadedServer: Initiating shutdown
real 0m21.733s
安装结果核对(psql 查询模块状态,实测回显):
SELECT name, latest_version, state FROM ir_module_module WHERE name IN ('crm','stock') ;
name | latest_version | state
--------+----------------+-----------
crm | 20.0.1.9 | installed
stock | 20.0.1.1 | installed
(2 rows)
依赖链自动带入:安装这两个应用后,库内 state='installed' 的模块从初始 16 个增至 56 个(mail、contacts、product 等依赖模块自动装入)。登录后访问 http://<服务器IP>:8133/odoo?db=odoo20demo 即可看到 CRM 与库存应用图标(实测 HTTP 302 引导跳转 → 后台 200)。
五、数据持久化说明
三个持久化面与其落盘位置(bind mount 到 /opt/odoo/ 下):
| 宿主目录 | 容器内路径 | 内容 |
|---|---|---|
| ./pgdata | /var/lib/postgresql/data/pgdata | 全部数据库(业务数据主体) |
| ./odoo-data | /var/lib/odoo | filestore(附件/图片)、sessions、addons 缓存 |
| ./config | /etc/odoo | odoo.conf 配置 |
| ./extra-addons | /mnt/extra-addons | 自定义模块 |
持久化实测:整栈 down(含删除网络)后重新 up,数据完整:
docker compose down && docker compose up -d
实测回显(down 关键行 + 重启后核对):
Network odoo-20-stack_odoo-net Removing
Network odoo-20-stack_odoo-net Removed
Container odoo-20-app Started
重启后库列表与 Web 状态(实测回显):
postgres
odoo20main
odoo20demo
odoo20restore
HTTP/1.1 200 OK
四库俱在、服务正常,证明 bind mount 持久化成立。注意:docker compose down -v 会删除 named volume 但不影响 bind mount;若未来切换为 named volume 方案,请勿对数据卷使用 -v 降级。
六、环境变量表
官方 odoo 镜像通过环境变量注入数据库连接(取证并实测自官方镜像文档,2026-10-27):
| 变量 | 作用 | 默认值 | 本篇实测取值 |
|---|---|---|---|
HOST | PostgreSQL 地址(容器网络内用服务名) | db | db |
PORT | PostgreSQL 端口 | 5432 | 5432(未显式设置) |
USER | 连接用的数据库角色 | odoo | odoo |
PASSWORD | 该角色的密码 | odoo | odoo20pg |
PASSWORD_FILE | 以 secrets 文件注入密码(优先于 PASSWORD) | 无 | 未使用(内网演示环境) |
PostgreSQL 侧环境变量(postgres 官方镜像约定):POSTGRES_DB=postgres(初始化的默认库)、POSTGRES_USER=odoo、POSTGRES_PASSWORD=odoo20pg、PGDATA=/var/lib/postgresql/data/pgdata。
优先级实测结论:entrypoint 会把环境变量转成 --db-host 等命令行参数附加到 odoo 启动命令;但若 odoo.conf 中已有同名参数,则以 配置文件值 为准(entrypoint 读取配置内已有值后再附加)。实践建议:环境变量与 odoo.conf 二选一为主,本篇以 odoo.conf 为主、环境变量作兜底,避免两处不一致引发排查困惑。
七、配置参数说明
容器内配置文件为 /etc/odoo/odoo.conf(由 ./config 挂载)。本篇部署用的最小配置逐项含义:
| 参数 | 实测取值 | 说明 |
|---|---|---|
addons_path | /mnt/extra-addons | 自定义模块搜索路径;官方镜像已内置 /usr/lib/python3/dist-packages/odoo/addons 与数据目录内的 /var/lib/odoo/addons/20.0(启动日志实测可见) |
data_dir | /var/lib/odoo | filestore/session/自动下载模块的根目录,必须与挂载卷一致 |
http_interface | 0.0.0.0 | Odoo 20 默认 127.0.0.1,容器化必须显式放开(排障实录 1) |
db_host/db_port/db_user/db_password | db/5432/odoo/odoo20pg | 数据库连接四件套,写入配置后 exec 直跑 odoo 命令也能连库(排障实录 3) |
admin_passwd | 未设置(默认 admin) | 数据库管理器主控口令;生产必须修改,否则任何能访问 8133 端口的人都可以建库/删库/备份任意库 |
完整的 odoo.conf 参数(workers、proxy_mode、日志、limit_* 系列等)逐项详解与本篇配置的深化,见 《Odoo 20 配置参数详解》(本专栏配置篇)。
八、配置文件挂载
挂载机制实测要点:
./config:/etc/odoo为整目录挂载:容器内看到的/etc/odoo/odoo.conf即宿主文件,修改后docker compose restart odoo生效(实测通过两次配置变更验证)。- 启动日志会回显生效配置:
Using configuration file at /etc/odoo/odoo.conf,其下一行列出解析后的 addons 路径与数据库连接(odoo: database: odoo@db:5432),是核对配置生效的最快方式。 - 官方还提供配置模板(github.com/odoo/docker 仓库各版本目录下的 odoo.conf),其已为容器场景预设了 addons_path 与 data_dir,可作为扩写底稿。
- 运行中修改配置的替代入口:odoo 命令行参数优先级高于配置文件(如
odoo --db-filter=xxx),仅适合一次性实验,勿作为持久配置方式。
九、等价 docker run
与上述 compose 完全等价的两条 docker run(按官方镜像文档形态,先库后应用):
# 1) PostgreSQL(数据持久化到 odoo-db 卷)
docker run -d -v odoo-db:/var/lib/postgresql/data \
-e POSTGRES_USER=odoo -e POSTGRES_PASSWORD=odoo20pg -e POSTGRES_DB=postgres \
-e PGDATA=/var/lib/postgresql/data/pgdata \
--name odoo-20-db postgres:17
# 2) Odoo 应用(挂载数据/配置/自定义模块,映射 8133)
docker run -d -v /opt/odoo/odoo-data:/var/lib/odoo \
-v /opt/odoo/config:/etc/odoo \
-v /opt/odoo/extra-addons:/mnt/extra-addons \
-p 8133:8069 \
--name odoo-20-app --link odoo-20-db:db odoo:20
与 compose 的取舍:docker run 形态与官方文档一致、便于单容器调试;compose 形态声明式可复制、网络/依赖/重启策略统一管理,多容器栈推荐 compose。两种方式混用时注意容器不能同名、网络需同网(--network odoo-20-stack_odoo-net)。
十、常用运维命令
# 栈状态与端口
docker compose ps
# 跟踪应用日志(-f 跟随)
docker logs -f odoo-20-app
# 重启单个服务(配置变更后)
docker compose restart odoo
# 整栈停止/启动(数据保留)
docker compose down && docker compose up -d
# 版本确认
docker exec odoo-20-app odoo --version
odoo --version 实测回显:
Odoo Server 20.0-20261015
备份(数据库管理器接口,zip 包含 dump.sql + manifest.json + filestore):
curl -s -X POST http://127.0.0.1:8133/web/database/backup \
-d 'master_pwd=admin&name=odoo20demo&backup_format=zip' \
-o odoo20demo-20261027.zip -w 'HTTP %{http_code}\n'
实测回显:
HTTP 200
产物核对(实测回显,大小 7360559 字节):
dump.sql
manifest.json
filestore/b3/b3ad3f5be80f15cc00f95c1b98e358443dca2328
filestore/ca/ca6a7b13789ae5a9075f6ae406d1725eb2ab13be
...
恢复(备份包恢复为新库 odoo20restore,实测):
curl -s -X POST http://127.0.0.1:8133/web/database/restore \
-F 'master_pwd=admin' -F 'name=odoo20restore' -F 'copy=false' \
-F 'backup_file=@odoo20demo-20261027.zip' \
-o /dev/null -w 'HTTP %{http_code}\n'
实测回显:
HTTP 303
恢复后库列表新增 odoo20restore(psql 实测回显见 4.6 同款命令)。命令行侧的备份/恢复与模块升级(odoo db、-u 参数)等进阶用法见 《Odoo 20 常用命令与运维实战》(本专栏命令篇)。
十一、常见问题
Q1:容器已 Up,端口也映射了,但浏览器打不开页面?
最常见于 Odoo 20:默认监听 127.0.0.1 而非所有网卡。在 odoo.conf 加 http_interface = 0.0.0.0 并重启(定位过程见排障实录 1,含 config.py 源码行号证据)。
Q2:页面 500,日志报 Permission denied: '/var/lib/odoo/sessions'?
bind mount 目录属主是 root 而进程用户是 uid=100。执行 chown -R 100:101 odoo-data extra-addons;或改用 named volume(排障实录 2)。
Q3:docker exec 里跑 odoo 命令报 Connection to the database failed?
把 db 连接四项写入 odoo.conf(排障实录 3);只靠环境变量时 entrypoint 不一定注入到 exec 场景。
Q4:建库接口报 AccessDenied?
master_pwd 与 odoo.conf 的 admin_passwd 不匹配(未设置时默认 admin)。生产环境务必设置强口令:odoo.conf 加 admin_passwd = <强口令> 后重启。
Q5:启动日志两行 WARNING ... invalid addons directory '/mnt/extra-addons', skipped 要处理吗?
目录为空时的正常提示,不影响服务;放入有效模块(含 __manifest__.py)后消失。
Q6:postgres 容器重启后 Odoo 报连不上库?
官方镜像文档明确:PostgreSQL 重启后需同步重启 Odoo 实例(连接随服务地址重建)。docker compose restart odoo 即可。
十二、参考链接
- 官方 Docker 镜像页(标签/环境变量/compose 示例,2026-10-27 取证):https://hub.docker.com/_/odoo
- 官方镜像维护仓库(含各版本 odoo.conf 模板):https://github.com/odoo/docker
- Odoo 官方文档 20.0(安装与 on-premise 管理):https://www.odoo.com/documentation/20.0/
- 官方数据库管理文档(建库/复制/备份恢复):https://www.odoo.com/documentation/20.0/administration/on_premise.html
- 官方每晚构建站:https://nightly.odoo.com/
- 本专栏选型认知:《Odoo 介绍与选型指南》