Frappe HR 16.52.0 Docker Compose 部署指南
Frappe HR 16.52.0 Docker Compose 部署指南:官方分层镜像自建(apps.json secret 传参)、11 服务编排(backend/frontend/websocket/双队列/scheduler/db/双 Redis/configurator/create-site)、4 数据卷持久化、bench 备份与 down -v 全毁后 restore 恢复闭环实测,含 502 排障等 5 个 FAQ。
Frappe HR 16.52.0 Docker Compose 部署指南
目标:在 Docker 环境中以 Compose 方式部署 Frappe HR v16.52.0(含 ERPNext 16.52.1 + Frappe 框架 16.53.0 依赖链),覆盖官方分层镜像自建、11 服务编排、数据持久化、备份与恢复闭环全流程。本文全部命令与回显已在 Docker 29.8.1 + Compose v2 5.5.1 环境实测通过,镜像版本组合:hrms 16.52.0 + erpnext 16.52.1 + frappe 16.53.0。
一、环境信息
- 操作系统:Linux x86_64(内核 5.15+,本文实测 Ubuntu 22.04 宿主)
- Docker:29.8.1(
docker version --format 'Docker {{.Server.Version}}'实测回显:Docker 29.8.1) - Compose:v2 插件 5.5.1(
docker compose version --short实测回显:5.5.1) - 目标版本:Frappe HR v16.52.0(2026-10-22 发布,GitHub Releases 实查),依赖 erpnext v16.52.1 与 frappe v16.53.0(兼容矩阵与版本选型依据见《Ubuntu 22.04 安装 Frappe HR 16.52.0 指南》第二章)
- 实验目录:
/opt/data/frappe-hr/(compose 工作目录),官方部署工具仓库 frappe_docker(本文实测 v4.1.1) - 服务端口:宿主 8150 → 前端 Nginx 8080(部署前用
ss -ltn | grep 8150确认未占用) - 站点名:
frontend(frappe_docker pwd 模板固定站点名,Nginx 以FRAPPE_SITE_NAME_HEADER路由),管理员密码与数据库 root 密码示例统一为q1w2e3r4 - 站内安装篇:《Ubuntu 22.04 安装 Frappe HR 16.52.0 指南》为 bench 直装路径,本篇为容器编排路径,二者选其一即可。
二、镜像选型说明
2.1 无官方 Frappe HR 镜像(实测确认)
Docker Hub 上不存在 frappe/hrms 官方镜像(docker pull frappe/hrms:v16.52.0 实测报 Error response from daemon: error from registry: denied)。官方推荐做法是使用部署工具仓库 frappe_docker 的**分层构建(layered)**方案:以官方 frappe/base:version-16 运行时为基础,用 images/layered/Containerfile 在构建期通过 bench init --apps_path 把 apps.json 里声明的 App 一起编译进镜像。
本文实测组合:
| 镜像 | 版本 | 用途 |
|---|---|---|
| frappe/build:version-16 | 构建阶段 | bench init + yarn build(约 5 分钟) |
| frappe/base:version-16 | 运行阶段 | Python 3.14 + Node 24 + Nginx 1.26.3 运行时 |
| 自建 frappe-hr-hrms:v16.52.0 | 最终产物 | 5.5GB(docker images 实测),含 frappe/erpnext/hrms 三 App |
frappe 官方同时发布 frappe/erpnext:v16 系列镜像(pwd.yml 默认引用),但其只含 erpnext 不含 hrms,装 Frappe HR 必须自建。自建镜像与官方镜像同构(同一 Containerfile 逻辑),后续 hrms 升级只需改 apps.json 的 branch 重新构建。
2.2 克隆部署工具仓库并声明 App 清单
git clone https://github.com/frappe/frappe_docker.git
cd frappe_docker
git checkout v4.1.1
cat > apps.json <<'EOF'
[
{"url": "https://github.com/frappe/erpnext", "branch": "v16.52.1"},
{"url": "https://github.com/frappe/hrms", "branch": "v16.52.0"}
]
EOF
apps.json 是构建期 secret(不落入镜像层),数组顺序即安装顺序,hrms 依赖的 erpnext 必须在列。
2.3 构建镜像(apps.json 以 BuildKit secret 传入)
docker buildx build \
--secret id=apps_json,src=apps.json \
-f images/layered/Containerfile \
-t frappe-hr-hrms:v16.52.0 .
实测回显(节选,构建约 6~9 分钟):
#1 [internal] load build definition from Containerfile
#1 transferring dockerfile: 2.06kB done
#5 [internal] load metadata for docker.io/frappe/build:version-16
#5 DONE 9.1s
#10 [builder 2/2] RUN --mount=type=secret,id=apps_json,target=/opt/frappe/apps.json,uid=1000,gid=0 ...
#10 12.47 Building frappe @ file:///home/frappe/frappe-bench/apps/frappe
#10 70.75 Building erpnext @ file:///home/frappe/frappe-bench/apps/erpnext
...
#18 exporting layers 50.1s done
#18 naming to docker.io/library/frappe-hr-hrms:v16.52.0 done
⚠️ 注意:apps.json 必须用
--secret传入(Containerfile 内以RUN --mount=type=secret消费),不要用--build-arg——build-arg 会把内容写进镜像历史层,凭据与构建清单都会永久留痕。
三、前置条件
- Docker 29.x 与 Compose v2 插件(
docker compose version可执行); - 宿主内存 ≥8GB(11 个服务常驻,backend/queue 各占 300MB~1GB,数据库初始化建站峰值约 2GB);
- 磁盘 ≥15GB(镜像 5.5GB + 数据卷与备份余量);
- 网络可访问 Docker Hub(拉取 frappe/build、frappe/base、mariadb:11.8、redis:6.2-alpine 四个基础镜像)与 GitHub/PyPI(构建期 bench init 拉代码与依赖);
- 端口 8150 未占用(
ss -ltn | grep 8150无输出)。
四、部署步骤
4.1 编排文件
frappe_docker 官方提供 compose.yaml + overrides 组合与免克隆的 pwd.yml 两种用法;pwd.yml 默认镜像不含 hrms,因此本文在 pwd.yml 结构基础上改用 2.3 节自建镜像,落地为单文件 compose.yaml(工作目录 /opt/data/frappe-hr/):
name: frappe-hr-16-stack
x-frappe-image: &frappe-image
image: frappe-hr-hrms:v16.52.0
services:
backend:
<<: *frappe-image
networks:
- frappe_network
restart: unless-stopped
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
environment:
DB_HOST: db
DB_PORT: "3306"
MYSQL_ROOT_PASSWORD: q1w2e3r4
MARIADB_ROOT_PASSWORD: q1w2e3r4
depends_on:
configurator:
condition: service_completed_successfully
configurator:
<<: *frappe-image
networks:
- frappe_network
restart: "no"
entrypoint:
- bash
- -c
command:
- >
ls -1 apps > sites/apps.txt;
bench set-config -g db_host $$DB_HOST;
bench set-config -gp db_port $$DB_PORT;
bench set-config -g redis_cache "redis://$$REDIS_CACHE";
bench set-config -g redis_queue "redis://$$REDIS_QUEUE";
bench set-config -g redis_socketio "redis://$$REDIS_QUEUE";
bench set-config -gp socketio_port $$SOCKETIO_PORT;
environment:
DB_HOST: db
DB_PORT: "3306"
REDIS_CACHE: redis-cache:6379
REDIS_QUEUE: redis-queue:6379
SOCKETIO_PORT: "9000"
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
create-site:
<<: *frappe-image
networks:
- frappe_network
restart: "no"
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
entrypoint:
- bash
- -c
command:
- >
wait-for-it -t 120 db:3306;
wait-for-it -t 120 redis-cache:6379;
wait-for-it -t 120 redis-queue:6379;
export start=`date +%s`;
until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] &&
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] &&
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]];
do
echo "Waiting for sites/common_site_config.json to be created";
sleep 5;
if (( `date +%s`-start > 120 )); then
echo "could not find sites/common_site_config.json with required keys";
exit 1
fi
done;
echo "sites/common_site_config.json found";
if [ -d "sites/frontend" ]; then
echo "Site frontend already exists, skipping creation";
else
bench new-site --mariadb-user-host-login-scope='%' --admin-password=q1w2e3r4 --db-root-username=root --db-root-password=q1w2e3r4 --install-app erpnext --set-default frontend;
bench --site frontend install-app hrms;
fi;
db:
image: mariadb:11.8
networks:
- frappe_network
restart: unless-stopped
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
start_period: 5s
interval: 5s
timeout: 5s
retries: 5
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --skip-character-set-client-handshake
environment:
MYSQL_ROOT_PASSWORD: q1w2e3r4
MARIADB_ROOT_PASSWORD: q1w2e3r4
volumes:
- db-data:/var/lib/mysql
frontend:
<<: *frappe-image
networks:
- frappe_network
restart: unless-stopped
depends_on:
- websocket
- backend
command:
- nginx-entrypoint.sh
environment:
BACKEND: backend:8000
FRAPPE_SITE_NAME_HEADER: frontend
SOCKETIO: websocket:9000
UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1
UPSTREAM_REAL_IP_HEADER: X-Forwarded-For
UPSTREAM_REAL_IP_RECURSIVE: "off"
PROXY_READ_TIMEOUT: 120
CLIENT_MAX_BODY_SIZE: 50m
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
ports:
- "8150:8080"
queue-long:
<<: *frappe-image
networks:
- frappe_network
restart: unless-stopped
command:
- bench
- worker
- --queue
- long,default,short
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
environment:
FRAPPE_REDIS_CACHE: redis://redis-cache:6379
FRAPPE_REDIS_QUEUE: redis://redis-queue:6379
depends_on:
configurator:
condition: service_completed_successfully
queue-short:
<<: *frappe-image
networks:
- frappe_network
restart: unless-stopped
command:
- bench
- worker
- --queue
- short,default
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
environment:
FRAPPE_REDIS_CACHE: redis://redis-cache:6379
FRAPPE_REDIS_QUEUE: redis://redis-queue:6379
depends_on:
configurator:
condition: service_completed_successfully
redis-queue:
image: redis:6.2-alpine
networks:
- frappe_network
restart: unless-stopped
volumes:
- redis-queue-data:/data
redis-cache:
image: redis:6.2-alpine
networks:
- frappe_network
restart: unless-stopped
scheduler:
<<: *frappe-image
networks:
- frappe_network
restart: unless-stopped
command:
- bench
- schedule
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
depends_on:
configurator:
condition: service_completed_successfully
websocket:
<<: *frappe-image
networks:
- frappe_network
restart: unless-stopped
command:
- node
- /home/frappe/frappe-bench/apps/frappe/socketio.js
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
depends_on:
configurator:
condition: service_completed_successfully
networks:
frappe_network:
driver: bridge
ipam:
config:
- subnet: 10.89.73.0/24
volumes:
db-data:
sites:
logs:
redis-queue-data:
11 个服务的分工:backend(gunicorn Web 进程,8000)、frontend(Nginx 1.26.3 反代静态资源与 API,8080)、websocket(Node SocketIO 实时通道,9000)、queue-short/queue-long(后台任务队列 worker)、scheduler(定时任务调度)、configurator(一次性:把数据库与 Redis 地址写入 common_site_config.json)、create-site(一次性:建站装 App)、db(MariaDB 11.8)、redis-cache/redis-queue(Redis 6.2 双实例)。
4.2 启动与站点初始化
cd /opt/data/frappe-hr
docker compose up -d
实测回显(节选,一次性服务 configurator/create-site 完成初始化后退出):
Container frappe-hr-16-stack-configurator-1 Exited
Container frappe-hr-16-stack-queue-long-1 Started
Container frappe-hr-16-stack-queue-short-1 Started
Container frappe-hr-16-stack-backend-1 Started
Container frappe-hr-16-stack-websocket-1 Started
Container frappe-hr-16-stack-frontend-1 Started
Container frappe-hr-16-stack-scheduler-1 Started
up -d 后 create-site 需要完成建站 + erpnext + hrms 三个 App 的安装(实测约 6~10 分钟),观察初始化进度:
docker logs -f frappe-hr-16-stack-create-site-1
实测完成回显(尾部):
Installing hrms...
Updating DocTypes for hrms : [========================================] 100%
Setting up Frappe HR...
Patching Existing Data...
rename_field: kra_title not found in table for: Appraisal Template
rename_field: kra_template not found in table for: Appraisal
Thank you for installing Frappe HR!
Updating Dashboard for hrms
💡 说明:
rename_field: ... not found in table是 hrms 补丁对全新站点的正常提示,不是错误。create-site 容器退出码 0(docker inspect -f '{{.State.Status}} {{.State.ExitCode}}' frappe-hr-16-stack-create-site-1实测exited 0)即初始化完成;站点目录已存在时该服务幂等跳过(Site frontend already exists, skipping creation),栈重启不会重复建站。
4.3 部署验证
curl --noproxy "*" -s http://127.0.0.1:8150/api/method/ping
实测回显:
{"message":"pong"}
docker compose -f /opt/data/frappe-hr/compose.yaml ps --format 'table {{.Name}}\t{{.Image}}\t{{.Status}}'
实测回显(configurator/create-site 为一次性服务,完成后退出不显示):
NAME IMAGE STATUS
frappe-hr-16-stack-backend-1 frappe-hr-hrms:v16.52.0 Up 3 minutes
frappe-hr-16-stack-db-1 mariadb:11.8 Up 6 minutes (healthy)
frappe-hr-16-stack-frontend-1 frappe-hr-hrms:v16.52.0 Up About a minute
frappe-hr-16-stack-queue-long-1 frappe-hr-hrms:v16.52.0 Up 3 minutes
frappe-hr-16-stack-queue-short-1 frappe-hr-hrms:v16.52.0 Up 3 minutes
frappe-hr-16-stack-redis-cache-1 redis:6.2-alpine Up 6 minutes
frappe-hr-16-stack-redis-queue-1 redis:6.2-alpine Up 6 minutes
frappe-hr-16-stack-scheduler-1 frappe-hr-hrms:v16.52.0 Up 3 minutes
frappe-hr-16-stack-websocket-1 frappe-hr-hrms:v16.52.0 Up 3 minutes
管理员登录验证(返回 200 即成功):
curl --noproxy "*" -s -o /dev/null -w "%{http_code}\n" -c cookie.txt \
-X POST http://127.0.0.1:8150/api/method/login \
-d "usr=Administrator&pwd=q1w2e3r4"
浏览器访问 http://<服务器IP>:8150,用 Administrator / q1w2e3r4 登录即进入 HR Desk。
五、数据持久化说明
全部持久化数据落在 4 个 named volume(docker volume ls | grep frappe-hr-16-stack 实测可见):
| 卷名 | 容器内挂载点 | 内容 | 重要性 |
|---|---|---|---|
| frappe-hr-16-stack_sites | /home/frappe/frappe-bench/sites | 站点配置 site_config.json、公共/私有文件、备份目录 | ★★★ 核心 |
| frappe-hr-16-stack_db-data | /var/lib/mysql | MariaDB 全部数据库(站点数据主体) | ★★★ 核心 |
| frappe-hr-16-stack_logs | /home/frappe/frappe-bench/logs | bench 各进程日志 | ★ |
| frappe-hr-16-stack_redis-queue-data | /data | 队列任务持久化(重启不丢待处理任务) | ★★ |
⚠️ 注意:
redis-cache实例未挂卷,属设计内行为(缓存重启即失效重建);App 代码(apps/)与前端构建产物(assets/)固化在镜像内,不落卷——升级版本靠换镜像,不靠改卷。
六、环境变量表
| 变量 | 所在服务 | 示例值 | 说明 |
|---|---|---|---|
| DB_HOST | backend/configurator | db | 数据库主机名(服务名) |
| DB_PORT | backend/configurator | 3306 | 数据库端口 |
| MYSQL_ROOT_PASSWORD / MARIADB_ROOT_PASSWORD | backend/db | q1w2e3r4 | 数据库 root 密码(建站与运维用) |
| REDIS_CACHE | configurator | redis-cache:6379 | 缓存 Redis 地址 |
| REDIS_QUEUE | configurator | redis-queue:6379 | 队列 Redis 地址(socketio 同用此实例) |
| SOCKETIO_PORT | configurator | 9000 | websocket 服务端口 |
| BACKEND | frontend | backend:8000 | Nginx 反代上游 |
| FRAPPE_SITE_NAME_HEADER | frontend | frontend | 请求路由到的站点名 |
| SOCKETIO | frontend | websocket:9000 | 实时通道上游 |
| PROXY_READ_TIMEOUT | frontend | 120 | 反代读超时(秒) |
| CLIENT_MAX_BODY_SIZE | frontend | 50m | 上传体积上限 |
| FRAPPE_REDIS_CACHE / FRAPPE_REDIS_QUEUE | queue-* | redis://redis-cache:6379 等 | worker 直连地址 |
💡 说明:compose 文件中
$$DB_HOST双写是 Compose 的变量转义——单$会被 Compose 自身展开,双$$才能落到 shell 里展开为服务环境变量。
七、配置参数说明
容器形态下 frappe 配置分两层:
- common_site_config.json(bench 全局):由 configurator 服务在每次栈启动时自动写入 sites 卷,键包括
db_host/db_port/redis_cache/redis_queue/redis_socketio/socketio_port。改数据库/Redis 地址应改 compose 环境变量后重建栈,不建议手改该文件(重启会被 configurator 覆盖)。 - site_config.json(站点级):create-site 建站时自动生成,含
db_name(如_2d48113933dc865b)、db_password(自动生成随机凭据)、installed_apps(frappe/erpnext/hrms)。查看命令:
docker exec frappe-hr-16-stack-backend-1 cat /home/frappe/frappe-bench/sites/frontend/site_config.json
实测回显(db_password 为自动生成敏感值,以 ${DB_PASSWORD} 示意):
{
"db_name": "_2d48113933dc865b",
"db_password": "${DB_PASSWORD}",
"db_type": "mariadb",
"encryption_key": "${ENCRYPTION_KEY}",
"installed_apps": [
{
"name": "frappe",
"path": "apps/frappe",
...
}
]
}
参数逐项含义与 HR 业务设置(HR Settings/Payroll Settings)的实测详解见《Frappe HR 16.52.0 配置参数详解》。
八、配置文件挂载
sites卷同时挂到 backend/frontend/configurator/create-site/scheduler/websocket/queue-*:站点配置与文件对所有服务可见,Nginx 直接从卷内读sites/assets提供静态资源(不回源 backend)。- 若需把站点文件落在宿主目录(便于外部备份),可将
sites:/home/frappe/frappe-bench/sites换成 bind mount 形式(如./data/sites:/home/frappe/frappe-bench/sites);切换时先docker compose cp迁移旧卷内容再重建,避免空目录覆盖。 - MariaDB 配置通过
command追加参数实现(utf8mb4 三参数,见 4.1 编排文件 db 服务),无需额外挂载 my.cnf。
九、等价 docker run 说明
Frappe HR 是 11 服务强依赖编排(gunicorn/Nginx/SocketIO/双队列/调度器/双 Redis/MariaDB + 2 个一次性初始化服务),官方不提供也不维护单容器形态;逐服务手工 docker run 需要手工对齐网络别名、共享卷与启动顺序,仅适合理解结构,不建议用于实际部署。db 服务的等价单容器形态实测如下(healthcheck 依赖宿主镜像内置的 healthcheck.sh):
docker run -d --name frappe-hr-mariadb-runtest \
-e MYSQL_ROOT_PASSWORD=q1w2e3r4 -e MARIADB_ROOT_PASSWORD=q1w2e3r4 \
mariadb:11.8 \
--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci \
--skip-character-set-client-handshake
docker exec frappe-hr-mariadb-runtest healthcheck.sh --connect --innodb_initialized
实测退出码 0(healthcheck.sh 校验通过、无输出即健康),与 compose db 服务的健康检查行为一致。其余 10 个服务的依赖关系呈网状(backend 依赖 configurator 完成态、frontend 依赖 backend+websocket),手工等价化的收益为零,本文不再展开。
十、常用运维命令
10.1 日常启停与日志
docker compose -f /opt/data/frappe-hr/compose.yaml ps # 服务状态
docker compose -f /opt/data/frappe-hr/compose.yaml logs -f backend # 跟随 backend 日志
docker compose -f /opt/data/frappe-hr/compose.yaml restart backend # 重启单服务
docker compose -f /opt/data/frappe-hr/compose.yaml down # 停栈(保留数据卷)
docker compose -f /opt/data/frappe-hr/compose.yaml down -v # 停栈并删卷(数据全失,慎用)
10.2 bench 运维入口
所有 bench 命令经 backend 容器执行:
docker exec -it frappe-hr-16-stack-backend-1 bash -c 'cd /home/frappe/frappe-bench && bench --site frontend migrate'
docker exec -it frappe-hr-16-stack-backend-1 bash -c 'cd /home/frappe/frappe-bench && bench --site frontend clear-cache'
docker exec -it frappe-hr-16-stack-backend-1 bash -c 'cd /home/frappe/frappe-bench && bench version'
实测 bench version 回显:
erpnext 16.52.1 HEAD (ade850b)
frappe 16.53.0 HEAD (e8d41dd)
hrms 16.52.0 HEAD (5c72c06)
10.3 备份(bench 一条命令出全套产物)
docker exec frappe-hr-16-stack-backend-1 bash -c \
'cd /home/frappe/frappe-bench && bench --site frontend backup --with-files'
实测回显:
Backup Summary for frontend at 2026-10-23 12:05:07.342604
Config : /home/frappe/frappe-bench/sites/frontend/private/backups/20261023_120505-frontend-site_config_backup.json 186.0B
Database: /home/frappe/frappe-bench/sites/frontend/private/backups/20261023_120505-frontend-database.sql.gz 1.1MiB
Public : /home/frappe/frappe-bench/sites/frontend/private/backups/20261023_120505-frontend-files.tar 10.0KiB
Private : /home/frappe/frappe-bench/sites/frontend/private/backups/20261023_120505-frontend-private-files.tar 10.0KiB
Backup for Site frontend has been successfully completed with files
产物在 sites 卷的 private/backups/ 下,拷出到宿主(备份文件名以日期时间开头):
mkdir -p /opt/data/frappe-hr/backups
docker cp frappe-hr-16-stack-backend-1:/home/frappe/frappe-bench/sites/frontend/private/backups/. \
/opt/data/frappe-hr/backups/
10.4 恢复闭环实测(down -v 全毁 → 重建 → restore)
以下为完整演练实录,验证备份能把站点数据完整带回。
第一步,模拟灾难,删栈删卷:
cd /opt/data/frappe-hr
docker compose down -v
实测回显(4 个卷全部移除):
Container frappe-hr-16-stack-scheduler-1 Removed
Volume frappe-hr-16-stack_sites Removing
Volume frappe-hr-16-stack_logs Removing
Volume frappe-hr-16-stack_redis-queue-data Removing
Volume frappe-hr-16-stack_db-data Removing
第二步,重建栈(create-site 会重新初始化一个全新空站点,含三 App 安装,约 6~10 分钟),完成后确认数据已清空——实测全新站点 Leave Type 列表只剩 5 个默认值,业务数据(实测录入的 年假(实测) 休假类型)随旧卷销毁:
第三步,备份产物拷入 backend 容器:
docker cp /opt/data/frappe-hr/backups frappe-hr-16-stack-backend-1:/tmp/b2-restore
第四步,恢复数据库与公共/私有文件(⚠️ --db-root-password 是 restore 子命令的选项,必须放在 SQL 路径之后):
docker exec frappe-hr-16-stack-backend-1 bash -c \
'cd /home/frappe/frappe-bench && bench --site frontend restore \
/tmp/b2-restore/20261023_114144-frontend-database.sql.gz \
--db-root-password q1w2e3r4 \
--with-public-files /tmp/b2-restore/20261023_114144-frontend-files.tar \
--with-private-files /tmp/b2-restore/20261023_114144-frontend-private-files.tar'
实测回显:
App frappe already installed
*** Scheduler is disabled ***
Site frontend has been restored with files
第五步,清缓存并重启业务进程(队列/调度器持着旧缓存连接):
docker exec frappe-hr-16-stack-backend-1 bash -c \
'cd /home/frappe/frappe-bench && bench --site frontend clear-cache'
docker restart frappe-hr-16-stack-backend-1 frappe-hr-16-stack-queue-short-1 \
frappe-hr-16-stack-queue-long-1 frappe-hr-16-stack-scheduler-1 frappe-hr-16-stack-websocket-1
docker restart frappe-hr-16-stack-frontend-1
第六步,端到端验证(ping + 管理员登录 + 业务数据回归):
curl --noproxy "*" -s http://127.0.0.1:8150/api/method/ping
实测回显:
{"message":"pong"}
管理员登录实测返回 200;实测查询 Leave Type 列表,年假(实测) 已随备份回归:
{"message":[{"name":"Casual Leave"},{"name":"Compensatory Off"},{"name":"Sick Leave"},
{"name":"Privilege Leave"},{"name":"Leave Without Pay"},{"name":"年假(实测)"}]}
💡 说明:restore 会用备份中的库名重建数据库并回写 site_config 的
db_name(实测库由重建时的新随机名变回备份时的_2d48113933dc865b);create-site 重建的空站点骨架(含三 App)+ 备份数据 = 完整恢复。
十一、常见问题(FAQ)
Q1:重启 backend 后站点 502(connect() failed (111: Connection refused)),怎么办?
现象(实测 frontend 的 Nginx 错误日志):
2026/10/23 06:33:15 [error] 16#16: *9 connect() failed (111: Connection refused) while connecting to upstream, ... upstream: "http://10.89.73.8:8000/api/method/ping"
定位:Nginx 启动时解析 backend 域名并缓存 IP;backend 容器重启后 compose 网络可能给它分配新 IP,Nginx 仍连旧地址被拒。解决:重启 frontend 容器刷新解析:
docker restart frappe-hr-16-stack-frontend-1
实测重启后 /api/method/ping 立即恢复 {"message":"pong"}。同理,重启 db/redis-cache/redis-queue 后建议一并 restart frontend。
Q2:8150 端口启动报 Bind for 0.0.0.0:8150 failed: port is already allocated,怎么办?
宿主端口被其他进程/栈占用。用 ss -ltnp | grep 8150 找到占用者;改 compose.yaml 里 frontend 的 "8150:8080" 为其他空闲端口后 docker compose up -d 重建(数据卷不受影响)。
Q3:create-site 失败退出(ExitCode 非 0),怎么重跑?
看日志定位原因(docker logs frappe-hr-16-stack-create-site-1)。多数情况是依赖服务未就绪(wait-for-it 超时)或半成品站点残留。修好依赖后直接重跑编排内的一次性服务:
docker compose -f /opt/data/frappe-hr/compose.yaml up -d create-site
docker logs -f frappe-hr-16-stack-create-site-1
若站点目录半成品(sites/frontend 存在但库不完整),先 down -v 清卷重来,或进 backend 容器删掉 sites/frontend 目录后再跑 create-site。
Q4:docker pull frappe/hrms 报 pull access denied?
frappe 官方未发布 hrms 镜像(实测确认),必须按第二章用 frappe_docker 的 layered Containerfile 自建;直接拉 frappe/erpnext:v16 只含 ERPNext,装不出 Frappe HR。
Q5:镜像构建时 git/PyPI 拉取失败?
构建期 builder 阶段需要访问 GitHub 与 PyPI。内网环境在 buildx 命令追加 --build-arg http_proxy=http://<代理>:<端口> --build-arg https_proxy=http://<代理>:<端口> 注入代理;apps.json 仍走 --secret 传参。
十二、参考链接
- frappe_docker 官方仓库(compose 模板与 layered 构建说明):https://github.com/frappe/frappe_docker
- 官方 pass-wordless 部署文档(pwd.yml 用法):https://github.com/frappe/frappe_docker/blob/main/docs/pwd.yml
- Frappe HR 仓库与安装说明:https://github.com/frappe/hrms
- hrms v16.52.0 发布页:https://github.com/frappe/hrms/releases/tag/v16.52.0
- MariaDB 11.8 官方镜像:https://hub.docker.com/_/mariadb
- 本系列关联文档:《Frappe HR 16.52.0 介绍与选型指南》《Ubuntu 22.04 安装 Frappe HR 16.52.0 指南》《Frappe HR 16.52.0 配置参数详解》《Frappe HR 16.52.0 常用 CLI 命令与工具详解》