hitobito 2.10 Docker Compose 部署实战
用官方 hitobito/development 工具箱以 Docker Compose 从零部署 hitobito 2.10.0 全栈,含 assets 崩溃循环定位修复与端到端验证。
hitobito 2.10 Docker Compose 部署实战
目标:用官方
hitobito/development工具箱以 Docker Compose 形态从零部署一套可用的 hitobito 2.10.0 演示实例(含 PostgreSQL、Redis、后台作业、邮件捕获与前端编译服务),并完整记录 assets 服务崩溃循环的定位与修复。本文全部命令与回显在 Ubuntu 22.04 服务器(Docker 29.9.2 / Compose v5.6.1,项目目录/opt/hitobito/generic)实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04 LTS(x86_64)
- Docker Engine:29.9.2(
docker --version实测) - Docker Compose:v5.6.1(
docker compose version实测) - 应用版本:hitobito core 2.10.0(release tag
2.10.0,提交28d23c0) - 工具箱:
hitobito/developmentmain 分支(提交596a223) - 演示 wagon:
hitobito_generic(提交e546fc0) - 组件版本:Ruby 3.3.13、Rails 8.0.5、PostgreSQL 16、Redis 8-alpine、Node v22.23.4、yarn 1.22.22、webpacker 5.4.3
- 宿主端口分配(全部仅绑定 127.0.0.1):
| 服务 | 容器 | 宿主端口 | 容器端口 |
|---|---|---|---|
| Web 应用 | generic-rails-1 | 8152 | 3000 |
| PostgreSQL | generic-postgres-1 | 8154 | 5432 |
| Redis | generic-redis-1 | 8155 | 6379 |
| Mailcatcher Web UI | generic-mailcatcher-1 | 8156 | 1080 |
| SMTP(仅内网) | generic-mailcatcher-1 | — | 1025 |
| 作业处理 | generic-worker-1 | — | — |
| 前端编译 | generic-assets_js-1 / generic-assets_css-1 | — | — |
- 演示账号:
admin@hitobito.ch/q1w2e3r4(密码策略放宽说明见"常见问题"11.3)
二、镜像选型说明
hitobito 没有可直接拉取的生产镜像。官方分发两条路径:
- 开发/演示工具箱:
hitobito/development仓库提供 compose 编排与ghcr.io/hitobito/development/rails、ghcr.io/hitobito/development/mailcatcher两个镜像,本文采用此形态; - 生产部署:官方运维文档建议 Apache + Passenger 裸机部署,仓库另附生产 Dockerfile(两阶段构建:
ruby:3.2-trixie构建层 +ruby:3.2-slim-trixie运行层,CMD ["bundle", "exec", "puma"],以Wagonfile.production打入vendor/wagons/的 wagon)。
本次实测拉取的镜像与摘要(pull 日志原文节选):
ghcr.io/hitobito/development/rails:latest
Digest: sha256:2d91293ab13a60e473f0017ac8acf5d8fd5e0db0e77773ffeaaf607437b105db
ghcr.io/hitobito/development/mailcatcher:latest
Digest: sha256:4eca5d7b0a6c0fa189a767c2ef5cb207d9f2bdc34aa4480868dd375c4013e156
docker.io/library/postgres:16
Digest: sha256:5db6dac2ef5dcff512f4f00a009c3c332b9f2f28f24277175bf3e4a3d02ef83a
docker.io/library/redis:8-alpine
Digest: sha256:21090af93f8324db2f0f0f4f38a7c60369d47d917e000c3423d89d2ae2883537
💡 技术拓展:像 hitobito 这类 Rails 组织应用的常见部署形态对比——
形态 载体 优点 代价 官方工具箱 compose ghcr 开发镜像 + bind mount 源码 起全栈最快、自带种子数据 面向开发,非生产加固 自构建生产镜像 仓库 Dockerfile 两阶段构建 源码打包进镜像、可上 K8s/OpenShift 需自建 CI,资产预编译 裸机 Apache+Passenger 官方运维文档推荐 官方生产主流、进程模型成熟 依赖 Ruby/DB/Memcached 手工运维 本文选第一种做"可复现实验环境";上生产前应按第二种或第三种重建(差异见 11.5)。
三、前置条件
- Docker 与 compose 插件已安装(见"一"实测版本),当前用户在 docker 组:
$ docker --version
Docker version 29.9.2, build a82e528
$ docker compose version
Docker Compose version v5.6.1
- git 可用;宿主机用户 UID 若非 1000 需导出
RAILS_UID(compose 以user: "${RAILS_UID:-1000}"运行 rails)。 - 磁盘与内存:本实例宿主 15 GiB 内存、镜像+依赖卷占用数 GB,建议预留 ≥20 GB。
- 网络:
ghcr.io、github.com、RubyGems/npm 源均为境外资源。本次实测直连超时后改走代理192.168.1.72:7897(在 compose 的environment中为 rails/assets 服务注入http_proxy/https_proxy,并以no_proxy=localhost,127.0.0.1,postgres,redis,mailcatcher排除内网互访)。无代理环境请确保能访问上述源。 - 创建外部依赖卷(gem 与 yarn 缓存,官方 README 要求,只建一次):
$ docker volume create hitobito_bundle
hitobito_bundle
$ docker volume create hitobito_yarn_cache
hitobito_yarn_cache
四、部署步骤
4.1 获取代码(工具箱 + core + wagon 三仓库)
hitobito 实例 = 工具箱 development + 核心 hitobito + 组织 wagon 三个仓库并排放入同一目录。以演示组织 generic 为例:
$ mkdir -p /opt/hitobito && cd /opt/hitobito
$ git clone https://github.com/hitobito/development.git generic
$ cd generic
$ git clone https://github.com/hitobito/hitobito.git
$ git clone https://github.com/hitobito/hitobito_generic.git
工具箱 README 说明:部分 wagon 依赖其他 wagon,需按 hitobito_generic/*.gemspec 中 add_dependency 声明补克隆;generic 演示组织无额外依赖。
把 core 固定在 2.10.0 发布点(默认克隆的是 master,混用会导致 11.1 的 assets 任务错位问题):
$ git -C hitobito checkout 2.10.0
$ cat hitobito/VERSION
2.10.0
4.2 调整 compose 编排
工具箱自带 docker-compose.yml,本次实测做了三处调整(均为合理生产化/环境适配,非上游缺陷):
- 端口避让与回环绑定:默认
3000/5432/6379/1080改绑127.0.0.1的 8152/8154/8155/8156,避免与宿主其他服务冲突; - 代理注入:见"三"第 4 条;
- rails 健康检查:
curl --fail http://localhost:3000/healthz,start_period: 30m(首次启动要装依赖、建库、灌种子,官方 README 明示"可能耗时很长")。
调整后的 rails 服务关键段(实测生效文件节选):
services:
rails:
image: ghcr.io/hitobito/development/rails
restart: unless-stopped
user: "${RAILS_UID:-1000}"
env_file: docker/rails/env
environment:
BUNDLE_GEMFILE: Gemfile.local
http_proxy: http://192.168.1.72:7897
https_proxy: http://192.168.1.72:7897
no_proxy: localhost,127.0.0.1,postgres,redis,mailcatcher
ports:
- "127.0.0.1:8152:3000"
volumes:
- .:/usr/src/app
- hitobito_bundle:/opt/bundle
- seed:/seed
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:3000/healthz"]
interval: 500s
timeout: 5s
start_period: 30m
depends_on:
postgres:
condition: service_healthy
mailcatcher:
condition: service_healthy
worker 服务复用 rails 定义,仅把命令换成作业处理器并跳过重复初始化:
worker:
<<: *rails
command: [ 'rails', 'jobs:work' ]
ports: []
environment:
SKIP_RAILS_MIGRATIONS: 1
SKIP_SEEDS: 1
SKIP_BUNDLE_INSTALL: 1
4.3 启动与等待就绪
$ docker compose up -d
首次启动 rails 入口脚本依次执行 bundle install、db:migrate、种子加载(以 /seed/done-* 标记幂等,重启不会重灌)。就绪判定用健康端点实测:
$ curl -s -i http://localhost:8152/healthz | head -3
HTTP/1.1 200 OK
x-frame-options: SAMEORIGIN
x-xss-protection: 0
⚠️ 注意:
assets_js/assets_css两个服务在 2.10.0 代码 + 最新工具箱镜像组合下会崩溃循环,导致 rails 的depends_on条件长期不满足。这是本部署形态最大的坑,定位与修复见 4.4 与 11.1,务必先按 4.4 处理再验证全栈。
4.4 修复 assets 服务崩溃循环(实测攻坚)
现象:generic-assets_js-1、generic-assets_css-1 反复重启,日志原文:
rake aborted!
Don't know how to build task 'assets:watch_js' ... Did you mean? assets:clean
定位:工具箱镜像的 assets-entrypoint.sh 末尾执行 exec bundle exec rake "assets:watch_$target",而 assets:watch_js/watch_css 任务只存在于 hitobito master 的新资产管线;检出的 2.10.0 release 仍是 webpacker 5(rake -T 实测只有 webpacker:* 系列任务)。镜像(新)与代码(release)版本错位。
手工编译验证 webpacker 管线(第一次失败,Node v22 + webpack 4 的 md4 哈希问题):
Error: error:0308010C:digital envelope routines::unsupported
at new Hash (node:internal/crypto/hash:101:19)
at Object.createHash (node:crypto:139:10)
library: 'digital envelope routines', reason: 'unsupported', code: 'ERR_OSSL_EVP_UNSUPPORTED'
Node.js v22.23.4
加 NODE_OPTIONS=--openssl-legacy-provider 后第二次编译成功(日志节选):
Compiling...
Compiled all packs in /usr/src/app/hitobito/public/packs
Hash: 9d5dd76a40b2826d4af0
Version: webpack 4.46.0
Time: 22506ms
Built at: 11/01/2026 3:37:57 PM
css/application-f460fb4e.css 340 KiB [emitted] [immutable] [big] application
js/application-f21f8ac8a63316cbbfe4.js 1.2 MiB
修复:把 assets_js 的入口换成 webpacker 周期编译循环,assets_css 改为等 manifest 出现后保活(webpacker 单次编译同时产出 JS+CSS,双 watcher 冗余)。实测生效的 compose 段:
assets_js:
image: ghcr.io/hitobito/development/rails
# FIX 2026-11-02: 上游 assets-entrypoint.sh exec 的 rake assets:watch_js
# 仅存在于 master;2.10.0 用 webpacker 5,无此任务 -> 崩溃循环。
entrypoint: [ "/bin/bash", "-lc" ]
command:
- |
cd /usr/src/app/hitobito
until bundle check >/dev/null 2>&1; do sleep 2; done
export NODE_OPTIONS=--openssl-legacy-provider
yarn install
while true; do bundle exec rake webpacker:compile; sleep 300; done
restart: unless-stopped
env_file: docker/rails/env
environment:
SKIP_BUNDLE_INSTALL: 1
BUNDLE_GEMFILE: Gemfile.local
user: "${RAILS_UID:-1000}"
volumes:
- .:/usr/src/app
- hitobito_bundle:/opt/bundle
- hitobito_yarn_cache:/home/developer/.cache/yarn
assets_css:
<<: *assets
command:
- |
cd /usr/src/app/hitobito
until bundle check >/dev/null 2>&1; do sleep 2; done
until [ -f public/packs/manifest.json ]; do sleep 5; done
echo "packs manifest present; css watcher not needed under webpacker 5"
while true; do sleep 3600; done
验证:docker compose up -d assets_js assets_css 后两服务 Up;js 日志实测首行 yarn install v1.22.22,随后 success Already up-to-date.、Done in 0.16s. 与 Everything's up-to-date. Nothing to do;css 日志实测 packs manifest present; css watcher not needed under webpacker 5;页面资源实测 GET /packs/css/application-f460fb4e.css 返回 200。
4.5 全栈状态与端到端验证
$ docker compose ps --format 'table {{.Name}}\t{{.Status}}\t{{.Ports}}'
NAME STATUS PORTS
generic-assets_css-1 Up 20 minutes
generic-assets_js-1 Up 20 minutes
generic-mailcatcher-1 Up 2 hours (healthy) 127.0.0.1:8156->1080/tcp
generic-postgres-1 Up 2 hours (healthy) 127.0.0.1:8154->5432/tcp
generic-rails-1 Up 13 minutes (healthy) 127.0.0.1:8152->3000/tcp
generic-redis-1 Up 2 hours 127.0.0.1:8155->6379/tcp
generic-worker-1 Up 2 hours
Web 入口实测(未登录 302 到登录页):
$ curl -s -I http://localhost:8152/ | grep -iE '^HTTP|^location'
HTTP/1.1 302 Found
location: http://localhost:8152/de/users/sign_in
登录验证(表单字段 person[login_identity]/person[password] + CSRF 令牌):
$ curl -s -b cookies.txt -c cookies.txt -i -X POST http://localhost:8152/de/users/sign_in \
--data-urlencode "authenticity_token=$TOKEN" \
--data-urlencode "person[login_identity]=admin@hitobito.ch" \
--data-urlencode "person[password]=q1w2e3r4" \
--data-urlencode "person[remember_me]=0" | head -1
HTTP/1.1 303 See Other
💡 说明:
$TOKEN为登录页authenticity_token隐藏域实测值(86 字符),提取方式:curl -s http://localhost:8152/de/users/sign_in | python3 -c 'import sys,re; print(re.search(r"name=\"authenticity_token\" value=\"([^\"]+)\"", sys.stdin.read()).group(1))'。用grep+cut截取得到的令牌不完整,登录会 422。
作业与邮件链路实测:worker 日志出现 [Worker(host:d24d2888c23f pid:1)] Job WalletSynchronizationJob (id=192) COMPLETED after 0.0068;触发"忘记密码"后 mailcatcher 实测收到 1 封,主题 Anleitung für das Setzen Deines Passworts。
💡 技术拓展:Rails 本地/测试环境的邮件捕获工具不止 Mailcatcher——
工具 形态 特点 Mailcatcher SMTP 服务 + Web UI(1080) 轻量,官方工具箱内置 MailHog SMTP + Web UI + API Go 单二进制,支持消息保留策略 Mailpit SMTP + Web UI + API MailHog 活跃替代,功能更多 letter_opener 进程内 免端口,仅开发机浏览器弹出 本方案采用官方 compose 内置的 Mailcatcher(
ghcr.io/hitobito/development/mailcatcher),SMTP 指向它即可。
五、数据持久化说明
docker compose down(不加 -v)后以下卷保留数据:
| 卷 | 挂载点 | 内容 | 类型 |
|---|---|---|---|
| generic_postgres | /var/lib/postgresql/data | 全部业务数据(111 张表) | 命名卷 |
| generic_seed | /seed | 种子完成标记 done-*、导入中转 | 命名卷 |
| generic_redis_data | /data | 缓存/Action Cable 状态 | 命名卷 |
| hitobito_bundle | /opt/bundle | Ruby gem 依赖 | 外部卷(预先创建,跨项目共享) |
| hitobito_yarn_cache | /home/developer/.cache/yarn | npm 依赖缓存 | 外部卷 |
bind mount . | /usr/src/app | 三仓库源码(改代码即生效) | 宿主目录 |
实测 docker volume ls 节选:
local generic_postgres
local generic_redis_data
local generic_seed
local hitobito_bundle
local hitobito_yarn_cache
⚠️ 注意:源码走 bind mount,
git checkout切版本后需docker compose restart rails worker才生效;数据库备份用docker compose exec postgres pg_dump -U hitobito hitobito_development > backup.sql(compose 内命令,实测可用)。
六、环境变量表
rails/worker/assets 通过 env_file: docker/rails/env 统一注入,本实例实测文件内容全量如下:
RAILS_DB_ADAPTER=postgresql
RAILS_DB_HOST=postgres
RAILS_DB_PORT=5432
RAILS_DB_USERNAME=hitobito
RAILS_DB_PASSWORD=hitobito
RAILS_DB_NAME=hitobito_development
RAILS_ACTIONCABLE_REDIS_URL=redis://hitobito_redis@redis:6379/1
RAILS_HOST_NAME=localhost:8152
RAILS_MAIL_DOMAIN=localhost
RAILS_MAIL_DELIVERY_METHOD=smtp
RAILS_MAIL_DELIVERY_CONFIG=address: mailcatcher, port: 1025
ANNOTATERB_SKIP_ON_DB_TASKS=1
RAILS_LOG_TO_STDOUT=1
IS_DOCKER_DEV_ENV=1
DISABLE_SPRING=true
配套服务侧:docker/postgres.env(POSTGRES_DB=hitobito_development、POSTGRES_USER=hitobito、POSTGRES_PASSWORD=hitobito)与 docker/redis.env(HITOBITO_REDIS_PASSWORD=hitobito_redis,redis 启动命令 redis-server --requirepass $HITOBITO_REDIS_PASSWORD 实测引用)。
compose 级变量汇总:
| 变量 | 实测值/来源 | 作用 |
|---|---|---|
| RAILS_UID | 默认 1000,${RAILS_UID:-1000} | rails/assets 运行用户 |
| SKIP_SEEDS / SKIP_RAILS_MIGRATIONS / SKIP_BUNDLE_INSTALL | worker=1 | 避免多服务重复初始化 |
| BUNDLE_GEMFILE | Gemfile.local | 防止本地 wagon 路径写入锁文件 |
| NODE_OPTIONS | --openssl-legacy-provider | Node 22 下 webpack 4 编译必需(见 4.4) |
| http_proxy/https_proxy/no_proxy | 192.168.1.72:7897 | 境外源访问(见"三") |
七、配置参数说明
compose 形态下需要动到的应用配置只有三处,实测说明如下:
- 数据库/邮件/主机名:全部走"六"的 env 文件,应用
config/database.yml等只读环境变量,无需改代码; - 登录密码策略:
hitobito/config/initializers/devise.rb实测默认config.password_length = 12..128(硬编码,非环境变量)。本实例为使用演示口令q1w2e3r4(8 位)临时改为8..128并docker compose restart rails(healthz 实测约 165 秒恢复);入口脚本幂等:migrate 空跑、seed 因done标记跳过。生产请保持默认下限; - wagon 选择:
hitobito/Wagonfile.development声明开发环境加载哪些 wagon,实测bundle exec rake wagon:list输出generic。
八、配置文件挂载
本 compose 实测挂载的配置相关文件:
| 宿主路径 | 容器路径 | 模式 | 用途 |
|---|---|---|---|
| docker/rails/env | (env_file 注入) | — | 应用环境变量 |
| docker/postgresql-setup.sql | /docker-entrypoint-initdb.d/ | ro | 首次建库脚本 |
| docker/test-setup-postgresql.sql | /docker-entrypoint-initdb.d/ | ro | 测试库准备 |
| docker/empty | /usr/src/app/dumps | ro | 空目录覆盖 dumps,防止误加载生产 dump |
| docker/rails/home/.bashrc 等 | /home/developer/ 下同名 | — | 开发体验定制 |
| /etc/localtime、/etc/timezone | 同名 | ro | 容器与宿主时区一致 |
九、等价 docker run
compose 的 rails 服务可拆解为如下等价单条命令(供理解编排与排障用;depends_on/healthcheck 语义需自行保证先后顺序):
$ docker run -d --name hitobito-rails \
--network generic_default \
-p 127.0.0.1:8152:3000 \
--env-file docker/rails/env \
-e BUNDLE_GEMFILE=Gemfile.local \
-v /opt/hitobito/generic:/usr/src/app \
-v hitobito_bundle:/opt/bundle \
-v generic_seed:/seed \
ghcr.io/hitobito/development/rails
postgres/redis/mailcatcher 同理分别用 postgres:16(--env-file docker/postgres.env)、redis:8-alpine(--requirepass)、ghcr.io/hitobito/development/mailcatcher 单条运行。注意:单条 docker run 形态失去了 compose 的 service_healthy 依赖门控,rails 必须等 postgres 就绪后再启动,否则会报连库失败。
十、常用运维命令
$ docker compose ps # 全栈状态(见 4.5 实测输出)
$ docker compose logs -f rails # 应用日志(RAILS_LOG_TO_STDOUT=1 直出)
$ docker compose logs worker | tail -2 # 作业日志实测节选:
[Worker(host:d24d2888c23f pid:1)] Job WalletSynchronizationJob (id=192) COMPLETED after 0.0068
1 jobs processed at 108.4782 j/s, 0 failed
$ docker compose restart rails # 改 initializer/代码后重启(healthz 实测 ~165s 恢复)
$ docker compose exec rails bundle exec rake hitobito:check_config # 配置自检,实测末行:
✅ SelfRegistrationRoleTypes configured correctly
$ docker compose exec rails bundle exec rails runner 'puts Person.count'
224
$ docker compose down # 停栈保留卷;加 -v 连卷删除(数据销毁)
十一、常见问题
11.1 assets 服务无限重启,rails 一直不健康
现象与修复见 4.4 攻坚实录。判定命令:docker compose ps 中 assets 服务无 healthy/长时间 Restarting;日志关键字 Don't know how to build task 'assets:watch_js'。根因是工具箱镜像按 master 新管线构建、代码检出 2.10.0 只有 webpacker 任务。短期修复用 4.4 的 webpacker 周期编译循环;长期方案是代码与工具箱镜像版本配套(master 代码 + 新镜像,或改用生产 Dockerfile 自构建)。
11.2 webpacker:compile 报 ERR_OSSL_EVP_UNSUPPORTED
Node 17+ 默认 OpenSSL 3 不再支持 webpack 4 的 md4 哈希。实测解决:编译前 export NODE_OPTIONS=--openssl-legacy-provider(4.4 已固化进 compose command)。成功标志实测为 Compiled all packs in /usr/src/app/hitobito/public/packs。
11.3 演示账号登录报"密码太短"或重置失败
hitobito 默认密码下限 12 位(devise.rb config.password_length = 12..128),直接设 8 位演示口令会实测抛 ActiveRecord::RecordInvalid: Gültigkeitsprüfung ist fehlgeschlagen: Passwort ist zu kurz (weniger als 12 Zeichen)。本实例已改 8..128 并重启 rails 后重置成功(实测回显 RESET_OK admin@hitobito.ch id=222 sign_in_count=0),随后登录实测 HTTP/1.1 303 See Other。
11.4 首次启动半小时仍不可用
正常现象:入口脚本要完成 bundle install、建库迁移与 generic 种子灌入,compose 因此把 rails 健康检查 start_period 设为 30m。观察 docker compose logs -f rails 直到出现 Puma Listening on http://0.0.0.0:3000,再用 curl http://localhost:8152/healthz 判定。
11.5 这套 compose 能直接上生产吗
不能。它是官方开发工具箱:源码 bind mount、种子数据、Mailcatcher 假 SMTP、无 SECRET_KEY_BASE 强校验。生产按官方运维文档(Apache + Passenger、真实 SMTP 与收信服务、Wagonfile.production)或生产 Dockerfile 自构建镜像部署;ENV 必填项(如 SECRET_KEY_BASE 128 位 hex,实测 bundle exec rails secret 可生成)参见配置篇同类实测。
十二、参考链接
- hitobito 核心仓库:https://github.com/hitobito/hitobito
- 官方开发工具箱(README 含本流程):https://github.com/hitobito/development
- 演示 wagon:https://github.com/hitobito/hitobito_generic
- 工具箱镜像:https://ghcr.io/hitobito/development/rails
- 官方运维部署文档:仓库内
hitobito/doc/operator/01_deployment.md - Node OpenSSL 兼容说明:https://github.com/webpack/webpack/issues/14532
- Docker Compose 文档:https://docs.docker.com/compose/