Pay-Unify 的推荐部署形态是单个镜像、单个容器、单个端口:
//go:embed all:web 内嵌;| 项 | 值 |
|---|---|
| 镜像 | difyz9/pay-unify:2.0.1(多架构:linux/amd64 + linux/arm64,≈57 MB) |
| Docker Hub | https://hub.docker.com/r/difyz9/pay-unify |
| 容器端口 | 8080(内部固定),对外映射任意端口 |
| 必须持久化 | 命名卷 pay-unify-data → /app/runtime、pay-unify-logs → /app/logs |
| 运行用户 | 非 root(uid/gid 1000) |
| 健康检查 | GET /health/db → {"db":"ok","status":"healthy"} |
| 初始账号 | admin / admin123(仅首次建库生效,登录后请立即改密) |
不需要克隆仓库,两条命令即可:
打开 **http://localhost:8097**,用 admin / admin123 登录。
编排已经处理好了三个最容易踩的坑:
| 坑 | 编排里的做法 |
|---|---|
| JWT 密钥不能给默认值(否则任何人可伪造管理员 token) | CONFIG_FILE 指向数据卷,首次启动由应用用 crypto/rand 生成 64 字符密钥并落盘,重建容器后登录态仍有效 |
| 数据卷属主不对,非 root 容器写不了证书 | 使用命名卷而非 bind mount:镜像内 /app/runtime 属主已是 uid 1000,Docker 初始化命名卷时继承该属主 |
| 服务先于数据库启动会直接退出 | depends_on: mysql: condition: service_healthy,等 MySQL 就绪后再启动 |
可覆盖的变量(完整清单见环境变量参考):
| 变量 | 默认值 | 说明 |
|---|---|---|
PAY_UNIFY_PORT |
8097 |
对外映射端口(容器内固定 8080) |
PAY_UNIFY_VERSION |
2.0.1 |
镜像版本,生产建议锁定具体版本 |
PAY_ADMIN_USERNAME / PAY_ADMIN_PASSWORD |
admin / admin123 |
初始管理员(仅首次建库生效) |
PAY_JWT_SECRET |
留空自动生成 | 留空则首次启动随机生成并持久化 |
MYSQL_ROOT_PASSWORD / MYSQL_PASSWORD |
payunify_root / payunify_pass |
内置 MySQL 的密码 |
docker run 命令不想用 Compose 时手工起,自带 MySQL:
⚠️ 两处
--restart unless-stopped不要省。 服务启动时会立即校验数据库连通性, 连不上就exit 1且不重试。连敲上述两条命令时 MySQL 往往还在初始化, 没有重启策略会得到一个已退出的容器;有它则 Docker 自动重试到 MySQL 就绪。
CONFIG_FILE指向数据卷是刻意的:镜像自带的/app/config.toml里是公开占位密钥replace-with-openssl-rand-hex-32-jwt-secret,直接用它等于 JWT 签名密钥公开; 指向卷内可让应用首次启动时自生成随机密钥。
只需提前建好空库,表结构由服务启动时自动 AutoMigrate:
macOS / Windows 连宿主机 MySQL 用
-e PANEL_DB_HOST=host.docker.internal; Linux 需加--add-host=host.docker.internal:host-gateway。
镜像已适配 1Panel 应用包格式,支持表单化一键安装(面板托管数据库、自动生成密钥)。
应用包位于源码仓库 deploy/1panel/,安装选项包括数据库服务、库名、JWT 密钥、管理员密码与端口。
详见仓库内 deploy/1panel/README.md。
完整参考见环境变量参考。核心约定:
| 变量 | 默认值 | 说明 |
|---|---|---|
PAY_LISTEN |
:8080(镜像内置) |
容器内监听地址,一般不改,改对外端口用 -p |
CONFIG_FILE |
/app/config.toml |
配置文件路径,不存在则自动生成 |
PAY_CERT_DIR |
/app/runtime/certs |
支付渠道证书落盘目录,需持久化 |
PAY_JWT_SECRET |
随机生成 | 会话签名密钥,生产建议显式设置(openssl rand -hex 32),< 16 字节拒绝启动 |
PAY_DEBUG |
false |
调试模式;开启后才创建测试 API 应用 |
TZ |
Asia/Shanghai |
时区 |
数据库既支持 1Panel 风格的 PANEL_DB_*(自动拼 DSN),也支持直接给 PAY_DB_DRIVER +
PAY_MYSQL_DNS / PAY_POSTGRES_DSN。
| 卷 | 用途 |
|---|---|
/app/runtime |
必须持久化。CONFIG_FILE 指向此处时,同时存放自动生成的 config.toml(含 JWT 密钥)与 certs/ 支付证书 |
/app/runtime/certs |
只持久化证书时也可以只挂这一层 |
/app/logs |
应用日志(Zap + lumberjack 轮转) |
用命名卷时无需处理属主;用 bind mount(如
./data:/app/runtime)时目录由 Docker 以 root 创建, 需自行chown -R 1000:1000 <dir>,否则非 root 容器写入证书会失败。
镜像内置 HEALTHCHECK(interval 30s / timeout 5s / start-period 30s / retries 3):
/health/db 是就绪探针(含数据库连通性),正常返回 {"db":"ok","status":"healthy"}。
编排中可作为依赖条件:
刻意用 GET(
-O /dev/null)而非--spider:/health/db只注册了 GET 路由,--spider发 HEAD 会 404,导致容器被误判为 unhealthy。
| 端点 | 说明 |
|---|---|
GET / |
管理控制台(浏览器返回 HTML;其它客户端返回服务信息 JSON) |
GET /health |
存活探针 |
GET /health/db |
就绪探针(含 DB 连通性) |
GET /metrics |
Prometheus 文本格式指标 |
GET /api/v1/info |
服务信息 |
GET /swagger/index.html |
Swagger UI |
GET /api/v1/payment/channels |
三渠道状态(公开) |
仓库根目录的 Dockerfile 是三阶段单镜像(前端静态导出 → Go 内嵌 → 单二进制):
国内网络可使用镜像站加速(基础镜像 + npm 国内源):
发布流程:推
v*tag 触发.github/workflows/docker-publish.yml, 自动多架构构建并推送到docker.io/difyz9/pay-unify(tag → 版本号 +latest)。 也可在 Actions 页面workflow_dispatch手动指定版本号。
Q:容器起来了但控制台打不开?
先看 docker logs pay-unify。若报 连接数据库(mysql)失败: connect: connection refused,
说明启动时数据库还没就绪——服务不重试,容器会直接退出。加 --restart unless-stopped 自愈,
或用 Compose 的 service_healthy 依赖。数据库可达仍失败时,检查 PANEL_DB_* 是否拼错、库是否已建、是否允许该来源 IP 连接。
Q:登录提示“尝试次数过多,账号已临时锁定”?
登录失败 5 次会锁定 30 分钟([Auth] MaxLoginAttempts / LockoutDuration)。
锁定记录在进程内存中,docker restart pay-unify 即可立即解锁。
Q:上传的支付证书重启后丢了?
/app/runtime/certs 没做持久化。务必挂载数据卷;挂载宿主机目录时还要保证 uid 1000 可写。
Q:改了渠道参数要重启吗? 支付宝 / 微信的配置与证书在控制台保存后热加载即时生效,不用重启。 PayPal 由配置文件决定,需改配置并重启。
Q:docker pull 拉取慢?
使用镜像加速:docker pull docker.m.daocloud.io/difyz9/pay-unify:2.0.1(或本地配置的加速地址)。
Q:如何升级版本?
把 PAY_UNIFY_VERSION(或 image: 里的 tag)改为新版本,
执行 docker compose pull && docker compose up -d。数据在命名卷中,不受影响。