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 HRDockerDocker Compose2026/10/23

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/mysqlMariaDB 全部数据库(站点数据主体)★★★ 核心
frappe-hr-16-stack_logs/home/frappe/frappe-bench/logsbench 各进程日志★
frappe-hr-16-stack_redis-queue-data/data队列任务持久化(重启不丢待处理任务)★★

⚠️ 注意:redis-cache 实例未挂卷,属设计内行为(缓存重启即失效重建);App 代码(apps/)与前端构建产物(assets/)固化在镜像内,不落卷——升级版本靠换镜像,不靠改卷。

六、环境变量表

变量所在服务示例值说明
DB_HOSTbackend/configuratordb数据库主机名(服务名)
DB_PORTbackend/configurator3306数据库端口
MYSQL_ROOT_PASSWORD / MARIADB_ROOT_PASSWORDbackend/dbq1w2e3r4数据库 root 密码(建站与运维用)
REDIS_CACHEconfiguratorredis-cache:6379缓存 Redis 地址
REDIS_QUEUEconfiguratorredis-queue:6379队列 Redis 地址(socketio 同用此实例)
SOCKETIO_PORTconfigurator9000websocket 服务端口
BACKENDfrontendbackend:8000Nginx 反代上游
FRAPPE_SITE_NAME_HEADERfrontendfrontend请求路由到的站点名
SOCKETIOfrontendwebsocket:9000实时通道上游
PROXY_READ_TIMEOUTfrontend120反代读超时(秒)
CLIENT_MAX_BODY_SIZEfrontend50m上传体积上限
FRAPPE_REDIS_CACHE / FRAPPE_REDIS_QUEUEqueue-*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 传参。

十二、参考链接