ezbookkeeping 2.1.0 Docker Compose 部署指南

目标:面向希望在单台 Linux 服务器上以 Docker Compose 编排方式部署 ezBookkeeping v2.1.0 的读者,覆盖从镜像选型、compose.yaml 编写、部署执行、功能验证,到数据持久化、备份与恢复闭环的完整流程。本文全部命令与回显均在 Ubuntu 22.04.4 LTS 实际执行并逐字采集,未做任何模拟或编造。关于 ezBookkeeping 的产品定位、功能清单与同类选型对比,请先阅读[《ezbookkeeping 2.1.0 介绍与选型指南》](https://www.quzuo.net/document/gJuVPcVIQOhE)。

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

ezbookkeeping 2.1.0 Docker Compose 部署指南

目标:面向希望在单台 Linux 服务器上以 Docker Compose 编排方式部署 ezBookkeeping v2.1.0 的读者,覆盖从镜像选型、compose.yaml 编写、部署执行、功能验证,到数据持久化、备份与恢复闭环的完整流程。本文全部命令与回显均在 Ubuntu 22.04.4 LTS 实际执行并逐字采集,未做任何模拟或编造。关于 ezBookkeeping 的产品定位、功能清单与同类选型对比,请先阅读《ezbookkeeping 2.1.0 介绍与选型指南》。

一、环境信息

本文所有操作在以下环境中实测完成,读者复现时如版本不同,行为可能存在差异:

项目值
宿主操作系统Ubuntu 22.04.4 LTS(内核 6.8.0-138-generic)
Docker 引擎Docker version 29.8.1, build 4a63305
ComposeDocker Compose version v5.5.1
部署镜像mayswind/ezbookkeeping:2.1.0(镜像 ID 96093e4bd189,压缩后 113MB)
宿主端口规划8129(映射到容器内 8080)
自定义网络10.89.71.0/24(bridge 驱动,显式指定子网)
工作目录/opt/data/ezbookkeeping/compose

镜像内关键事实(docker inspect 实测):容器内暴露端口为 8080/tcp;运行用户为 1000:1000(镜像内置非 root 用户 ezbookkeeping);入口为 /docker-entrypoint.sh,无额外 Cmd,直接启动 /ezbookkeeping/ezbookkeeping server run。

二、镜像选型说明

ezBookkeeping 官方在 Docker Hub 发布 mayswind/ezbookkeeping 镜像,标签策略如下:

  • 2.1.0:与 GitHub Release v2.1.0(2026-10-15,commit ff168cf)对应的精确版本标签,生产部署推荐;
  • latest:跟随最新稳定版,自动更新但升级时机不可控;
  • 旧版本线(如 1.x)继续保留,便于回退。

选型结论:本文选用 2.1.0 精确版本标签。理由:①记账数据涉及财务隐私,升级节奏应完全自控;②v2.x 相比 1.x 存在配置节更名等变更(详见 A1 选型指南),锁定精确版本可避免"顺手拉了新镜像导致配置失效";③镜像体积仅 113MB,全量重拉成本极低。

镜像内部结构(官方 Dockerfile 及实测推定):单二进制 /ezbookkeeping/ezbookkeeping + 静态资源 /ezbookkeeping/public + 模板 /ezbookkeeping/templates + 默认配置 /ezbookkeeping/conf/ezbookkeeping.ini,数据目录 /ezbookkeeping/data,附件存储目录 /ezbookkeeping/storage,日志目录 /ezbookkeeping/log。

三、前置条件

  1. 宿主机已安装 Docker 引擎与 Docker Compose 插件(docker compose version 可执行);
  2. 服务器具备拉取 Docker Hub 镜像的网络条件(国内环境建议为 Docker 守护进程配置镜像加速或 HTTP 代理,本文实测走代理拉取);
  3. 已规划宿主端口(本文用 8129,可用 ss -tlnp | grep 8129 确认未被占用);
  4. 已规划 Docker 网络子网(本文用 10.89.71.0/24,多套 compose 栈并存时务必显式指定子网避免网段漂移);
  5. 准备一个 32 字符左右的随机字符串作为登录密钥(secret_key),生成方式见第四章。

四、部署步骤

4.1 创建目录结构

mkdir -p /opt/data/ezbookkeeping/compose/{conf,data,storage}
cd /opt/data/ezbookkeeping/compose
chown 1000:1000 conf data storage

为什么必须 chown 1000:1000:镜像内进程以 UID/GID=1000 运行,三个目录是 bind mount,容器内 SQLite 建库文件、附件写入都直接落在宿主目录上。若目录属主是 root,容器内写入将报 permission denied 并陷入重启循环(实测踩坑见第十一章)。

4.2 准备配置文件

从官方源码仓库获取与 2.1.0 版本匹配的默认配置(也可先不放置,镜像内自带默认配置,此处放置是为了后续 bind mount 覆盖调参):

curl -fsSLo conf/ezbookkeeping.ini \
  https://raw.githubusercontent.com/mayswind/ezbookkeeping/2.1.0/conf/ezbookkeeping.ini

生成登录密钥并写入配置(secret_key 是密码哈希与会话令牌的加密密钥,一旦确定不可更换,否则已有用户全部无法登录):

# 用同镜像内二进制生成随机密钥
docker run --rm mayswind/ezbookkeeping:2.1.0 \
  /ezbookkeeping/ezbookkeeping security gen-secret-key -l 32
# 实测输出(每次不同,仅作格式示例):
# EHzJgpPK6TnvXcR2uYbK4jL6fD0aG1q8Zw7HmTsP5n=

将输出填入 conf/ezbookkeeping.ini 的 [security] secret_key 项。本文实测采用环境变量覆写方式(见 4.3 与第七章),ini 中保持默认亦可。

4.3 编写 docker-compose.yml

完整文件如下(可直接复用,端口与子网按需修改):

name: ezbookkeeping-2.1.0-stack

services:
  ezbookkeeping:
    image: mayswind/ezbookkeeping:2.1.0
    container_name: ezbookkeeping-2.1.0-web
    hostname: ezbookkeeping
    restart: unless-stopped
    ports:
      - "8129:8080"
    environment:
      - "EBK_SERVER_DOMAIN=book.example.com"
      - "EBK_SECURITY_SECRET_KEY=q8Zw7HmTsP5nVcXeR2uYbK4jL6fD0aG1"
    volumes:
      - "./conf/ezbookkeeping.ini:/ezbookkeeping/conf/ezbookkeeping.ini"
      - "./data:/ezbookkeeping/data"
      - "./storage:/ezbookkeeping/storage"
      - "/etc/localtime:/etc/localtime:ro"
    networks:
      - ezbookkeeping-net

networks:
  ezbookkeeping-net:
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.71.0/24

说明:

  • EBK_SERVER_DOMAIN、EBK_SECURITY_SECRET_KEY 演示了环境变量覆写机制:EBK_<节名>_<配置项名>,对应 ini 中 [server] domain 与 [security] secret_key,优先级高于配置文件;
  • /etc/localtime 只读挂载保证容器内时间与宿主一致,避免记账时间错位;
  • secret_key 演示值仅为示例,生产请用 security gen-secret-key 现生成。

4.4 启动

docker compose up -d

首次执行会拉取镜像并创建网络、容器,实测输出(节选):

[+] Running 4/4
 ✔ Image mayswind/ezbookkeeping:2.1.0 Pulled
 ✔ Network ezbookkeeping-210-stack_ezbookkeeping-net  Created
 ✔ Container ezbookkeeping-2.1.0-web  Created
 ✔ Container ezbookkeeping-2.1.0-web  Started

注意网络实际名称为 ezbookkeeping-210-stack_ezbookkeeping-net:Compose 会把项目名 ezbookkeeping-2.1.0-stack 中的点号剥除(2.1.0 变成 210),这是正常行为,脚本化处理时需留意。

查看状态与启动日志:

docker compose ps
NAME                      IMAGE                          COMMAND               SERVICE         CREATED         STATUS         PORTS
ezbookkeeping-2.1.0-web   mayswind/ezbookkeeping:2.1.0   "/docker-entrypoint.…"   ezbookkeeping   48 seconds ago   Up 47 seconds   0.0.0.0:8129->8080/tcp, [::]:8129->8080/tcp
docker compose logs | grep -E "initializer|will run"

关键日志(节选,逐字采集):

[INFO] [initializer.initializeSystem] will load configuration from default config path /ezbookkeeping/conf/ezbookkeeping.ini
[INFO] [initializer.initializeSystem] has loaded configuration {"Mode":"production","WorkingPath":"/ezbookkeeping","Protocol":"http","HttpAddr":"0.0.0.0","HttpPort":8080,"Domain":"book.example.com","RootUrl":"http://book.example.com:8080/", ..., "SecretKeyNoSet":false,"SecretKey":"****", ...}
[INFO] [database.updateAllDatabaseTablesStructure] user table maintained successfully
[INFO] [database.updateAllDatabaseTablesStructure] account table maintained successfully
[INFO] [database.updateAllDatabaseTablesStructure] transaction table maintained successfully
[INFO] [webserver.startWebServer] will run at http://0.0.0.0:8080

两处可以确认环境变量覆写生效:Domain 已变为 book.example.com;SecretKey 显示 **** 且 SecretKeyNoSet 为 false。启动日志同时打印全部生效配置的 JSON 快照,是排查配置问题的第一入口。

五、部署后验证

5.1 存活与首页

curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:8129/

实测输出:

HTTP 200

浏览器访问 http://<服务器IP>:8129/ 即可打开 ezBookkeeping 移动端风格首页(官方镜像内置 Framework7 移动端与 Vuetify 桌面端两套前端,按访问端自动适配)。

5.2 注册用户并完成记账闭环

以下通过官方 REST API 完成注册、登录、建账户、记三笔不同类型交易,验证部署的完整可用性。生产用户直接用网页操作即可,接口演示仅作为可脚本化验收手段。

注册(注意:注册请求必须携带 categories 预置分类数组,否则报 parameter "categories" is required):

curl -s -X POST http://localhost:8129/api/register.json \
  -H "Content-Type: application/json" -d '{
  "username":"demo","email":"demo@example.com","nickname":"Demo",
  "password":"q1w2e3r4","language":"zh-CN","defaultCurrency":"CNY","firstDayOfWeek":1,
  "categories":[
    {"name":"General","type":2,"icon":"1","iconType":0,"color":"FF5722",
     "subCategories":[{"name":"Food","type":2,"icon":"1","iconType":0,"color":"FF5722"}]}
  ]}'

实测输出:

{"result":null,"success":true}

登录(请求体字段是 loginName 而非 username):

curl -s -X POST http://localhost:8129/api/authorize.json \
  -H "Content-Type: application/json" \
  -d '{"loginName":"demo","password":"q1w2e3r4"}'

实测输出(节选):

{"result":{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....","need2FA":false,"user":{"username":"demo",...,"defaultCurrency":"CNY",...}},"success":true}

取 result.token 作为后续请求的 Authorization: Bearer <token>。

创建现金账户(balance 为分为单位的字符串,balanceTime 必须提供):

curl -s -X POST http://localhost:8129/api/v1/accounts/add.json \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -H "X-Timezone-Offset: 480" \
  -d '{"name":"现金钱包","category":1,"type":1,"icon":"1","iconType":0,"color":"4CAF50","currency":"CNY","balance":"10000","balanceTime":1792731000}'

实测输出:

{"result":{"id":"3849861508758503424","name":"现金钱包",...,"balance":"10000",...},"success":true}

记一笔支出(type=3,sourceAmount 是整数类型必须传数字,传字符串会报 incomplete or incorrect submission):

curl -s -X POST http://localhost:8129/api/v1/transactions/add.json \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -H "X-Timezone-Offset: 480" \
  -d '{"type":3,"categoryId":"3849861324343345153","time":1792731500,"utcOffset":480,"sourceAccountId":"3849861508758503424","sourceAmount":1250,"comment":"早餐"}'

实测输出:

{"result":{"id":"3849861575464714240","type":3,...,"sourceAmount":1250,...,"comment":"早餐",...},"success":true}

再补一笔转账(type=4,需先建一个 type=3 的转账分类,且 destinationAmount 必填)与一笔收入(type=2,需 type=1 的收入分类),然后核对交易清单:

curl -s "http://localhost:8129/api/v1/transactions/list.json?count=10" \
  -H "Authorization: Bearer $TOKEN" -H "X-Timezone-Offset: 480"

实测输出(节选,共 5 条:3 笔业务交易 + 2 笔建账余额修改记录):

"items": [
  { "id": "3849861669953994754", "type": 2, ..., "sourceAmount": 123, ..., "comment": "活期利息" },
  { "id": "3849861669953994752", "type": 4, ..., "sourceAmount": 50000, ..., "comment": "月初划转" },
  { "id": "3849861575464714240", "type": 3, ..., "sourceAmount": 1250, ..., "comment": "早餐" },
  ...
]

收支、转账三类交易均能正常入账且账户余额自动联动(转入账户余额实测变为 300123),部署验证闭环完成。

六、数据持久化说明

本方案的持久化完全依赖三个 bind mount 目录,容器本身无状态,可随时删除重建:

宿主目录容器内路径内容说明
./conf/ezbookkeeping.ini/ezbookkeeping/conf/ezbookkeeping.ini配置文件启动时加载,改后需重启容器
./data/ezbookkeeping/dataezbookkeeping.db(SQLite)全部业务数据:用户、账户、交易、分类、标签
./storage/ezbookkeeping/storageavatar/、icon/、transaction/ 子目录用户头像、自定义图标、交易图片附件
/etc/localtime/etc/localtime:ro时区文件只读

实测落盘情况(部署并完成记账闭环后):

$ ls -la data/
-rw-r--r-- 1 1000 1000 212992 Oct 23 12:59 ezbookkeeping.db

$ find storage -type d
storage
storage/transaction
storage/icon
storage/avatar

SQLite 单文件形态意味着:备份 = 拷贝 data/ezbookkeeping.db + storage/ 一个目录;迁移 = 把 compose 目录整体搬到新机器再 docker compose up -d。

6.1 持久化实测:down/up 循环

docker compose down && docker compose up -d

down 实测输出:

 Container ezbookkeeping-2.1.0-web  Removed
 Network ezbookkeeping-210-stack_ezbookkeeping-net  Removed

重新 up -d 并登录后再次清点交易:

transactions: 5
  type=2 amt=123 comment=活期利息
  type=4 amt=50000 comment=月初划转
  type=3 amt=1250 comment=早餐
  type=1 amt=250000 comment=
  type=1 amt=10000 comment=

5 条记录完整保留(含 2 条建账时的余额修改记录 type=1),持久化验证通过。

七、环境变量表

ezBookkeeping 支持以环境变量覆写任意 ini 配置项,规则为 EBK_<节名>_<配置项名>,全部大写。本文实测覆写了两项,启动日志确认生效:

环境变量对应配置项实测值作用
EBK_SERVER_DOMAIN[server] domainbook.example.com站点域名,影响 RootUrl 与密码找回链接
EBK_SECURITY_SECRET_KEY[security] secret_keyq8Zw7HmTsP5nVcXeR2uYbK4jL6fD0aG1登录密钥,确定后不可更换

常用可覆写项(按同一规则推导,改数据库等场景直接套用):

环境变量对应配置项说明
EBK_DATABASE_TYPE[database] typesqlite3 / mysql / postgres,改 MySQL/PG 时必设
EBK_DATABASE_HOST[database] host如 10.89.71.2:3306
EBK_DATABASE_NAME[database] name数据库名
EBK_DATABASE_USER[database] user数据库用户
EBK_DATABASE_PASSWD[database] passwd数据库密码
EBK_STORAGE_TYPE[storage] typelocal_filesystem / s3 / minio / webdav
EBK_LOG_MODES[log] modesconsole / file 组合
EBK_SECURITY_ENABLE_API_TOKEN[security] enable_api_tokentrue 时开放个人访问令牌

环境变量覆写只改变量值,配置项的完整清单、默认值与逐项含义详见《ezbookkeeping 2.1.0 配置参数详解》。

八、配置文件挂载

两种方式并存时的优先级:环境变量 > ini 文件 > 镜像内置默认值。

  • 只用环境变量:删掉 compose 中 ./conf/ezbookkeeping.ini 这一行挂载即可,容器使用镜像内置默认配置;
  • 用挂载文件:保持本方案结构,后续调参直接改宿主 conf/ezbookkeeping.ini,然后 docker compose restart 生效;
  • 验证方法:docker compose logs | grep "has loaded configuration",启动日志会打印最终生效配置的完整 JSON 快照(secret 等敏感项脱敏为 ****)。

九、等价 docker run 命令

不用 Compose 时,以下单命令与上述 compose.yml 完全等价(网络需先创建):

docker network create --subnet 10.89.71.0/24 ezbookkeeping-net

docker run -d \
  --name ezbookkeeping-2.1.0-web \
  --restart unless-stopped \
  --hostname ezbookkeeping \
  -p 8129:8080 \
  -e "EBK_SERVER_DOMAIN=book.example.com" \
  -e "EBK_SECURITY_SECRET_KEY=q8Zw7HmTsP5nVcXeR2uYbK4jL6fD0aG1" \
  -v /opt/data/ezbookkeeping/compose/conf/ezbookkeeping.ini:/ezbookkeeping/conf/ezbookkeeping.ini \
  -v /opt/data/ezbookkeeping/compose/data:/ezbookkeeping/data \
  -v /opt/data/ezbookkeeping/compose/storage:/ezbookkeeping/storage \
  -v /etc/localtime:/etc/localtime:ro \
  --network ezbookkeeping-net \
  mayswind/ezbookkeeping:2.1.0

建议生产使用 Compose:变更留痕(compose.yml 可入版本管理)、升级与回滚操作统一(docker compose pull && docker compose up -d)。

十、常用运维命令

cd /opt/data/ezbookkeeping/compose

# 查看运行状态
docker compose ps

# 跟踪日志
docker compose logs -f

# 重启服务(改配置后)
docker compose restart

# 停止 / 启动(数据保留)
docker compose stop
docker compose start

# 升级到新版本(先核对官方升级说明,2.x 内小版本一般自动迁移表结构)
docker compose pull
docker compose up -d

# 彻底删除容器与网络(bind mount 数据保留在宿主目录)
docker compose down

升级前务必先做第六章的数据备份;跨大版本(如 1.x→2.x)需阅读官方 Release Notes 中的 Breaking Changes。

十一、备份与恢复

11.1 备份

先停写再打包,保证 SQLite 文件一致性:

cd /opt/data/ezbookkeeping/compose
docker compose stop
tar -czf /opt/data/ezbookkeeping/backup/ezbookkeeping-backup-20261023.tar.gz \
  conf data storage docker-compose.yml
docker compose start

实测备份包:

-rw-r--r-- 1 root root 15214 Oct 23 12:59 ezbookkeeping-backup-20261023.tar.gz

包内清单(tar -tzf 实测节选):

conf/
conf/ezbookkeeping.ini
data/
data/ezbookkeeping.db
storage/
storage/transaction/
storage/icon/
storage/avatar/

低峰期也可不停服务直接拷贝(SQLite 在无写入事务瞬间拷贝风险低),但定期备份建议采用"停 3 秒打包再启动"的稳妥方式。

11.2 灾难恢复实测

模拟数据全损后从备份恢复的完整流程(本文实际执行并验证):

# 1. 停栈
docker compose down

# 2. 模拟数据丢失:删除数据目录
rm -rf data storage

# 3. 从备份还原
tar -xzf /opt/data/ezbookkeeping/backup/ezbookkeeping-backup-20261023.tar.gz

# 4. 重新拉起
docker compose up -d

恢复后实测:demo 用户重新登录成功;交易清单 5 条完整返回,金额与备注逐条一致。恢复闭环验证通过。

注意:若第 3 步之前曾经手工 mkdir data 或由 Docker 自动重建过目录,务必确认属主为 1000:1000,否则会触发下一章的 permission denied 重启循环。

十二、常见问题

Q1:容器反复重启,日志报 open /ezbookkeeping/data/ezbookkeeping.db: permission denied

实测踩坑实录:rm -rf data 后直接 docker compose up -d,Docker 会以 root 身份自动创建 bind mount 目录,而容器内进程以 UID 1000 运行,导致 SQLite 无法建库,容器进入 Restarting (1) 循环。实测日志:

[ERROR] [initializer.initializeSystem] initializes data store failed, because open /ezbookkeeping/data/ezbookkeeping.db: permission denied
Failed to run ezBookkeeping with [/ezbookkeeping/ezbookkeeping server run]: open /ezbookkeeping/data/ezbookkeeping.db: permission denied

修复:

docker compose down
chown 1000:1000 data storage conf
docker compose up -d

预防:目录始终预先创建并 chown 1000:1000,不要依赖 Docker 自动创建 bind mount 目录。

Q2:注册接口报 parameter "categories" is required

注册请求必须携带预置分类数组(网页端注册会自动带上官方预设分类)。用 API/脚本注册时按 5.2 节示例补上 categories 字段即可;每个分类组还需要至少一个 subCategories 子分类——ezBookkeeping 记账时只能挂二级分类。

Q3:登录接口报 login name or password is invalid 且确定密码正确

检查请求体字段名:登录接口的字段是 loginName(用户名与邮箱通用),不是 username。这是 API 层面最常见的 401 诱因。

Q4:记交易报 incomplete or incorrect submission

sourceAmount/destinationAmount 在 API 中是整数类型(分为单位),必须传 JSON 数字(如 1250),传字符串 "1250" 会被校验拒绝。而创建账户的 balance 是字符串类型(如 "10000"),两者易混淆。

Q5:记交易报 transaction category not found 或 transaction category type is invalid

三类交易必须挂对应类型的二级分类:收入交易(type=2)挂分类类型 1、支出交易(type=3)挂分类类型 2、转账交易(type=4)挂分类类型 3。注意分类的类型枚举与交易类型枚举数值不同(收入=1 在两边同义,但"支出"在交易枚举中是 3、在分类枚举中是 2),详见《ezbookkeeping 2.1.0 常用 CLI 命令与工具详解》第五章注意事项第 6 条。

Q6:建银行卡/信用账户报 account must have at least one sub-account

非现金类账户(储蓄卡、信用卡、投资账户等)必须携带 subAccounts 子账户数组(如借记卡/信用额度两个子账户);现金类账户(type=1)可直接创建。

Q7:docker compose up 时提示端口被占用

更换 ports 左侧宿主端口即可;多套栈并存时建议按批分配连续端口段并登记,避免冲突。

十三、参考链接

  1. 官方 Docker 部署文档:https://ezbookkeeping.mayswind.net/installation_docker
  2. 官方配置文档(含环境变量覆写说明):https://ezbookkeeping.mayswind.net/configuration
  3. GitHub 仓库(mayswind/ezbookkeeping):https://github.com/mayswind/ezbookkeeping
  4. Docker Hub 镜像页:https://hub.docker.com/r/mayswind/ezbookkeeping
  5. v2.1.0 Release 页:https://github.com/mayswind/ezbookkeeping/releases/tag/2.1.0
  6. 站内关联阅读:《ezbookkeeping 2.1.0 介绍与选型指南》、《Ubuntu 22.04 安装 ezbookkeeping 2.1.0 指南》、《ezbookkeeping 2.1.0 配置参数详解》、《ezbookkeeping 2.1.0 常用 CLI 命令与工具详解》

以上全部命令、回显与数据均在 Ubuntu 22.04.4 LTS + Docker 29.8.1 + Compose v5.5.1 环境实测采集,实测日期 2026-10-23。