1. ERPNext V15 生产环境 Docker 部署到底难在哪
ERPNext V15 是一套开源的企业资源计划系统,涵盖财务、库存、生产、HR、CRM 等模块,适合中小制造、贸易、服务类企业自建数字化底座。它基于 Frappe 框架,天然支持多应用扩展,但正因为模块多、依赖重,生产环境部署时踩坑概率远高于普通 Web 应用。很多人第一次用 Docker 跑 ERPNext,卡在镜像构建、数据库健康检查、站点创建、反向代理这几步上,反复重来。
我试过在一台 4 核 8G 的 Ubuntu 24.04 上从零走完整流程,最深的感受是:官方pwd.yml那种一键演示部署确实五分钟能跑起来,但它用的是内置 MariaDB 和 Redis,端口直接暴露 8080,没有 HTTPS,没有备份策略,绝对不能拿去生产。生产环境要的是可复现的镜像、独立的数据卷、可控的数据库连接、反向代理加证书,以及出问题能回滚。
这篇文章面向的是已经决定自建 ERPNext、需要一套能直接复制粘贴落地的部署方案的人。我会给出完整的docker-compose.yml生成方式、环境变量模板、站点创建命令、健康检查脚本,以及端口冲突、权限报错、备份恢复这些高频问题的排查路径。同时,部署过程中如果涉及调用外部模型服务(比如智能客服、单据 OCR、报表问答),凭据管理容易散落各处,我会用 TaoToken 的统一 Key 通道把这类模型服务的 Base URL、Key、Model ID 收敛到一处,避免每个应用各配一份。
先说清楚整体架构:ERPNext 生产部署通常包含四层——MariaDB 数据库、Redis 缓存与队列、Frappe 后端(含 worker 和 scheduler)、前端与反向代理。Docker Compose 负责编排这四层,数据卷负责持久化,反向代理负责 HTTPS 和域名。下面按这个顺序展开。
2. TaoToken 统一 Key 通道在部署中的前置准备
ERPNext 本身不依赖大模型,但生产环境里往往要接一些智能能力:比如用模型做采购单摘要、客户邮件自动分类、库存异常问答。这些调用如果每个应用单独配 Key,运维会非常痛苦——Key 轮换要改多处,额度分散看不清,日志里还容易泄露。TaoToken 提供的是统一 Key/API 通道,把模型服务的接入点收敛成一个 Base URL 加一个 Key,模型用 Model ID 区分。
前置准备分三步。第一步,拿到统一 Key。访问 https://taotoken.net/api-keys 创建 API Key,建议按环境命名,比如erpnext-prod,方便后续审计。第二步,确认接入文档里的 Base URL 和可用 Model ID,文档地址 https://taotoken.net/doc 。第三步,在 ERPNext 侧决定凭据存放方式——不要硬编码进代码,用环境变量或.env文件,Compose 启动时注入。
这里要强调一个原则:模型服务的 Base URL、Key、Model ID 三件套必须成组出现,缺一个都调不通。Base URL 统一填https://taotoken.net/api,Key 填你创建的那串,Model ID 按文档里列出的填。如果你用的是 Claude Code 这类编码工具做部署脚本辅助,它的配置也是同样的三件套逻辑,Base URL 指向统一通道,Key 用同一个,Model ID 选对应模型。
为什么要在部署阶段就规划好这个?因为 ERPNext 的自定义应用(custom app)里如果写了模型调用,凭据来源必须和 Compose 的环境变量对齐。否则容器重启后环境变量丢失,应用报 401,你还得进容器排查。提前把.env里的变量名定好,比如TAOTOKEN_BASE_URL、TAOTOKEN_API_KEY、TAOTOKEN_MODEL_ID,后面写自定义应用时直接读这三个变量即可。
另外提醒一点:TaoToken 是模型服务的统一接入通道,不是数据库或 Redis 的替代品,别把它和 ERPNext 的核心依赖混在一起理解。它的作用域是“部署过程中及部署后调用的模型服务凭据管理”,核心业务数据仍然走 MariaDB 和 Redis。
3. 可复制的 Compose 配置与环境变量模板
这一节是全文最核心的可操作部分。我按官方frappe_docker仓库的 layered 镜像方案来写,因为 layered 基于预构建基础镜像,构建速度快,适合生产。先克隆仓库:
cd ~/ git clone -b v2.0.1 https://github.com/frappe/frappe_docker cd frappe_docker然后准备apps.json,决定要装哪些应用。ERPNext 核心必装,HRMS 和 Payments 按需:
[ { "url": "https://github.com/frappe/erpnext.git", "branch": "version-15" }, { "url": "https://github.com/frappe/hrms.git", "branch": "version-15" }, { "url": "https://github.com/frappe/payments.git", "branch": "version-15" } ]设置 base64 环境变量并构建镜像:
export APPS_JSON_BASE64=$(base64 -w 0 ~/frappe_docker/apps.json) docker build \ --build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \ --build-arg=FRAPPE_BRANCH=version-15 \ --build-arg=APPS_JSON_BASE64=$APPS_JSON_BASE64 \ --tag=myimages/custom:1.0.0 \ --file=images/layered/Containerfile .构建完成后,复制环境变量模板并修改:
cp example.env custom.envcustom.env关键字段如下,这是生产环境的最小可用模板:
DB_PASSWORD=ChangeMe_StrongPass_2024 DB_HOST= DB_PORT= REDIS_CACHE= REDIS_QUEUE= FRAPPE_SITE_NAME_HEADER=frontend CUSTOM_IMAGE=myimages/custom CUSTOM_TAG=1.0.0 PULL_POLICY=missing TAOTOKEN_BASE_URL=https://taotoken.net/api TAOTOKEN_API_KEY=sk-你的统一Key TAOTOKEN_MODEL_ID=按文档填写注意FRAPPE_SITE_NAME_HEADER必须填frontend,这是 Frappe 内部站点名匹配用的,填错会导致访问 404。DB_HOST和DB_PORT留空表示用 Compose 内置的 MariaDB;如果接外部数据库,填 IP 和端口。REDIS_CACHE和REDIS_QUEUE同理。
生成最终 Compose 文件,这里用 HTTPS 反向代理方案:
docker compose --env-file custom.env \ -f compose.yaml \ -f overrides/compose.mariadb.yaml \ -f overrides/compose.redis.yaml \ -f overrides/compose.https.yaml \ config > compose.custom.yaml如果你暂时不接 HTTPS,把compose.https.yaml换成compose.noproxy.yaml,端口走 8080。生产环境强烈建议 HTTPS,证书可以用 Let's Encrypt,Traefik 会自动申请。
启动容器:
docker compose -p frappe -f compose.custom.yaml up -d低配服务器上 MariaDB 健康检查可能超时,先等它变 Healthy:
docker ps --filter "name=frappe-db" --format "{{.Status}}"看到healthy后再重启一次:
docker compose -p frappe -f compose.custom.yaml restart创建站点并安装应用:
docker compose -p frappe exec backend bench new-site \ --mariadb-user-host-login-scope='%' \ --admin-password=Admin_Strong_2024 \ --db-root-username=root \ --db-root-password=ChangeMe_StrongPass_2024 \ --install-app erpnext \ --set-default frontend docker compose -p frappe exec backend bench --site frontend install-app hrms docker compose -p frappe exec backend bench --site frontend install-app payments到这里,核心部署完成。模型服务的凭据已经通过custom.env注入到容器环境,自定义应用里用os.environ.get('TAOTOKEN_API_KEY')读取即可,不需要在代码里写死。
4. 验证请求与成功结果确认
部署完不能只看容器起来了就完事,要逐层验证。第一层,容器状态:
docker compose -p frappe ps正常应该看到backend、db、redis-cache、redis-queue、frontend、websocket、scheduler、worker等容器都是Up或healthy。如果某个 worker 反复重启,先看日志:
docker compose -p frappe logs --tail=100 worker第二层,站点健康检查。Frappe 自带bench doctor:
docker compose -p frappe exec backend bench doctor输出里Scheduler is enabled和Workers online是正常标志。如果 scheduler 没启用,执行:
docker compose -p frappe exec backend bench --site frontend enable-scheduler第三层,HTTP 验证。用 curl 打站点首页:
curl -I https://erp.yourdomain.com返回HTTP/2 200且content-type: text/html说明反向代理和前端都通。如果返回 502,多半是 backend 没起来或 Traefik 配置没生效。
第四层,模型服务通道验证。这一步很多人忽略,但生产环境里自定义应用调模型失败,往往就是这里没验。在 backend 容器里执行:
docker compose -p frappe exec backend bash -c ' curl -s -o /dev/null -w "%{http_code}" \ -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d "{\"model\":\"$TAOTOKEN_MODEL_ID\",\"messages\":[{\"role\":\"user\",\"content\":\"ping\"}]}" '返回200说明统一 Key 通道在容器内可用。如果返回401,检查 Key 是否复制完整、有没有多余空格;返回404,检查 Base URL 是否写成了带路径的完整地址。
第五层,登录界面确认。浏览器打开域名,用administrator和你设置的 admin 密码登录,能看到中文设置向导和左侧 ERPNext 菜单,说明站点创建成功。到这一步,部署验证闭环完成。
5. 常见报错排查:401、端口冲突、权限与备份恢复
生产部署最耗时的不是顺利路径,而是报错排查。下面按真实遇到的频率排序。
401 Unauthorized(模型通道):容器内 curl 返回 401,先确认TAOTOKEN_API_KEY是否真的注入到容器:
docker compose -p frappe exec backend printenv | grep TAOTOKEN如果变量为空,说明custom.env没被 Compose 读取,检查启动命令有没有带--env-file custom.env。如果变量有值但仍 401,去 https://taotoken.net/api-keys 确认 Key 状态是否正常、额度是否耗尽。
local proxy failed / 502 Bad Gateway:反向代理到 backend 失败。先确认 backend 容器在运行,再看 Traefik 日志:
docker compose -p frappe logs --tail=50 frontend常见原因是FRAPPE_SITE_NAME_HEADER没设成frontend,或者域名解析没指向服务器。另外检查compose.https.yaml里的域名变量是否和实际域名一致。
端口冲突:启动时报bind: address already in use。查占用:
ss -tlnp | grep -E '80|443|8080|3306|6379'如果是本机已有 Nginx 占了 80/443,要么停掉它,要么改 Compose 映射端口。MariaDB 默认 3306、Redis 6379,如果宿主机已装同款服务,也会冲突,建议生产环境用独立端口映射,比如3307:3306。
权限报错(Permission denied):数据卷挂载后容器内用户无写权限。Frappe 容器内用户 UID 通常是 1000,宿主机目录属主要对齐:
sudo chown -R 1000:1000 /path/to/sites如果用的是命名卷而非绑定挂载,一般不会有这个问题,所以生产环境优先用命名卷。
reading choices 报错:这是 Frappe 前端读取字段选项时的报错,多半是站点没设默认或缓存没刷新。执行:
docker compose -p frappe exec backend bench --site frontend clear-cache docker compose -p frappe exec backend bench --site frontend migrateOAuth / 登录跳转异常:如果接了 SSO 或 OAuth,回调地址必须和实际域名一致。检查site_config.json里的host_name:
docker compose -p frappe exec backend cat sites/frontend/site_config.json不一致就改掉再重启。
备份与恢复:生产环境必须配定时备份。手动备份命令:
docker compose -p frappe exec backend bench --site frontend backup --with-files备份文件在sites/frontend/private/backups/。恢复时先停服务,再执行:
docker compose -p frappe exec backend bench --site frontend restore /path/to/backup.sql.gz --with-public-files /path/to/files.tar --with-private-files /path/to/private-files.tar恢复后跑一次bench migrate和clear-cache。回滚方案同理:保留上一个版本的镜像 tag,出问题时把CUSTOM_TAG改回旧版本,重新up -d即可,数据卷不动。
6. 长期编码与 Agent 场景下的通道管理建议
ERPNext 部署不是一次性动作,后续会有自定义应用开发、报表脚本、定时任务、Agent 自动化等需求。这些场景里模型调用会越来越频繁,凭据管理如果一开始没规划好,后面会变成技术债。
我的建议是把 TaoToken 的统一 Key 通道当成基础设施的一部分来管。具体做法:在custom.env里只放一个 Key,所有自定义应用通过环境变量读取;不同环境(开发、测试、生产)用不同的 Key,在 https://taotoken.net/api-keys 里分别创建,命名带环境后缀;Key 轮换时只改.env一处,重启容器生效,不用动代码。
如果你用 Claude Code 或类似编码工具辅助写 ERPNext 自定义应用,它的配置也是同一套三件套:Base URL 指向https://taotoken.net/api,Key 用统一 Key,Model ID 按文档选。这样编码工具和运行时应用共用一套凭据体系,排查问题时不用在两套配置之间切换。
对于长期跑 Agent 任务(比如自动生成采购建议、库存预警摘要)的场景,建议单独申请一个 Key,和交互式应用隔离,方便按额度监控。Coding Plan 这类长期编码方案也适合放在这个通道下统一管理,避免多个 Key 散落。
最后给一个实用技巧:在custom.env里加一行注释记录 Key 的创建时间和用途,比如# TAOTOKEN_API_KEY created 2024-06 for erpnext-prod custom app。半年后回头看,能省下大量回忆成本。部署这件事,顺利跑通只是开始,能长期稳定维护才是生产环境的真正要求。