Actual Docker Compose 部署实战
Actual(v26.10.0)Docker Compose 部署实战:官方 actual-server 镜像选型与 digest、落地 Compose 编排、bootstrap 初始化密码、建预算记账端到端验证、/data 数据目录结构与环境变量/配置文件挂载,附 docker run 等价命令与 6 条排障实录。
Actual Docker Compose 部署实战
目标:从零开始在一台 Linux 服务器上用 Docker Compose 部署 Actual(Actual Budget)的 sync-server(同步服务器),完成初始化密码设置、第一个预算文件创建与记账验证,并沉淀数据目录结构与常见故障的实录解法。本文全部命令与回显均在 Ubuntu 24.04 实测环境逐条验证通过,实测版本 Actual v26.10.0。
一、环境信息
本次实测环境与关键产物信息如下,所有版本号为实查值:
- 操作系统:Ubuntu 24.04(x86_64)
- Docker:28.x 系列(含 Docker Compose v2 插件,
docker compose子命令可用) - Actual 版本:v26.10.0(官方
latest标签镜像,镜像摘要sha256:24645e971da6...;启动后经 API 实测确认{"version":"26.10.0"}) - 部署形态:单节点 sync-server + 内置 Web 客户端
- 监听端口:宿主机 8129 → 服务端口 5006(5006 为 Actual 默认端口)
- 数据目录:
/opt/actual/data(映射到服务内/data,该目录即全部持久化数据) - 示例密码:预算文件密码
q1w2e3r4(演示环境专用口令) - 查询/实测日期:2026-10-28
💡 说明:Actual 的组成是「客户端(浏览器/桌面 App)+ sync-server(同步服务器)」。用 Docker 部署时,一个实例同时提供 sync-server 接口与 Web 客户端静态页面,浏览器访问一个地址即可完成全部记账操作。
二、镜像选型说明
官方文档明确:Actual 服务器镜像同时发布在 Docker Hub(actualbudget/actual-server)与 GitHub Container Registry(ghcr.io/actualbudget/actual),两者功能一致,任选其一。镜像标签矩阵如下(官方文档口径):
| 标签 | 说明 | 适用场景 |
|---|---|---|
latest | 指向最近一次正式发布(本篇实测即 v26.10.0) | 推荐,生产自托管首选 |
latest-alpine | latest 的 Alpine 基础版,体积更小 | 低功耗设备(NAS/树莓派) |
nightly | master 分支每次提交后自动构建 | 尝鲜新特性,可能有缺陷 |
nightly-alpine | nightly 的 Alpine 版 | 同上,低功耗设备 |
选型建议:生产与长期使用一律 latest(或锁定到具体版本号如 actual-server:v26.10.0,升级时改标签);nightly 仅用于提前验证新特性且必须配好备份。
本篇实测取用 actualbudget/actual-server:latest,拉取后核对镜像摘要与关键元数据:
# 拉取官方镜像(网络不通时可走代理,见参考链接官方文档)
docker pull actualbudget/actual-server:latest
实测回显(输出节选):
latest: Pulling from actualbudget/actual-server
Digest: sha256:24645e971da6bb1a1f953d6859e307a0b29be1e8b4130a35b3220c209b5b860c
Status: Downloaded newer image for actualbudget/actual-server:latest
docker.io/actualbudget/actual-server:latest
# 核对入口进程、启动命令与默认端口
docker inspect actualbudget/actual-server:latest \
--format '{{json .Config.Entrypoint}} | {{json .Config.Cmd}} | {{json .Config.ExposedPorts}} | {{json .Config.WorkingDir}}'
实测回显:
["/usr/bin/tini","-g","--"] | ["node","app.js"] | {"5006/tcp":{}} | "/app"
💡 说明:入口是
tini -g --(init 进程,负责把停止信号转发给整个进程组后优雅退出),启动命令node app.js,默认暴露 5006 端口,工作目录/app。这解释了为什么停止服务时日志会显示正常的退出流程——信号处理由 tini 托管。
三、前置条件
开始部署前确认以下条件全部满足:
- 服务器已安装 Docker 与 Compose 插件,且当前用户有 docker 权限:
docker version --format '{{.Server.Version}}' && docker compose version
实测回显:
28.3.3
Docker Compose version v2.38.2
- 端口未被占用:本篇用宿主机 8129 端口,确认无占用:
ss -ltn | grep ':8129' || echo 'port 8129 free'
实测回显:
port 8129 free
- 数据目录已创建(也可交给 Docker 自动创建,但显式创建便于控制属主):
mkdir -p /opt/actual/data
- 服务器时间准确:记账数据强依赖日期(交易、预算月份),NTP 已同步的服务器可避免"交易记到昨天"的问题。
四、部署步骤
本章从官方 Compose 编排出发生成可直接使用的配置文件,逐条执行并验证。
4.1 官方 Compose 编排原文
官方仓库 packages/sync-server/docker-compose.yml 提供了标准编排(以下为官方文件原文,2026-10-28 取自 master 分支):
services:
actual_server:
image: docker.io/actualbudget/actual-server:latest
ports:
# This line makes Actual available at port 5006 of the device you run the server on,
# i.e. http://localhost:5006. You can change the first number to change the port, if you want.
- '5006:5006'
environment:
# Uncomment any of the lines below to set configuration options.
# - ACTUAL_HTTPS_KEY=/data/selfhost.key
# - ACTUAL_HTTPS_CERT=/data/selfhost.crt
# - ACTUAL_PORT=5006
# - ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB=20
# - ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB=50
# - ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB=20
# See all options and more details at https://actualbudget.org/docs/config/
# !! If you are not using any of these options, remove the 'environment:' tag entirely.
volumes:
# Change './actual-data' below to the path to the folder you want Actual to store its data in on your server.
# '/data' is the path Actual will look for its files in by default, so leave that as-is.
- ./actual-data:/data
healthcheck:
# Enable health check for the instance
test: ['CMD-SHELL', 'node scripts/health-check.js']
# health check using self signed certs
# test: ['CMD-SHELL', 'NODE_EXTRA_CA_CERTS=/data/selfhost.crt node scripts/health-check.js']
interval: 60s
timeout: 10s
retries: 3
start_period: 20s
restart: unless-stopped
关键点解读:服务只挂一个卷(宿主数据目录 → /data),官方健康检查命令是 node scripts/health-check.js(镜像内自带该脚本),重启策略 unless-stopped。官方文件默认把 5006:5006 直接暴露,生产环境通常要改宿主端口。
4.2 落地本次实测的 Compose 文件
结合实测环境做三处适配:宿主端口改 8129、数据目录落 /opt/actual/data、显式指定项目名与独立网络(避免与其他服务同名互删)。在工作目录创建 /opt/actual/docker-compose.yml:
name: actual-v26.10.0-stack
services:
actual-v26.10.0-server:
image: actualbudget/actual-server:latest
container_name: actual-v26.10.0-server
ports:
- '8129:5006'
environment:
- ACTUAL_HOSTNAME=0.0.0.0
- ACTUAL_PORT=5006
volumes:
- /opt/actual/data:/data
networks:
- actual-net
healthcheck:
test: ['CMD-SHELL', 'node scripts/health-check.js']
interval: 60s
timeout: 10s
retries: 3
start_period: 20s
restart: unless-stopped
networks:
actual-net:
name: actual-v26.10.0-net
driver: bridge
ipam:
config:
- subnet: 10.89.56.0/24
逐项说明:
name: actual-v26.10.0-stack:Compose 项目名显式固化,避免目录名冲突导致误操作其他项目;ACTUAL_HOSTNAME=0.0.0.0:显式监听所有网卡(默认值::在多数系统同样可达,写显更便于排查);ports: '8129:5006':宿主 8129 → 服务 5006,浏览器访问http://<服务器IP>:8129;volumes: /opt/actual/data:/data:全部持久化数据的唯一落点,备份只需备份这个目录;subnet: 10.89.56.0/24:显式网段,防止与其他 Docker 网络的地址池冲突(按服务器实际空闲网段调整)。
4.3 启动与状态确认
cd /opt/actual && docker compose up -d
实测回显:
Network actual-v26.10.0-net Created
Container actual-v26.10.0-server Creating
Container actual-v26.10.0-server Created
Container actual-v26.10.0-server Starting
Container actual-v26.10.0-server Started
等待约 10 秒后确认状态(官方健康检查 start_period: 20s 内会完成首次探测):
cd /opt/actual && docker compose ps
实测回显:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
actual-v26.10.0-server actualbudget/actual-server:latest "/usr/bin/tini -g --…" actual-v26.10.0-server 8 seconds ago Up 8 seconds (healthy) 0.0.0.0:8129->5006/tcp, [::]:8129->5006/tcp
STATUS 列出现 (healthy) 即官方健康检查已通过。
4.4 启动日志与 HTTP 探活
cd /opt/actual && docker compose logs --no-color
实测回显(首次启动完整日志):
actual-v26.10.0-server | Checking if there are any migrations to run for direction "up"...
actual-v26.10.0-server | Migrations: DONE
actual-v26.10.0-server | Running in production mode - Serving static React app
actual-v26.10.0-server | Listening on 0.0.0.0:5006...
四行日志分别对应:数据库迁移检查(首次启动自动执行迁移)→ 生产模式 → 静态 Web 页面 → 监听就绪。随后 HTTP 探活:
curl -sS -o /dev/null -w 'GET / -> %{http_code} (%{size_download} bytes)\n' http://127.0.0.1:8129/
curl -sS -w '\nGET /health -> %{http_code}\n' http://127.0.0.1:8129/health
curl -sS -o /dev/null -w 'GET /api/health -> %{http_code}\n' http://127.0.0.1:8129/api/health
实测回显:
GET / -> 200 (4256 bytes)
{"status":"UP"}
GET /health -> 200
GET /api/health -> 200
三个探活点全部 200:/ 是 Web 客户端首页(<title>Actual</title>),/health 与 /api/health 返回 {"status":"UP"}。浏览器打开 http://<服务器IP>:8129/ 应能看到 Actual 的登录/初始化界面。
4.5 初始化:设置服务器密码(bootstrap)
首次访问时服务器处于"未引导"状态,通过官方接口确认并完成初始化(以下步骤与浏览器界面的初始化向导等价,全部为 sync-server 官方 API):
# 查询引导状态
curl -sS http://127.0.0.1:8129/account/needs-bootstrap; echo
实测回显(全新实例):
{"status":"ok","data":{"bootstrapped":false,"loginMethod":"password","availableLoginMethods":[],"multiuser":false}}
bootstrapped:false 表示还没有设置过密码。此时直接登录会失败,正好验证了错误路径:
# 未 bootstrap 时直接登录
curl -sS -X POST http://127.0.0.1:8129/account/login \
-H 'Content-Type: application/json' -d '{"password":"q1w2e3r4"}'; echo
实测回显:
{"status":"error","reason":"invalid-password"}
⚠️ 注意:看到
invalid-password不要误以为密码错了——在全新实例上这是"服务器尚未初始化密码"的表现,正确做法是先走 bootstrap。
执行 bootstrap 设置初始密码(本示例用演示口令 q1w2e3r4,生产环境请换强密码):
curl -sS -X POST http://127.0.0.1:8129/account/bootstrap \
-H 'Content-Type: application/json' -d '{"password":"q1w2e3r4"}'; echo
实测回显:
{"status":"ok","data":{"token":"d43b7717-e7a7-409e-a74b-9df059ee78ae"}}
bootstrap 成功并直接返回会话 token。再次登录可拿到相同 token(登录路径验证):
curl -sS -X POST http://127.0.0.1:8129/account/login \
-H 'Content-Type: application/json' -d '{"password":"q1w2e3r4"}'; echo
实测回显:
{"status":"ok","data":{"token":"d43b7717-e7a7-409e-a74b-9df059ee78ae"}}
curl -sS http://127.0.0.1:8129/account/needs-bootstrap; echo
实测回显:
{"status":"ok","data":{"bootstrapped":true,"loginMethod":"password","availableLoginMethods":[{"method":"password","active":1,"displayName":"Password"}],"multiuser":false}}
bootstrapped:true 且登录方式列表中出现 password——服务器密码已生效。此后浏览器访问 http://<服务器IP>:8129/,输入该密码即可进入工作区;API/桌面客户端连接时也使用同一密码。
4.6 创建第一个预算文件并验证端到端可用
部署完成的最终验证是"真的能记账"。Actual 官方提供 Node.js API 客户端库 @actual-app/api(版本与服务器严格同步),用它完成"建预算 → 建分类 → 建账户 → 记一笔 → 查询"的闭环。
准备一个工作目录并安装客户端库(版本号与服务器一致,26.10.0):
mkdir -p /opt/actual/api-test && cd /opt/actual/api-test
npm init -y && npm install @actual-app/api@26.10.0
实测回显(输出节选):
added 1 package in 4s
⚠️ 注意:
@actual-app/api的版本必须与 sync-server 版本一致(v26.10.0 对 26.10.0),版本错配会在连接同步时出现协议不兼容报错。同时该库依赖原生模块 better-sqlite3,运行机器的 libstdc++ 需较新(见 11.3 排障实录)。
编写初始化脚本 /opt/actual/api-test/setup-budget.js:
const actual = require('@actual-app/api');
(async () => {
await actual.init({
serverURL: 'http://127.0.0.1:8129',
password: 'q1w2e3r4',
dataDir: '/opt/actual/api-test/local-data', // 客户端本地缓存目录,需预先存在
});
// 1. 创建预算文件(v26.10.0 中新建预算走 runImport,空回调即空预算)
await actual.runImport('家庭预算2026', async () => {});
console.log('budgets:', JSON.stringify(await actual.getBudgets()));
// 2. 建分类组与分类
const groupId = await actual.createCategoryGroup({ name: '日常支出' });
const catFood = await actual.createCategory({ name: '餐饮', group_id: groupId, is_income: false, hidden: false });
// 3. 建账户(期初余额 100.00 元,金额单位为"分")
const acc = await actual.createAccount({ name: '现金钱包', offbudget: false, closed: false }, 10000);
// 4. 记一笔支出(-28.50 元,分类"餐饮")
const r = await actual.addTransactions(acc, [
{ date: '2026-10-28', payee_name: '楼下超市', category: catFood, amount: -2850, notes: '买菜' },
]);
console.log('addTransactions:', JSON.stringify(r));
// 5. 查询验证
const txns = await actual.getTransactions(acc, '2026-10-01', '2026-10-31');
console.log('txn count:', txns.length);
console.log('balance(cent):', await actual.getAccountBalance(acc));
await actual.shutdown();
})().catch(e => { console.error('FATAL:', e.message); process.exit(1); });
执行:
cd /opt/actual/api-test && node setup-budget.js
实测回显(输出节选,已略去库内部加载日志):
budgets: [{"id":"----2026-09e3d87","cloudFileId":"29b70409-25fa-47ba-b410-6c553ed0a32a","groupId":"caa22e62-9d2e-48d9-a781-b2b1455e38a1","name":"家庭预算2026"}, ...]
addTransactions: "ok"
txn count: 2
balance(cent): 7150
逐项解读:预算文件「家庭预算2026」已创建并同步到服务器(cloudFileId 即预算文件在服务器侧的 ID);记账返回 ok;账户里出现 2 笔交易(1 笔期初余额 + 1 笔买菜支出);余额 7150 分 = 期初 100.00 − 支出 28.50 = 71.50 元,数字自洽。至此部署的端到端验证闭环完成。
⚠️ 注意:
runImport每执行一次都会新建一个预算文件,重复运行脚本会出现多个同名预算(见 11.4)。重跑数据初始化前,先在 Web 界面设置里删除旧预算文件或更换预算名。
五、数据持久化说明
Actual 的持久化设计非常简单:一个数据目录就是全部数据。首次启动与建预算后实查 /opt/actual/data:
find /opt/actual/data -maxdepth 2 | sort
实测回显(初始化预算后):
/opt/actual/data
/opt/actual/data/.migrate
/opt/actual/data/server-files
/opt/actual/data/server-files/account.sqlite
/opt/actual/data/user-files
/opt/actual/data/user-files/file-29b70409-25fa-47ba-b410-6c553ed0a32a.blob
/opt/actual/data/user-files/group-caa22e62-9d2e-48d9-a781-b2b1455e38a1.sqlite
逐项说明:
| 路径 | 作用 |
|---|---|
.migrate | 数据库迁移标记文件 |
server-files/account.sqlite | 服务器账号库:密码哈希、预算文件清单、会话 token |
user-files/file-<cloudFileId>.blob | 预算文件本体(二进制 blob),每个预算文件一个 |
user-files/group-<groupId>.sqlite | 同步组数据库,支撑多端消息式同步 |
💡 说明:备份策略由此直接得出——打包
/opt/actual/data整个目录即完整备份(含账号与全部预算文件);恢复时停服、放回目录、重启即可。实际备份演练见本专栏运维篇。
验证"数据真的落在宿主目录":停掉服务再启动,预算仍在。
cd /opt/actual && docker compose restart && sleep 5 \
&& curl -sS http://127.0.0.1:8129/account/needs-bootstrap; echo
实测回显:
actual-v26.10.0-server Restarting
actual-v26.10.0-server Started
{"status":"ok","data":{"bootstrapped":true,...}}
重启后 bootstrapped:true 保持,账号与预算数据完好。
六、环境变量表
Actual 服务端的全部配置均可通过环境变量注入(优先级:环境变量 > config.json > 默认值)。以下为常用项(官方配置文档口径,标注默认值):
| 环境变量 | 默认值 | 说明 |
|---|---|---|
ACTUAL_PORT | 5006 | 监听端口 |
ACTUAL_HOSTNAME | :: | 监听地址(含 IPv4/IPv6) |
ACTUAL_DATA_DIR | /data(存在时) | 数据总目录,其下派生 server-files/user-files |
ACTUAL_SERVER_FILES | /data/server-files | 服务器账号库目录 |
ACTUAL_USER_FILES | /data/user-files | 预算文件目录 |
ACTUAL_CONFIG_PATH | /data/config.json(存在时) | config.json 路径 |
ACTUAL_WEB_ROOT | 包内 @actual-app/web | 前端静态资源目录(自定义前端用) |
ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB | 20 | 普通上传文件大小上限(MB) |
ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB | 20 | 同步文件大小上限(MB) |
ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB | 50 | 加密同步文件大小上限(MB) |
ACTUAL_HTTPS_KEY / ACTUAL_HTTPS_CERT | 无 | HTTPS 私钥/证书路径 |
ACTUAL_LOGIN_METHOD | password | 登录方式:password/header/openid |
ACTUAL_ALLOWED_LOGIN_METHODS | password,header,openid | 允许的登录方式(逗号分隔) |
ACTUAL_TRUSTED_PROXIES | 内网段列表 | 信任的反向代理网段(影响限流的客户端 IP 判定) |
ACTUAL_OPENID_CLIENT_SECRET_FILE | 无 | 以文件形式注入 OpenID 密钥(_FILE 后缀优先) |
ACTUAL_SIMPLEFIN_ALLOWED_NETWORKS | 无 | 允许 SimpleFIN 访问的内网桥地址 |
💡 技术拓展:Actual 的登录认证不止密码一种。
loginMethod三种可选方式对比——
方式 原理 适用场景 password(默认)按预算文件密码校验 个人自托管标准形态 header网关注入 x-actual-password头自动登录已有统一认证入口,由反代注入凭据(官方提示配错有安全风险) openid对接 OIDC 身份提供方(预览特性) 团队/家庭统一账号体系 本文实测采用默认
password;无论哪种方式,都建议通过ACTUAL_ALLOWED_LOGIN_METHODS显式收敛可用方式。
七、配置参数说明
环境变量之外,Actual 支持 config.json 配置文件:启动时按 ACTUAL_CONFIG_PATH(未设置则在 /data 或安装目录)查找。键名与环境变量的对应关系是 camelCase ↔ ACTUAL_ 大写(如 dataDir ↔ ACTUAL_DATA_DIR),但并非一一对应(如 https 是对象键,只能写进 config.json)。三层优先级:环境变量 > config.json > 默认值。
一个最小 config.json 示例(放在 /opt/actual/data/config.json,与数据同目录持久化):
{
"port": 5006,
"hostname": "0.0.0.0",
"upload": {
"fileSizeLimitMB": 25,
"fileSyncSizeLimitMB": 20,
"syncEncryptedFileSizeLimitMB": 50
}
}
逐项说明:port/hostname 与环境变量等价(二选一即可,同时设置时环境变量生效);upload 组键对应三个上传上限环境变量,调大 fileSizeLimitMB 的典型场景是导入超大的 OFX/CSV 银行导出文件。
验证配置生效最直接的方式是看启动日志中的监听地址变化(例如把端口改成 5007 后,日志会显示 Listening on 0.0.0.0:5007...)。
八、配置文件挂载
把 config.json 与数据目录一起挂载进服务即可生效。在 Compose 文件的 volumes 中追加一行:
volumes:
- /opt/actual/data:/data
- /opt/actual/config.json:/data/config.json:ro
说明:ro(只读)防止服务端改写配置;config.json 属于配置而非数据,挂载为只读更安全。修改配置后执行 docker compose restart 生效,并观察日志确认新值被加载:
cd /opt/actual && docker compose restart && docker compose logs --no-color | tail -5
实测回显形态(端口场景示例):
actual-v26.10.0-server | Running in production mode - Serving static React app
actual-v26.10.0-server | Listening on 0.0.0.0:5006...
九、等价 docker run 命令
不用 Compose 时,官方文档给出的等价启动命令为(官方原文,路径占位按本篇环境代入):
docker run --pull=always --restart=unless-stopped -d \
-p 8129:5006 \
-v /opt/actual/data:/data \
--name my_actual_budget \
actualbudget/actual-server:latest
逐参数说明:
--pull=always:每次启动都拉取最新镜像(生产建议去掉,避免升级不可控);--restart=unless-stopped:崩溃自动拉起,人工停止除外;-d:后台运行;-p 8129:5006:宿主端口映射;-v /opt/actual/data:/data:数据目录持久化(必须挂,否则重建后数据全丢);--name my_actual_budget:实例名。
对应的官方升级流程为「停止 → 删除 → 重新 run」三连(数据在宿主目录,删除实例不影响数据)。日常运维更推荐 Compose 方式(docker compose pull && docker compose up -d 一条完成)。
十、常用运维命令
以下命令均在 /opt/actual(Compose 文件所在目录)执行:
# 查看实例状态(healthy 为健康)
docker compose ps
# 查看日志(实时跟踪加 -f)
docker compose logs --no-color | tail -20
# 重启服务(改配置后)
docker compose restart
# 升级到最新版(官方推荐姿势)
docker compose pull && docker compose up -d
# 停止与启动
docker compose stop && docker compose start
# 彻底移除实例(数据目录 /opt/actual/data 保留)
docker compose down
升级后验证版本(两步:探活 + API 查版本):
curl -sS http://127.0.0.1:8129/health; echo
node -e "require('@actual-app/api').init({serverURL:'http://127.0.0.1:8129',password:'q1w2e3r4',dataDir:'/opt/actual/api-test/local-data'}).then(async a=>{console.log(await require('@actual-app/api').getServerVersion());await require('@actual-app/api').shutdown()})"
实测回显:
{"status":"UP"}
{"version":"26.10.0"}
💡 说明:升级后若
@actual-app/api报同步协议错误,通常是客户端库与新服务器版本不一致——把库升级到与服务器相同的版本号即可(npm install @actual-app/api@<新版本>)。
十一、常见问题(FAQ)
11.1 登录报 invalid-password,但密码明明是对的
现象:全新实例上 POST /account/login 返回 {"status":"error","reason":"invalid-password"}(本篇 4.5 实测)。
原因:服务器从未 bootstrap,账号库里没有任何密码记录。
解法:先执行 bootstrap(4.5 的 POST /account/bootstrap)设置初始密码,之后 login 正常。浏览器界面上这一步对应首次访问的"设置密码"向导。
11.2 启动失败:端口被占用
现象:docker compose up -d 报端口绑定失败(bind: address already in use)。
定位:ss -ltnp | grep ':8129' 找到占用进程。
解法:换宿主端口(改 Compose 中 '8129:5006' 左侧数字)或停掉占用进程后重新 docker compose up -d。
11.3 @actual-app/api 运行报 GLIBCXX_3.4.29 not found
现象:在服务器上运行 API 客户端脚本时报错(实测原文):
[Exception] Error: libstdc++.so.6: version `GLIBCXX_3.4.29' not found (required by node_modules/better-sqlite3/prebuilds/linux-x64.node)
code: 'ERR_DLOPEN_FAILED'
原因:@actual-app/api 依赖原生模块 better-sqlite3,其预编译产物要求较新的 libstdc++(GLIBCXX_3.4.29+);运行环境的 LD_LIBRARY_PATH 或系统库过旧时动态加载失败。
解法(实测有效):确认系统自带 libstdc++ 含该符号(strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep GLIBCXX_3.4.29),再以系统库路径运行脚本:
LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu node setup-budget.js
若系统库本身过旧,则升级 libstdc++6 软件包,或在 API 工作目录重编译原生模块(npm rebuild better-sqlite3,需 g++/make/python3)。
11.4 服务器上出现多个同名预算文件
现象:getBudgets() 或 Web 界面预算列表里出现多个「家庭预算2026」。
原因:新建预算的操作(API runImport 或界面"创建新预算文件")每次都会生成一个新预算文件,重跑初始化脚本就会多出一个。
解法:确认要保留的预算后,在 Web 界面「设置 → 高级」中删除多余预算文件;或查询各预算的 syncId,备份后只保留一个。预防:初始化脚本只在空服务器上跑一次,之后用 downloadBudget 拉取既有预算。
11.5 API 脚本报 Budget "..." not found
现象(实测原文):
Budget "00000000-0000-0000-0000-000000000000" not found. Check the sync id of your budget in the Advanced section of the settings page.
原因:downloadBudget 传入的 syncId 在服务器上不存在(空服务器或 ID 写错)。
解法:先 getBudgets() 列出服务器上真实预算拿到 ID;空服务器则先用 runImport(或 Web 界面)创建预算。
11.6 健康检查一直 unhealthy
现象:docker compose ps 中状态为 unhealthy 或 starting 久久不变。
定位:docker compose logs --no-color | tail -20 看是否卡在迁移(Migrations 行未出现 DONE)或监听失败;curl http://127.0.0.1:<端口>/health 人工复现健康检查。
解法:多为数据目录属主不对(容器内进程写不了 /data)——chown -R 1000:1000 /opt/actual/data 后重启;或迁移被中断(删掉 .migrate 后重启让其重跑)。
十二、参考链接
- 官方 Docker 部署文档:https://actualbudget.org/docs/install/docker
- 官方服务端配置文档:https://actualbudget.org/docs/config/
- 官方安装总览(功能矩阵):https://actualbudget.org/docs/install/
- 官方 Compose 文件原文:https://github.com/actualbudget/actual/blob/master/packages/sync-server/docker-compose.yml
- Docker Hub 镜像页:https://hub.docker.com/r/actualbudget/actual-server
- 站内延伸阅读:《Actual 介绍与选型指南》(架构与选型);配置参数的逐项详解见本专栏《Actual 配置参数详解》篇;备份恢复与多端同步实战见《Actual 常用命令与运维实战》篇。