IHateMoney 7.2.1 Docker Compose 部署指南

Docker Compose 部署 IHateMoney 7.2.1 官方镜像:SQLite 单容器主栈与 ihatemoney+PostgreSQL 双容器栈两套编排实测,环境变量逐项表、数据持久化、down/up 升级运维与 $$ 转义等坑位实录。

IHateMoney财务记账自托管2026/10/23

IHateMoney 7.2.1 Docker Compose 部署指南

目标:用 Docker Compose 一键部署 IHateMoney 7.2.1(多人共享记账 Web 应用)官方镜像,覆盖镜像选型、SQLite 单容器主栈与 ihatemoney + PostgreSQL 双容器栈两套编排、环境变量逐项说明、数据持久化、升级与日常运维命令。已在 Docker Engine + Docker Compose v2 环境、ihatemoney/ihatemoney:7.2.1、postgres:16-alpine 镜像实测通过(含建项目、重启持久化、PG 建表验证)。

一、环境信息

  • 宿主系统:Linux x86_64(实验基线与《Ubuntu 22.04 安装 IHateMoney 7.2.1 指南》同源)
  • Docker Engine:已启用(docker compose version 为 v2 系列,支持 name: 项目名字段与 depends_on.condition: service_healthy)
  • IHateMoney 镜像:ihatemoney/ihatemoney:7.2.1(Docker Hub 官方镜像,与 GitHub Releases 7.2.1 同版本号;镜像内 Python 3.11.15、gunicorn 26.0.0,实查自镜像元数据与启动日志)
  • 数据库镜像:postgres:16-alpine(仅 PostgreSQL 栈需要;SQLite 主栈无需)
  • 端口分配:SQLite 主栈 Web 8130,PostgreSQL 栈 Web 8131(均映射到容器内 8000)
  • compose 项目目录:/opt/ihatemoney/compose/(下文 compose 文件、数据库 bind mount 均在此目录)
  • 部署形态:两套独立栈——ihatemoney-7.2.1-stack(SQLite)与 ihatemoney-7.2.1-pg-stack(PostgreSQL),各自显式项目名与独立网络,互不影响

二、镜像选型说明

候选镜像说明结论
ihatemoney/ihatemoney:7.2.1Docker Hub 官方镜像(仓库名与项目同名,官方文档指名使用),tag 精确到版本✅ 本文采用
ihatemoney/ihatemoney:latest同仓库滚动标签,随上游发布漂移可用但生产建议锁版本号
自建构建(仓库 Dockerfile)需自行维护构建与升级除非要改代码,否则不必要

镜像关键事实(docker image inspect 实查):

  • 入口为 /src/conf/entrypoint.sh,监听 0.0.0.0:8000(环境变量 PORT 可改);
  • 所有配置均通过环境变量注入:entrypoint 每次启动都会用环境变量重新生成 /etc/ihatemoney/ihatemoney.cfg,随后启动 gunicorn;
  • 默认环境变量中 SECRET_KEY=tralala(演示值)与 SESSION_COOKIE_SECURE=True,生产必须覆盖(见第六章);
  • 镜像内置数据库驱动:psycopg2-binary 2.9.12(PostgreSQL)、PyMySQL 1.1.3(MySQL/MariaDB),切换后端数据库无需改镜像;
  • 支持 PUID/PGID 环境变量:同时非 0 时,gunicorn 以对应 uid/gid 运行(bind mount 场景对齐宿主目录属主用)。

⚠️ 注意:因为 entrypoint 启动时会重写 /etc/ihatemoney/ihatemoney.cfg,不要把自定义配置文件挂载到 /etc/ihatemoney/ihatemoney.cfg——会被覆盖。镜像形态下改配置的唯一正确方式是环境变量(对应关系见第六章参数表)。

三、前置条件

# 1. 宿主机已安装 Docker Engine 与 Compose v2 插件
docker --version && docker compose version
# 2. 拉取官方镜像(离线环境可先在有网机器 pull 后 docker save/load 迁移)
docker pull ihatemoney/ihatemoney:7.2.1
docker pull postgres:16-alpine   # 仅 PostgreSQL 栈需要
# 3. 创建项目目录
mkdir -p /opt/ihatemoney/compose/database

实测回显(输出节选):

Docker version 29.8.1, build 4a63305
Docker Compose version v5.5.1
Status: Downloaded newer image for ihatemoney/ihatemoney:7.2.1
docker.io/ihatemoney/ihatemoney:7.2.1

生成两个部署必用值(在生产口令 q1w2e3r4 的演示约定下):

# 随机 SECRET_KEY(64 位十六进制)
python3 -c 'import secrets; print(secrets.token_hex(32))'
# 管理口令哈希(交互输入 q1w2e3r4)
docker run -it --rm --entrypoint ihatemoney ihatemoney/ihatemoney:7.2.1 generate_password_hash

实测回显(每次生成的值不同,属正常的加盐行为):

6d625d1d54514763dee0c94bf2655ae8dce023a20eebd32b6f02159d8484de24
Password:
scrypt:32768:8:1$Fp1sFH6qf2GckPTF$74ac8c4bbb52aee9e4a5fa8bbbfc9297e9e536b152c62444c03e85afa1f3b4be94b461cf2de6f8b67b8a2488affb5c5de3bfa4ab599e5fb978978a14f9aefcae

四、部署步骤

4.1 主栈:SQLite 单容器(推荐入门/小团队)

编写 docker-compose.yml:

name: ihatemoney-7.2.1-stack

services:
  ihatemoney:
    image: ihatemoney/ihatemoney:7.2.1
    container_name: ihatemoney-7.2.1-web
    restart: unless-stopped
    ports:
      - "8130:8000"
    environment:
      SECRET_KEY: "6d625d1d54514763dee0c94bf2655ae8dce023a20eebd32b6f02159d8484de24"
      SQLALCHEMY_DATABASE_URI: "sqlite:////database/ihatemoney.db"
      ACTIVATE_DEMO_PROJECT: "False"
      ACTIVATE_ADMIN_DASHBOARD: "True"
      # 管理口令为 q1w2e3r4 的 scrypt 哈希;compose 中 $ 必须写作 $$
      ADMIN_PASSWORD: "scrypt:32768:8:1$$Fp1sFH6qf2GckPTF$$74ac8c4bbb52aee9e4a5fa8bbbfc9297e9e536b152c62444c03e85afa1f3b4be94b461cf2de6f8b67b8a2488affb5c5de3bfa4ab599e5fb978978a14f9aefcae"
      ALLOW_PUBLIC_PROJECT_CREATION: "True"
      SESSION_COOKIE_SECURE: "False"
      BABEL_DEFAULT_TIMEZONE: "Asia/Shanghai"
      ENABLE_CAPTCHA: "False"
      SHOW_ADMIN_EMAIL: "True"
      MAIL_DEFAULT_SENDER: "Budget manager <admin@example.com>"
    volumes:
      - ./database:/database
    networks:
      - ihm-net

networks:
  ihm-net:
    name: ihatemoney-compose-net
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.59.0/24

逐项说明:name: 显式声明 compose 项目名,避免与其他部署同名互相干扰;ports 把容器 8000 映射到宿主 8130(按宿主实际空闲端口调整);ADMIN_PASSWORD 中哈希含 $,在 compose 文件里必须写成 $$,否则 Compose 会把 $Fp1sFH6qf2GckPTF 当变量插值替换成空(官方文档专门提醒此坑);volumes 把宿主 ./database 挂到容器 /database(镜像默认 SQLite 路径即 sqlite:////database/ihatemoney.db);网络显式命名并指定子网,避免与宿主上其他 compose 项目网段冲突。

启动并查看状态:

cd /opt/ihatemoney/compose
docker compose up -d
docker compose ps --format 'table {{.Name}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'

实测回显:

 Container ihatemoney-7.2.1-web Created
 Container ihatemoney-7.2.1-web Starting
 Container ihatemoney-7.2.1-web Started
NAME                   IMAGE                         STATUS         PORTS
ihatemoney-7.2.1-web   ihatemoney/ihatemoney:7.2.1   Up 4 seconds   0.0.0.0:8130->8000/tcp, [::]:8130->8000/tcp

启动日志(首次启动会看到运行用户 uid/gid 与 gunicorn 启动信息):

docker compose logs --no-log-prefix ihatemoney | head -5

实测回显:


User uid: 0
User gid: 0

[2026-10-23 11:20:47 +0000] [1] [INFO] Starting gunicorn 26.0.0

数据库文件自动落盘到 bind mount(首次访问时应用自动建库迁移):

ls -la database/

实测回显:

total 152
drwxr-xr-x 2 root root   4096 10月 23 19:20 .
drwxr-xr-x 3 root root   4096 10月 23 19:20 ..
-rw-r--r-- 1 root root 143360 10月 23 19:20 ihatemoney.db

验证 Web 与 API(创建测试项目,HTTP 201 即成功):

curl -s -o /dev/null -w "home HTTP %{http_code}\n" http://127.0.0.1:8130/
curl -s -o /dev/null -w "create-project HTTP %{http_code}\n" -X POST http://127.0.0.1:8130/api/projects -d 'name=宿舍AA账本&id=dorm2026&password=q1w2e3r4&contact_email=admin@example.com'

实测回显:

home HTTP 200
create-project HTTP 201

浏览器访问 http://<宿主机IP>:8130/,用项目标识 dorm2026、私有码 q1w2e3r4 即可登录测试项目;管理仪表盘入口 /admin 用口令 q1w2e3r4 进入。

4.2 变体栈:ihatemoney + PostgreSQL 双容器

需要外部数据库(多人运维/已有 PG 设施)时,用双容器编排。编写 docker-compose.postgres.yml:

name: ihatemoney-7.2.1-pg-stack

services:
  ihatemoney:
    image: ihatemoney/ihatemoney:7.2.1
    container_name: ihatemoney-7.2.1-pg-web
    restart: unless-stopped
    ports:
      - "8131:8000"
    environment:
      SECRET_KEY: "6d625d1d54514763dee0c94bf2655ae8dce023a20eebd32b6f02159d8484de24"
      SQLALCHEMY_DATABASE_URI: "postgresql://ihatemoney:q1w2e3r4@postgres:5432/ihatemoney"
      ACTIVATE_DEMO_PROJECT: "False"
      ACTIVATE_ADMIN_DASHBOARD: "True"
      ADMIN_PASSWORD: "scrypt:32768:8:1$$Fp1sFH6qf2GckPTF$$74ac8c4bbb52aee9e4a5fa8bbbfc9297e9e536b152c62444c03e85afa1f3b4be94b461cf2de6f8b67b8a2488affb5c5de3bfa4ab599e5fb978978a14f9aefcae"
      SESSION_COOKIE_SECURE: "False"
      BABEL_DEFAULT_TIMEZONE: "Asia/Shanghai"
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - ihm-pg-net

  postgres:
    image: postgres:16-alpine
    container_name: ihatemoney-7.2.1-pg-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: "ihatemoney"
      POSTGRES_PASSWORD: "q1w2e3r4"
      POSTGRES_DB: "ihatemoney"
    volumes:
      - ./pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ihatemoney -d ihatemoney"]
      interval: 5s
      timeout: 3s
      retries: 10
    networks:
      - ihm-pg-net

networks:
  ihm-pg-net:
    name: ihatemoney-pg-net
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.61.0/24

逐项说明:两个 service 通过 depends_on + pg_isready 健康检查保证数据库就绪后应用才启动(IHateMoney 启动即连库迁移,数据库没就绪会启动失败);连接串用服务名 postgres 作主机名(同一 compose 网络内置 DNS 解析);./pgdata 持久化 PG 数据目录。

docker compose -f docker-compose.postgres.yml up -d
docker compose -f docker-compose.postgres.yml ps --format 'table {{.Name}}\t{{.Image}}\t{{.Status}}'

实测回显:

 Container ihatemoney-7.2.1-pg-db Healthy
 Container ihatemoney-7.2.1-pg-web Starting
 Container ihatemoney-7.2.1-pg-web Started
NAME                        IMAGE                         STATUS
ihatemoney-7.2.1-pg-db      postgres:16-alpine            Up 11 seconds (healthy)
ihatemoney-7.2.1-pg-web     ihatemoney/ihatemoney:7.2.1   Up 6 seconds

验证(建项目 + 进 PG 容器核对建表):

curl -s -o /dev/null -w "pg-stack home HTTP %{http_code}\n" http://127.0.0.1:8131/
curl -s -o /dev/null -w "pg-stack create HTTP %{http_code}\n" -X POST http://127.0.0.1:8131/api/projects -d 'name=旅行基金&id=trip2026&password=q1w2e3r4&contact_email=admin@example.com'
docker exec ihatemoney-7.2.1-pg-db psql -U ihatemoney -d ihatemoney -c '\dt'

实测回显(建表输出节选):

pg-stack home HTTP 200
pg-stack create HTTP 201
                List of relations
 Schema |       Name        | Type  |   Owner
--------+-------------------+-------+------------
 public | alembic_version   | table | ihatemoney
 public | archive           | table | ihatemoney
 public | bill              | table | ihatemoney
 public | bill_version      | table | ihatemoney
 public | billowers         | table | ihatemoney
 public | billowers_version | table | ihatemoney
 public | person            | table | ihatemoney
 public | person_version    | table | ihatemoney
 public | project           | table | ihatemoney

4.3 两栈对比与选择建议

维度SQLite 主栈PostgreSQL 变体栈
容器数12(应用 + 数据库)
数据落盘./database/ihatemoney.db 单文件./pgdata/ 目录
运维复杂度低(无额外依赖)中(需维护 PG 口令/升级/备份)
适用规模家庭/小团队,几十个项目内多人协作、已有 PG 设施、需外部备份体系
连接串sqlite:////database/ihatemoney.dbpostgresql://用户:口令@postgres:5432/ihatemoney

选择建议:个人/家庭自托管直接 SQLite 主栈;多人生产、需要专业备份与并发写入再上 PG 栈。切 MySQL/MariaDB 只需把连接串改为 mysql+pymysql://用户:口令@主机:3306/库名(镜像已内置 PyMySQL 驱动)。

五、数据持久化说明

  • SQLite 栈:全部数据在一个文件 ./database/ihatemoney.db(bind mount 到容器 /database)。实测重启后数据完好——
docker compose restart
sleep 3
curl -s --basic -u dorm2026:q1w2e3r4 http://127.0.0.1:8130/api/projects/dorm2026 -o /dev/null -w "after-restart login HTTP %{http_code}\n"

实测回显:

 Container ihatemoney-7.2.1-web Started
after-restart login HTTP 200
  • PG 栈:数据在 ./pgdata/;备份用 docker exec ihatemoney-7.2.1-pg-db pg_dump -U ihatemoney ihatemoney > ihm-$(date +%F).sql。
  • 迁移(换宿主机):SQLite 栈打包 docker compose down 后整个 compose 目录(含 database/)拷贝到新机重新 up -d 即可。
  • 权限:容器默认以 root(uid 0)写入 bind mount(实测 ihatemoney.db 属主为 root)。若宿主要求非 root 属主,在两个栈的 ihatemoney 服务上加 PUID/PGID 环境变量(如 PUID: "1000"、PGID: "1000")并把宿主目录 chown 成对应 uid/gid。

六、环境变量表(镜像全部可用参数)

以下为镜像 entrypoint 支持的全部环境变量(实查自镜像默认 Env 与 entrypoint.sh,值列标注镜像默认值):

环境变量镜像默认值建议生产值说明
SECRET_KEYtralala64 位随机串⚠️ 默认是公开演示值,必须覆盖;泄露可伪造会话登录任意项目
SQLALCHEMY_DATABASE_URIsqlite:////database/ihatemoney.db同默认或 PG/MySQL 连接串数据库连接串
DEBUGFalseFalse调试模式,生产禁开
ACTIVATE_ADMIN_DASHBOARDFalseTrue(配合口令)管理仪表盘开关
ACTIVATE_DEMO_PROJECTTrueFalse首页公共演示项目,私有实例关闭
ADMIN_PASSWORD空scrypt 哈希管理口令哈希;compose 里 $ 写 $$
ALLOW_PUBLIC_PROJECT_CREATIONTrue按需匿名建项目开关,收口则 False(建项目需管理口令)
BABEL_DEFAULT_TIMEZONEUTCAsia/Shanghai界面显示时区
SESSION_COOKIE_SECURETrueHTTPS 保持 True;纯 HTTP 部署 False纯 HTTP 下保持默认会导致浏览器丢会话 Cookie
SHOW_ADMIN_EMAILTrue按需错误页是否展示管理员邮箱
APPLICATION_ROOT//(子路径部署改)应用挂载路径
ENABLE_CAPTCHAFalse公网实例 True建项目验证码
LEGAL_LINK空按需法律/隐私页 URL
MAIL_DEFAULT_SENDERBudget manager <admin@example.com>真实发件人发件人展示
MAIL_SERVERlocalhost外部 SMTP 主机邮件服务器(邀请/找回邮件用)
MAIL_PORT25465(SSL)/587(TLS)SMTP 端口
MAIL_USE_SSLFalse465 端口时 TrueSSL 直连
MAIL_USE_TLSFalse587 端口时 TrueSTARTTLS
MAIL_USERNAME空SMTP 账号认证账号
MAIL_PASSWORD空SMTP 口令认证口令(敏感,勿入库)
PUID / PGID0 / 0bind mount 对齐属主时设置以非 root 运行 gunicorn

配置文件挂载方式(与原生部署的差异):镜像不支持挂载 /etc/ihatemoney/ihatemoney.cfg(启动时被 entrypoint 重写);所有配置以环境变量为准。若确需复杂配置文件(如含邮件全套参数),可把文件挂到其他路径并额外注入 IHATEMONEY_SETTINGS_FILE_PATH 指向它——但注意该变量必须在 entrypoint 生成配置之后对应用生效,因此更稳妥的做法仍是一律用环境变量。

等价 docker run 命令(与 4.1 主栈等价,便于对照理解或临时起测试实例):

docker run -d --name ihatemoney-7.2.1-web \
  -p 8130:8000 \
  -e SECRET_KEY="6d625d1d54514763dee0c94bf2655ae8dce023a20eebd32b6f02159d8484de24" \
  -e SQLALCHEMY_DATABASE_URI="sqlite:////database/ihatemoney.db" \
  -e ACTIVATE_DEMO_PROJECT=False \
  -e ACTIVATE_ADMIN_DASHBOARD=True \
  -e ADMIN_PASSWORD='scrypt:32768:8:1$Fp1sFH6qf2GckPTF$74ac8c4bbb52aee9e4a5fa8bbbfc9297e9e536b152c62444c03e85afa1f3b4be94b461cf2de6f8b67b8a2488affb5c5de3bfa4ab599e5fb978978a14f9aefcae' \
  -e SESSION_COOKIE_SECURE=False \
  -e BABEL_DEFAULT_TIMEZONE="Asia/Shanghai" \
  -v /opt/ihatemoney/compose/database:/database \
  ihatemoney/ihatemoney:7.2.1

⚠️ 注意 docker run 形式里 ADMIN_PASSWORD 的 $ 不需要 $$ 转义(那是 Compose 的变量插值规则,docker run 直接传给 shell 时用单引号保护即可)。

七、常用运维命令

cd /opt/ihatemoney/compose

# 查看状态/资源占用
docker compose ps
docker compose top

实测回显(docker compose top,可见容器内 gunicorn 主/工作进程以 root 运行、监听 0.0.0.0:8000):

SERVICE     #   UID   PID      PPID     C   STIME  TTY  TIME      CMD
ihatemoney  1   root  1409953  1409925  1   19:24  ?    00:00:00  /usr/local/bin/python3.11 /usr/local/bin/gunicorn ihatemoney.wsgi:application -b 0.0.0.0:8000 --log-syslog
ihatemoney  1   root  1410135  1409953  3   19:24  ?    00:00:00  /usr/local/bin/python3.11 /usr/local/bin/gunicorn ihatemoney.wsgi:application -b 0.0.0.0:8000 --log-syslog

# 日志(实时跟踪)
docker compose logs -f --tail 100 ihatemoney

# 重启应用容器
docker compose restart

# 停止与销毁(SQLite 栈;PG 栈把 -f 换成 docker-compose.postgres.yml)
docker compose down          # 保留 bind mount 数据
docker compose down -v       # 连同 named volume 一起删(本文栈未用 named volume,等价上一条)

# 升级版本(锁版本 tag 的改法)
docker pull ihatemoney/ihatemoney:7.2.2   # 以官方 Releases 实际最新版为准
# 把 compose 文件中的 image tag 改为新版本后:
docker compose up -d

实测回显(docker compose down 后再 up -d 的完整循环,已验证 down/up 后登录接口正常 HTTP 200):

 Container ihatemoney-7.2.1-web Removing
 Container ihatemoney-7.2.1-web Removed
 Network ihatemoney-compose-net Removing
 Network ihatemoney-compose-net Removed
 Container ihatemoney-7.2.1-web Created
 Container ihatemoney-7.2.1-web Starting
 Container ihatemoney-7.2.1-web Started

💡 说明:镜像内应用启动即自动执行数据库迁移(首访时建表/补迁移),升级版本无需手动跑 db upgrade;但升级前请按第五章备份 SQLite 文件或 pg_dump,回滚时才有的退。

八、常见问题(FAQ)

Q1:登录后一刷新就退回登录页? 原因:镜像默认 SESSION_COOKIE_SECURE=True,纯 HTTP 部署时浏览器拒发 Cookie。解决:compose 的 environment 里显式设 SESSION_COOKIE_SECURE: "False"(本文 4.1 主栈已按此配置);HTTPS 部署则保持默认并在前面加 TLS 代理。

Q2:管理仪表盘 /admin 无法进入? 排查两处:ACTIVATE_ADMIN_DASHBOARD 是否为 True、ADMIN_PASSWORD 是否为非空哈希(两者任一为默认值时管理面禁用)。另外检查哈希里的 $ 是否已写成 $$——若漏写,Compose 插值后 ADMIN_PASSWORD 实际为空串,管理面即禁用。可用以下命令核对实际生效值(本文实测,$$ 正确转义后容器内为单 $ 的完整哈希):

docker compose exec ihatemoney grep ADMIN_PASSWORD /etc/ihatemoney/ihatemoney.cfg

实测回显(截断显示):

ADMIN_PASSWORD = 'scrypt:32768:8:1$Fp1sFH6qf2GckPTF$74ac8c4bbb52aee9e4a5fa8bbbfc9297e9e536b152c62444

Q3:bind mount 里的数据库文件属主是 root,宿主程序读不了? 容器默认以 root 运行。解决:按第五章加 PUID/PGID 并对齐宿主目录属主后 docker compose up -d 重建容器。

Q4:想在 ihatemoney 后面接 Nginx/HTTPS 怎么做? 把 4.1 主栈的 ports 改为只绑本机("127.0.0.1:8130:8000"),宿主 Nginx 反代到 http://127.0.0.1:8130(站点配置可参考《Ubuntu 22.04 安装 IHateMoney 7.2.1 指南》4.5 节的 Nginx 模板,proxy_pass 目标换成本机端口);上 HTTPS 后把 SESSION_COOKIE_SECURE 改回 True。

Q5:升级后页面报数据库错误? 个别版本迁移在升级后首次请求时进行,刷新或重启容器(docker compose restart)后自动补齐;仍异常时查 docker compose logs ihatemoney 中的 alembic 报错行,必要时回滚镜像 tag 并恢复第五章备份的数据库。

九、参考链接