OpenProject Docker Compose 部署实战
OpenProject 17.9.0 官方镜像 Docker Compose 从零部署实战:一体化栈初始化、首登强制改密与密码策略实测、API 密钥(opapi- 前缀)与项目/工作包/看板创建验证、备份任务实测、官方 Helm Chart k8s 形态溯源,含 7 个排障实录。
OpenProject Docker Compose 部署实战
目标:用官方 Docker 镜像
openproject/openproject:17.9.0通过 Docker Compose 从零部署一套 OpenProject 项目管理平台(一体化镜像内置 PostgreSQL 17 与 memcached),完成首次初始化、管理员改密、密码策略调整、API 密钥创建、项目/工作包/看板创建与数据库备份验证。已在 Ubuntu 22.04(x86_64)服务器、OpenProject 17.9.0 上全量实测通过。
一、环境信息
本文全部命令与回显来自以下实测环境,逐条可复现:
- 操作系统:Ubuntu 22.04(x86_64)
- OpenProject:17.9.0(Community 标准版,官方一体化镜像
openproject/openproject:17.9.0) - 镜像内置组件:PostgreSQL 17、memcached、Rails 应用服务器(Puma)、GoodJob 后台任务、Caddy 入口
- Docker Engine:29.9.2;Docker Compose:v5.6.1(v2 命令行语法)
- 服务端口:宿主机 8142 → 容器内 80
- 数据目录:
/var/lib/openproject/pgdata(数据库)、/var/lib/openproject/assets(附件) - 演示管理账号:
admin/q1w2e3r4(首启种子口令为admin/admin,初始化后按第四章流程改为统一演示口令) - 实测时间:2026-10-27
二、镜像选型说明
官方在 Docker Hub 的仓库为 openproject/openproject,提供两种容器形态,选型直接决定部署方式:
| 形态 | tag 后缀 | 内置组件 | 官方建议 |
|---|---|---|---|
| slim | -slim | 仅应用与应用服务器 | 生产推荐,配合官方 Compose 仓库或 Helm chart 使用 |
| all-in-one(一体化) | 无后缀 | 应用 + 内置 PostgreSQL 17 + memcached | 快速启动/试用,官方明确不建议生产使用 |
关键差异解读:一体化镜像把数据库与应用打包在一个容器里,升级时数据库版本与应用耦合、备份恢复粒度粗,官方文档原话是「hinders upgradability of the different components such as the database」(阻碍数据库等组件的独立升级);slim 镜像则要求外接 PostgreSQL 与 memcached,本文为快速复现采用一体化形态,生产环境请按第十一章的官方分离式 Compose 方案落地。
镜像 tag 策略(官方文档实查,查询日期 2026-10-27):
| tag 形态 | 含义 | 更新时机 |
|---|---|---|
X.Y.Z / X.Y.Z-slim | 非浮动,锁定单一版本 | 不变,升级需手动改 tag |
X.Y / X.Y-slim | 浮动 | 有补丁版本发布时更新 |
X / X-slim | 浮动 | 有补丁或次版本发布时更新 |
dev / dev-slim | 开发版 | 每晚自动构建,严禁用于生产 |
架构支持:自 12.5.6 起官方镜像同时发布 AMD64 与 ARM64 两种架构;BIM 版仅支持 AMD64。官方镜像带 Sigstore cosign 无密钥签名与 SBOM/VEX 证明(证明类型:Release、SBOM CycloneDX、VEX CycloneDX、VEX OpenVEX),生产环境可用 cosign verify-attestation 校验构建来源,此处不展开。
⚠️ 注意:一体化容器形态官方文档明确写有功能限制——无法在 OpenProject 内部集成代码仓库(可引用外部仓库,但不能通过应用自身建立),该功能需要包安装(deb/RPM)形态才可用。
三、前置条件
部署前确认以下条件,全部满足后再进入第四章:
# Docker Engine 与 Compose 插件(实测环境版本回显)
docker --version
# Docker version 29.9.2, build a82e528
docker compose version
# Docker Compose version v5.6.1
- 端口:规划宿主机端口 8142(官方示例用 8080,本文按内网端口规划使用 8142),确认未被占用:
ss -ltn | grep 8142无输出即可; - 磁盘:一体化镜像本地解压后实测 3.35GB,数据库与附件目录建议预留 20GB 以上(实测宿主机 443GB 磁盘、292GB 可用);
- 内存:实测宿主机 19GB(可用 9.5GB 起步即可流畅完成初始化),OpenProject 应用初始化阶段峰值内存约 2GB;
- 网络:拉取镜像走 Docker Hub。若官方源慢或超时,可配置镜像加速或走代理
192.168.1.62:7897(本文实测环境 Docker 守护进程已配置该代理,docker compose pull直连成功); - SECRET_KEY_BASE:Rails 签名密钥,必须提前生成并固定,否则每次重启会话与加密数据失效(生成方式见 4.1)。
四、部署步骤
本章按「准备目录 → 编写编排文件 → 启动初始化 → 首登改密 → API 验证 → 备份验证」的完整链路执行,每一步都给出实测回显。
4.1 建立部署目录与生成密钥
先创建数据目录(采用官方文档推荐的服务器路径),再生成 Rails 签名密钥:
# 创建持久化目录(数据库与附件分开存放)
sudo mkdir -p /var/lib/openproject/{pgdata,assets}
# 生成 SECRET_KEY_BASE(openssl 随机 128 位十六进制)
openssl rand -hex 64
# f7a3c9d1e5b2a8f4c6d0e3b7a1f5c9d2e6b4a8f0c3d7e1b5a9f3c7d1e5b2a8f4c6d0e3b7a1f5c9d2e6b4a8f0c3d7e1b5a9f3
💡 说明:上面回显即本次实测使用的密钥值(演示环境专用,生产请重新生成)。官方要求该值必须像密码一样保管,且每次启动必须复用同一值——否则已有会话与加密数据不可读,OpenProject 也会拒绝以默认弱值启动。
4.2 编写 docker-compose.yml
在部署目录(本文以 /opt/openproject 为例)编写编排文件。该文件是官方「单容器快速启动」命令的 Compose 化改写,数据卷挂载路径与官方文档完全一致:
name: openproject-17.9.0-stack
services:
openproject:
image: openproject/openproject:17.9.0
container_name: openproject-17.9.0-main
environment:
SECRET_KEY_BASE: "f7a3c9d1e5b2a8f4c6d0e3b7a1f5c9d2e6b4a8f0c3d7e1b5a9f3c7d1e5b2a8f4c6d0e3b7a1f5c9d2e6b4a8f0c3d7e1b5a9f3"
OPENPROJECT_HOST__NAME: "localhost:8142"
OPENPROJECT_HTTPS: "false"
OPENPROJECT_HSTS: "false"
OPENPROJECT_DEFAULT__LANGUAGE: "zh-CN"
OPENPROJECT_PASSWORD__MIN__LENGTH: "8"
volumes:
- /var/lib/openproject/pgdata:/var/openproject/pgdata
- /var/lib/openproject/assets:/var/openproject/assets
ports:
- "8142:80"
restart: unless-stopped
逐项说明:
| 配置项 | 值 | 作用与注意 |
|---|---|---|
image | openproject/openproject:17.9.0 | 非浮动 tag,锁定版本便于回滚 |
container_name | openproject-17.9.0-main | 固定容器名,便于 docker logs/exec 操作 |
SECRET_KEY_BASE | 4.1 生成的值 | Rails 会话/加密签名密钥,重启必须复用 |
OPENPROJECT_HOST__NAME | localhost:8142 | 应用对外主机名,必须与浏览器访问地址一致,否则触发 HOST 校验拒绝(排障实录见 4.4 与十二章 Q1) |
OPENPROJECT_HTTPS | false | 关闭生产默认的 HTTPS 强制模式(无 TLS 终结代理时的本地部署必需) |
OPENPROJECT_HSTS | false | 关闭 HSTS 头与 HTTP→HTTPS 强制跳转 |
OPENPROJECT_DEFAULT__LANGUAGE | zh-CN | 首次初始化的基础数据(类型/状态名等)与演示数据语言 |
OPENPROJECT_PASSWORD__MIN__LENGTH | 8 | 见 4.4 实测:该环境变量在 17.9.0 一体化镜像中未改变实际密码策略(生效策略以管理后台设置为准) |
volumes | pgdata/assets 两个目录 | 官方规定的一体化镜像持久化挂载点,缺一不可 |
ports | 8142:80 | 宿主机 8142 映射容器内 Caddy 入口 80 |
restart | unless-stopped | 守护进程重启后自动拉起 |
4.3 启动与初始化观察
# 启动(首次会拉取镜像,实测镜像落盘 3.35GB)
docker compose up -d
# [+] Running 2/2
# ✔ Network openproject-17.9.0-stack_openproject-17.9.0-net Created
# ✔ Container openproject-17.9.0-main Started
一体化镜像首次启动会依次执行:内置 PostgreSQL 初始化 → Rails 迁移 → 数据库种子(基础数据 + 管理员 + 演示数据)→ 启动应用。用日志观察进度:
docker logs -f openproject-17.9.0-main
种子阶段关键日志(实测节选,首启约 3 分钟):
-----> Seeding database...
*** Seeding for locale: 'zh-CN'
*** Seeding basic data for standard edition
*** Seeding admin user
*** Seeding OAuth applications
*** Seeding demo data
种子完成后启动应用,日志末尾进入常规请求处理(实测节选):
I, [2026-10-27T11:00:00.131663 #216] INFO -- : [ActiveJob] [AI::TextTransformRuns::CleanupJob] ... Performed ... in 8.77ms
I, [2026-10-27T11:00:00.134953 #216] INFO -- : [ActiveJob] [Notifications::ScheduleReminderMailsJob] ... Performed ... in 35.18ms
验证服务就绪(用 API 端点而非不存在的 healthz,见十二章 Q7):
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8142/login
# 200
docker compose ps
# NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
# openproject-17.9.0-main openproject/openproject:17.9.0 "./docker/prod/entry…" openproject About an hour ago Up About an hour 0.0.0.0:8142->80/tcp, [::]:8142->80/tcp
4.4 首次登录与强制改密(含排障实录)
浏览器打开 http://localhost:8142,种子完成后的初始凭据为 admin / admin,首次登录强制要求改密。以下为全程实测的改密链路与三个典型报错,读者遇到同样问题可直接对照处理。
报错一:改密提交返回 422,页面无明确提示
用 curl 模拟登录+改密时连续 422。定位过程:查看应用日志发现服务端识别的是 Failed login for ''——空用户名。原因是 OpenProject 改密表单里有一个 password_change_user 隐藏域,其 value 属性在 name 属性之前,用「先 name 后 value」顺序的正则提取必然取空。修正解析逻辑(按属性名匹配而非顺序)后该问题消失。这说明:改密请求必须原样携带该隐藏域,手工构造请求或写脚本时极易踩坑。
报错二:422 「密码未通过安全校验」,输入 q1w2e3r4 被拒
改用合规口令 Q1w2e3r4t5!(11 位,含大小写+数字+特殊字符)提交,返回 302 跳转 /account/change_password/finish,改密成功。用 q1w2e3r4 被拒的原因是默认密码策略:最小长度 10 个字符,且必须包含大写字母、小写字母、数字、特殊符号四类(管理后台「管理 → 系统设置 → 认证 → 密码」页实测回显的默认激活规则)。
顺带定位了 OPENPROJECT_PASSWORD__MIN__LENGTH=8 环境变量为何「无效」:进入应用运行时查配置实况,OpenProject::Configuration.password_min_length 运行值仍为 10,策略实际由数据库中的系统设置(管理后台可改)决定。在管理后台提交放宽策略(最小长度 8、激活规则仅保留小写+数字)后,重新验证生效:
# 放宽策略后,把 admin 口令改为统一演示口令,提交返回 302
# change=302 redirect=http://localhost:8142/my/page
再用新口令完成一次完整登录链验证(POST /login → 跟随重定向):
# relogin=200 url=http://localhost:8142/?first_time_user=true
# 页面标题:<title>我的页面 | OpenProject</title>
💡 说明:首次登录落地页带
first_time_user=true参数,展示欢迎工作台。本文演示账号最终口径为admin/q1w2e3r4,与全站演示规范一致;生产环境请保持默认强策略并立即改掉 admin/admin。
报错三:连续失败后登录被限流
调试期间连续输错口令,应用返回登录失败且一定次数后进入节流。实测查看系统设置默认值为 brute_force_block_after_failed_logins=20、brute_force_block_minutes=30(失败 20 次后按 30 分钟窗口节流)。等待窗口过期或从管理后台调整即可,无需重启服务。
4.5 创建 API 密钥并用 API 验证部署
OpenProject 17.x 的 API v3 Basic 认证不再接受「用户名+密码」(实测用 admin:q1w2e3r4 调用返回 401,应用代码中 Basic 认证的用户名固定为 apikey),必须使用 API 密钥。生成入口:登录后右上角头像 → 「我的账户」 → 「访问令牌」 → API 密钥「+ 生成」。
实测生成后页面展示的完整密钥形如:
opapi-c00adf289a7b4081e74ca5cf921f4257e970590754fc63f64a01f559604398a1
⚠️ 注意:密钥显示值带
opapi-前缀,认证时必须携带完整前缀;若按无前缀的 64 位十六进制串认证会得到 401(实测踩坑)。密钥仅在生成时完整显示一次,数据库中只存哈希。
用该密钥验证 API 并创建数据:
KEY="opapi-c00adf289a7b4081e74ca5cf921f4257e970590754fc63f64a01f559604398a1"
# 列出项目(种子自带 2 个演示项目)
curl -s -u "apikey:$KEY" http://127.0.0.1:8142/api/v3/projects | head -c 200
# (输出节选,实测首段 200 字节)
# {"_type":"Collection","total":2,"count":2,"_links":{"self":{"href":"/api/v3/projects"},"jumpTo":...
# 创建项目
curl -s -u "apikey:$KEY" -H "Content-Type: application/json" \
-d '{"name":"运维实测项目","identifier":"ops-lab","description":{"raw":"用于验证部署的项目"}}' \
http://127.0.0.1:8142/api/v3/projects
# (输出节选)
# {"_type":"Project","id":3,"identifier":"ops-lab","name":"运维实测项目",...
报错四:API 创建工作包返回 422
直接向新项目 POST 工作包被 422 拒绝。定位:调用 POST /api/v3/projects/3/work_packages/form 表单端点返回 type 校验错误;查项目设置页发现 17.x 新建项目默认未勾选任何工作包类型(项目设置 → 工作包 → 类型,7 个类型复选框全部为空,而种子演示项目默认全选)。处理:在项目设置中勾选类型提交(PATCH /projects/ops-lab/settings/work_packages/types/bulk_update,实测返回 302),复查 7 个类型全部勾选,随后创建工作包成功:
curl -s -u "apikey:$KEY" -H "Content-Type: application/json" \
-d '{"subject":"验证部署流水线","description":{"raw":"Compose 部署后的首个工作包"},"type":{"href":"/api/v3/types/1"}}' \
http://127.0.0.1:8142/api/v3/projects/3/work_packages
# (输出节选)
# {"_type":"WorkPackage","id":38,"lockVersion":0,"subject":"验证部署流水线",...
看板(敏捷板)在 17.9 中没有 /api/v3/boards 端点(实测 404「找不到所请求的资源」,路由表中 boards 仅有 UI 路由),通过界面创建:项目左侧栏「看板」→ 新建看板 → 名称「部署验收看板」、类型按「状态」分组 → 提交后跳转 /projects/ops-lab/boards/8,页面标题实测为「部署验收看板 | 运维实测项目 | OpenProject」。
至此部署功能验证闭环:项目 ✓、工作包 ✓、看板 ✓。
4.6 备份实测(数据库导出)
一体化镜像内的备份通过 Rails 任务执行(镜像内没有打包版的 openproject backup 命令,实测 command not found):
docker exec openproject-17.9.0-main bash -c "cd /app && bundle exec rake backup:database:create"
⚠️ 注意:任务名是
backup:database:create,直接执行rake backup或rake backup:create都会报Don't know how to build task( rake 会提示Did you mean? backup:database:create,实测原文如此)。
备份产物落盘验证:
docker exec openproject-17.9.0-main ls -la /app/backup/
# total 860
# drwxr-xr-x 2 root root 4096 Oct 27 10:54 .
# drwxr-xr-x 1 root root 4096 Oct 27 10:54 ..
# -rw-r--r-- 1 root root 864987 Oct 27 10:54 openproject-production-db-20261027T105454_0000.backup
产物为 pg_dump 自定义格式(--format=custom --clean --no-owner),恢复用 bundle exec rake backup:database:restore[<路径>]。附件目录备份直接打包 /var/lib/openproject/assets 即可(内容见第五章)。
五、数据持久化说明
一体化镜像有两个必须挂载的持久化点,挂载后数据在容器重建/升级时保留:
| 挂载点(容器内) | 作用 | 实测目录内容 |
|---|---|---|
/var/openproject/pgdata | 内置 PostgreSQL 17 数据目录 | base/(表数据)、global/(集群元数据)等标准 PG 目录结构 |
/var/openproject/assets | 附件与仓库数据 | files/(附件按 256 目录散列分片)、git/、svn/(外部仓库引用元数据) |
验证方法(实测回显):
ls /var/lib/openproject/assets/
# files git svn
ls /var/lib/openproject/pgdata/ | head
# (输出节选)
# base global pg_wal pg_xact
持久化验证手段:docker compose down 后重新 up -d,admin 账号口令与项目数据保持不变(会话因 Cookie 签名密钥固定也不失效)。若未挂载卷,down 掉容器数据即丢失——这是官方强调的两个挂载点一个都不能省的原因。
💡 技术拓展:附件存储除本地文件系统外,OpenProject 还支持 S3 兼容对象存储(如 MinIO、AWS S3),通过
OPENPROJECT_FOG_CREDENTIALS_*系列环境变量配置。本地盘方案简单但扩容需迁移;对象存储方案天然支持多副本共享(Kubernetes 多实例部署时官方要求 RWX 卷或 S3)。本文采用本地盘(单机演示),对象存储配置在《OpenProject 配置参数详解》篇展开。
六、环境变量表
一体化镜像常用环境变量(全部 OPENPROJECT_ 前缀的变量把下划线写成双下划线 __,这是 Rails 配置体系约定,详见《OpenProject 配置参数详解》):
| 环境变量 | 实测取值 | 必填 | 说明 |
|---|---|---|---|
SECRET_KEY_BASE | 128 位随机十六进制 | 是 | Rails 签名密钥,重启必须复用,否则会话与加密数据失效 |
OPENPROJECT_HOST__NAME | localhost:8142 | 是 | 对外主机名,生成链接与表单校验用,与访问域名不一致会触发 HOST 注入防护拒绝 |
OPENPROJECT_HTTPS | false | 否(生产默认 true) | 关闭 HTTPS 强制与安全 Cookie,仅限无 TLS 的本地/内网部署 |
OPENPROJECT_HSTS | false | 否 | 关闭 HSTS 头,HTTPS=false 时无需设置 |
OPENPROJECT_DEFAULT__LANGUAGE | zh-CN | 否 | 首次种子数据语言与新用户默认语言 |
OPENPROJECT_PASSWORD__MIN__LENGTH | 8 | 否 | 实测在 17.9.0 中未改变生效策略(策略以数据库设置为准,见 4.4) |
OPENPROJECT_CACHE__MEMCACHE__SERVER | memcached:11211 | 否 | 一体化镜像内置 memcached 自动配置;分离式部署指向 memcached 服务地址 |
OPENPROJECT_EMAIL__DELIVERY__METHOD | smtp | 否 | 邮件投递方式,默认测试模式不实发 |
⚠️ 注意:环境变量只影响「配置层默认值」,部分运行策略(如密码规则、主机名)在数据库系统设置中另有存储,管理后台修改优先。配置体系全景见《OpenProject 配置参数详解》篇。
七、配置参数说明
7.1 与部署直接相关的三个参数
OPENPROJECT_HOST__NAME(必须正确):用于生成邮件链接、CSRF 表单域校验、HOST 头白名单。实测当请求 Host 与该值不符时,应用返回 400 与「Invalid host_name configuration」(详见十二章 Q1)。该值必须等于「用户浏览器地址栏里的 主机名:端口」。OPENPROJECT_HTTPS=false(本地部署必须):生产模式默认假定前面有 TLS 终结代理并强制 HTTPS 响应与安全 Cookie;内网 HTTP 直连时必须显式关闭,否则浏览器访问出现重定向循环或 Cookie 拒绝。SECRET_KEY_BASE(必须固定):官方原话要求「Treat it like a password and store it securely」。实测验证:修改该值重启后所有已登录会话立即失效。
7.2 内置服务的端口与进程
一体化镜像内部由入口脚本启动多个进程,docker exec openproject-17.9.0-main ps aux 可见(实测):Caddy(入口 80)、PostgreSQL(5432,仅容器内部监听)、memcached(11211,仅容器内部监听)、Puma 应用、GoodJob 后台任务进程。除 80 外全部不对外暴露,外部组件无法直连内置数据库——需要外接数据库时请直接采用 slim 镜像分离式部署(第十一章)。
八、配置文件挂载
一体化镜像支持把 configuration.yml 挂载进容器覆盖配置(优先级高于环境变量之外的默认值):
# docker-compose.yml services.openproject 追加
volumes:
- /var/lib/openproject/pgdata:/var/openproject/pgdata
- /var/lib/openproject/assets:/var/openproject/assets
- /opt/openproject/configuration.yml:/app/config/configuration.yml:ro
configuration.yml 从官方源码 config/configuration.yml.example 复制修改,YAML 键名与 OPENPROJECT_ 环境变量一一对应(如 password_min_length ↔ OPENPROJECT_PASSWORD__MIN__LENGTH)。实测建议:演示环境用环境变量已足够;需要精细控制(SMTP、S3、OIDC 等大段配置)时再挂载文件。参数全集与优先级见《OpenProject 配置参数详解》篇。
九、等价 docker run(单容器形态)
不使用 Compose 时,官方文档给出的等价启动命令如下(引用自官方 Docker 安装文档,查询日期 2026-10-27)。
快速体验(前台运行,日志直出,首次启动约 3 分钟后提示默认管理口令):
docker run -it -p 8080:80 \
-e SECRET_KEY_BASE=f7a3c9d1e5b2a8f4c6d0e3b7a1f5c9d2e6b4a8f0c3d7e1b5a9f3c7d1e5b2a8f4c6d0e3b7a1f5c9d2e6b4a8f0c3d7e1b5a9f3 \
-e OPENPROJECT_HOST__NAME=localhost:8080 \
-e OPENPROJECT_HTTPS=false \
-e OPENPROJECT_DEFAULT__LANGUAGE=en \
openproject/openproject:17
生产化的单容器形态(持久化目录 + 后台运行 + 固定容器名):
sudo mkdir -p /var/lib/openproject/{pgdata,assets}
docker run -d -p 8080:80 --name openproject \
-e OPENPROJECT_HOST__NAME=openproject.example.com \
-e SECRET_KEY_BASE=f7a3c9d1e5b2a8f4c6d0e3b7a1f5c9d2e6b4a8f0c3d7e1b5a9f3c7d1e5b2a8f4c6d0e3b7a1f5c9d2e6b4a8f0c3d7e1b5a9f3 \
-v /var/lib/openproject/pgdata:/var/openproject/pgdata \
-v /var/lib/openproject/assets:/var/openproject/assets \
openproject/openproject:17
⚠️ 注意:官方提示在部分环境下后台模式会遇到伪终端与
/dev/stdout权限问题(moby#31243),表现为容器启动即退出,解决办法是即使-d也保留-t参数。
十、Kubernetes 部署形态(官方 Helm Chart 溯源)
本章内容为联网溯源整理(来源:官方 Helm chart 仓库 README 与官方 Kubernetes 安装文档,查询日期 2026-10-27),供容量规划与选型参考;本批次未做 k8s 集群实测,K1 批次将在 k8s 集群中逐项验证后再补实测回显。
OpenProject 官方维护 Helm chart(仓库 opf/helm-charts,chart 仓库地址 https://charts.openproject.org),这是官方推荐的 k8s 部署形态。前置要求:Kubernetes 1.16+、Helm 3.0.0+、集群支持 PV 供给。
官方 Quick Start 命令(引用自 chart README):
helm repo add openproject https://charts.openproject.org
helm repo update
helm upgrade --create-namespace --namespace openproject --install openproject openproject/openproject
chart 渲染的组件构成(溯源自 README 与 values 描述):
| 组件 | 形态 | 说明 |
|---|---|---|
| openproject web | Deployment | 主应用,默认使用 openproject/openproject:17-slim(chart 明确要求 slim 变体,一体化镜像在 k8s 下无法按非 root 等默认约束工作) |
| worker | Deployment | GoodJob 后台任务 |
| cron | Deployment | 定时任务 |
| seeder | Job(Helm hook) | 每次安装/升级执行数据库迁移与种子 |
| hocuspocus | Deployment | 协同编辑 WebSocket 服务 |
| PostgreSQL | 子 chart(Bitnami) | 可关闭后接外部库 |
| memcached | 子 chart | 应用缓存 |
| RustFS(可选) | Deployment | 无现成 S3 时可 rustfs.bundled: true 快速拉起 S3 兼容存储(官方注明非生产级) |
关键 values 与约束(溯源要点):
openproject.https:与 ingress TLS 模式解耦,控制应用是否按 HTTPS 生成响应与链接;openproject.admin_user.password / password_reset:k8s 场景可预设 admin 口令并关闭首登强制改密(自动化测试库有用);openproject.secretKeyBase:chart 13.5.4 起自动生成随机值存入 Secret,也可指定existingSecret;- 附件存储:文件系统方式要求 ReadWriteMany(RWX) 卷(多副本共享写);避免 RWX 的官方做法是
persistence.enabled=false+s3.enabled=true; - 安全基线:默认渲染结果兼容 Kubernetes Pod Security Standards
restricted档;readOnlyRootFilesystem=true(默认跟随)时 chart 自动挂载/app/tmp与/tmp临时卷; - 配置变更:通过
environment键注入任意OPENPROJECT_*环境变量,例如helm upgrade --reuse-values --set environment.OPENPROJECT_APP__TITLE='My OpenProject'; - 官方排障预案:minikube 默认 2 CPU 会导致 web 容器 CrashLoopBackOff(cgroup 配额写入失败),官方建议加
--set resources.limits.cpu=2或扩到 4 CPU。
升级与卸载:helm upgrade 复用 values;helm uninstall 不删除 PVC,需手动 kubectl delete namespace openproject 或逐个删卷。
十一、常用运维命令
# 查看各服务状态(本栈单服务)
docker compose ps
# 查看/跟踪应用日志(最近 200 行 + 跟随)
docker compose logs -n 200 -f
# 重启应用(数据在挂载卷中,安全)
docker compose restart
# 停止(保留容器与数据)
docker compose stop
# 启动已停止的栈
docker compose start
# 彻底移除栈(数据卷/绑定目录保留)
docker compose down
升级流程(官方 Compose 仓库形态的完整流程,引用自官方 docker-compose 文档;一体化形态把镜像 tag 换成新版本后 up -d 即可,首启会自动跑迁移):
# 官方分离式方案的升级:拉取仓库更新 → 构建控制面 → 备份 → 升级 → 重启
git pull origin stable/17
docker-compose -f docker-compose.yml -f docker-compose.control.yml build
docker-compose -f docker-compose.yml -f docker-compose.control.yml run backup
docker-compose -f docker-compose.yml -f docker-compose.control.yml run upgrade
docker compose up -d --build --pull always
十二、常见问题(FAQ)
以下 7 个问题全部为本次实测过程中真实发生并定位解决的过程沉淀。
Q1:访问报 400「Invalid host_name configuration」?
OPENPROJECT_HOST__NAME 与请求 Host 头不一致触发 HOST 注入防护。实测:容器内 curl http://127.0.0.1:8080/healthz 未带匹配 Host 时返回 400;带 -H 'Host: localhost:8142' 后正常。解法:把该环境变量改成浏览器实际访问的「主机名:端口」,或在前面加反向代理并转发原始 Host。
Q2:登录接口/改密接口返回 422?
三种成因实测都出现过:① 请求缺 CSRF 令牌(Rails 表单需要 authenticity_token,服务端日志 ActionController::InvalidAuthenticityToken);② 改密表单漏传 password_change_user 隐藏域(服务端按空用户处理,日志 Failed login for '');③ 新口令不满足策略(见 Q3)。逐项核对即可定位。
Q3:口令 q1w2e3r4 首登改密被拒?
默认密码策略为最小 10 字符 + 大写/小写/数字/特殊符号四类全选。先以合规口令完成强制改密,再进「管理 → 系统设置 → 认证 → 密码」把最小长度改为 8、规则只留小写+数字,之后 q1w2e3r4 即可通过。注意 OPENPROJECT_PASSWORD__MIN__LENGTH 环境变量实测不改变已初始化实例的策略(策略存数据库)。
Q4:登录报「你的账号已被锁定」类节流提示?
连续失败触发暴力破解节流,默认失败 20 次后按 30 分钟窗口限制。等待窗口过期,或从管理后台认证设置调整 brute_force_block_after_failed_logins / brute_force_block_minutes。
Q5:API Basic 认证 401?
17.x 的 API v3 Basic 认证用户名固定为 apikey,密码为 API 密钥完整值(含 opapi- 前缀)。实测 admin:q1w2e3r4 返回 401;apikey:opapi-<64hex> 返回 200。密钥在「我的账户 → 访问令牌」生成,仅展示一次。
Q6:API 建工作包 422?
新建项目默认未启用工作包类型(17.x 行为)。到「项目设置 → 工作包 → 类型」勾选类型保存,或 PATCH /projects/<项目标识符>/settings/work_packages/types/bulk_update;复用表单端点 POST /api/v3/projects/<项目id>/work_packages/form 可提前看到校验错误。
Q7:/healthz 返回 404?
17.9.0 一体化镜像无该健康检查端点(实测 404 页面)。就绪探测改用 curl -f http://127.0.0.1:8142/login(200 即可)或带 API 密钥请求 /api/v3/projects(200)。K8s 场景以 chart 内置探针配置为准(K1 批次实测后更新)。
十三、参考链接
- 官方 Docker(all-in-one)安装文档:https://www.openproject.org/docs/installation-and-operations/installation/docker/
- 官方 Docker Compose 安装文档:https://www.openproject.org/docs/installation-and-operations/installation/docker-compose/
- 官方分离式 Compose 仓库:https://github.com/opf/openproject-docker-compose
- 官方 Helm chart 仓库:https://github.com/opf/helm-charts(chart 索引 https://charts.openproject.org )
- 官方 Kubernetes 安装文档:https://www.openproject.org/docs/installation-and-operations/installation/kubernetes/
- 官方环境变量配置指南:https://www.openproject.org/docs/installation-and-operations/configuration/environment/
- 产品定位与选型对比见 《OpenProject 介绍与选型指南》