binance-trading-bot 部署 · 官方 operator runbook 中文化

binance-trading-bot 部署:一条 9 步 Docker Compose runbook

官方把首次部署的目标写成一句话:在一台干净的 Linux VM 上,用官方给出的步骤把它跑起来。下面把前置条件、每一步的真实命令、预期输出、容器角色、密钥注入与 TLS 方案集中在一页,遇到问题可直接跳到最后一节的失败对照表。

部署方式:Docker Compose(官方给出的完整部署路径)前置:Docker 24+ / Compose 2.20+起步规格:2 vCPU · 4 GB · 20 GB依据官方 deploy 与 install 文档(2026-09-17 核验)

部署拓扑

Operator 浏览器React SPA,可安装为 PWA
TLS 边缘栈外,compose 不含反向代理
app 容器ROLE=all:api + worker + study
Postgres 17TimescaleDB,持久化状态
Redis 8队列、缓存、限速计数
币安接口REST + WebSocket(外部)
部署拓扑示意图(依据官方 tech-stack / deploy 文档绘制,非官方原图)。栈自带 app + Postgres + Redis,不含反向代理;生产必须自行在边缘加 TLS。
Prerequisites

部署前必须满足的 7 项前置条件

这些条件来自官方 deploy runbook 的 Prerequisites 与 install 页的「What you need first」。它们决定了你后面能不能一次跑通,建议先逐条对照再动手。

要求为什么需要不满足会怎样依据
Linux VM,Docker ≥ 24,Docker Compose ≥ 2.20(带 compose 扩展)整套栈以容器方式发布,官方给出的唯一完整部署路径就是 compose无法按官方路径启动;旧版 `docker-compose`(v1 语法)会因命令形式不同直接报错deploy/README.md · Prerequisites
2 vCPU / 4 GB 内存 / 20 GB 磁盘官方说明这足够跑一个 master 账户与少量 Profile回测是 CPU 密集型任务,规格过小会让容器被调度拖慢甚至被杀deploy/README.md · Prerequisites
出站 HTTPS 到 `api.binance.com` 与 `wss://stream.binance.com`交易下单走 REST,实时行情走 WebSocket,两者都不能少服务能起来但永远不交易;面板显示 worker 未在跟单deploy/README.md · Prerequisites
币安 Spot API Key(读权限 + 现货与杠杆交易,开启 IP 白名单)读权限用于拉钱包与账户快照,交易权限用于下单与撤单币安会直接拒绝请求,常见返回 `-2015`install.md Step 6 · deploy step 8
`AUTH_SECRET` ≥ 32 字符(用 `openssl rand -hex 32` 生成)它用于签发登录 Cookie,配置层用 Zod 强制校验长度启动直接失败——占位符故意做得太短,就是为了让未修改的拷贝启动不起来.env.example · install.md Step 3
端口 3000 / 9100 / 9101 可用(生产入口默认映射到 80)3000 是公共入口(`/api` + SPA),9100 是 api 管理端口,9101 是 worker 管理端口端口冲突时容器起不来,或健康检查在宿主机上不可达tech-stack.md · Ports
(建议)先 `docker login` 一个有账号的 Docker Hub镜像公开发布在 Docker Hub,匿名拉取有配额限制`docker compose pull` 被限流失败,报配额超限deploy/README.md · Troubleshooting
关于「大陆网络环境」:官方只声明运行需要能出站访问币安的行情与交易接口。中国大陆的网络可达性与合规要求请自行确认,本站不对「能否直连」作出可用性承诺。
Runbook

九步部署:命令、说明与预期输出

严格按官方 runbook 的顺序执行,每一步都给出真实命令与你应该看到的结果。官方给这条路径的目标是:一台干净的 VM 上完成部署。

  1. 克隆仓库

    shellgit clone https://github.com/chrisleekr/binance-trading-bot.git
    cd binance-trading-bot

    拿到仓库与 compose 文件;注意 compose 文件不在仓库根目录,而在 `deploy/compose/` 下。

    预期输出:当前目录出现 `deploy/compose/docker-compose.yml`、`.env.example`、`package.json`。

  2. 创建环境文件并填好对外地址

    shellcp .env.example .env

    把 `WEB_ORIGIN` 改成你实际访问这个面板的地址(例如 `WEB_ORIGIN=https://bot.<你的域名>`)。用 compose 启动时,`DATABASE_URL` 与 `REDIS_URL` 用容器内 DNS 名,或者直接删掉这两行走 compose 文件里的默认值。

    预期输出:仓库根目录出现 `.env`;其余变量都有可用默认值。

  3. 生成会话密钥

    shellsed -i.bak "s|^AUTH_SECRET=.*|AUTH_SECRET=$(openssl rand -hex 32)|" .env \
      && rm .env.bak

    用 sed 就地替换而不是追加,重复执行不会在 `.env` 里写重复的键。

    预期输出:`.env` 中 `AUTH_SECRET=` 后面变成 64 位十六进制字符串。

  4. 拉取镜像(或改为本地构建)

    shelldocker compose -f deploy/compose/docker-compose.yml \
                   -f deploy/compose/docker-compose.prod.yml \
                   --env-file .env pull

    生产叠加层会启用 secrets 与生产默认值。建议在 `.env` 里用 `IMAGE_TAG` 钉住一个 `vX.Y.Z` 版本,而不是跟随 `latest`。

    预期输出:拉到 `chrisleekr/binance-trading-bot` 对应版本标签的镜像。离线环境可改用 `docker compose -f deploy/compose/docker-compose.yml build` 从源码构建。

  5. 启动整个栈

    shelldocker compose -f deploy/compose/docker-compose.yml \
                   -f deploy/compose/docker-compose.prod.yml \
                   --env-file .env up -d

    compose 用 `depends_on: condition: service_healthy` 串起启动顺序,app 会等 Postgres 与 Redis 真的可连接后才启动,所以新机器首次启动不会陷入重试循环。

    预期输出:`postgres`、`redis` 先进入 healthy,随后 `app` 启动。注意:在仓库根目录直接跑裸的 `docker compose up` 不生效,因为根目录没有 compose 文件。

  6. 数据库迁移(自动执行,无需手动)

    容器入口 `apps/server/docker-entrypoint.sh` 会在启动应用之前先跑迁移器,因此没有什么要你手动执行的。迁移器是幂等的,并用 Postgres advisory lock 串行化并发调用,所以 api / worker / study 每个角色都会在引用表结构之前先完成迁移;迁移失败会让容器以非零码退出、就绪检查永远不就绪。

    shell(仅在离线维护路径才需要)docker compose exec app bun /app/dist/migrate.js

    预期输出:正常启动时无额外输出;手动执行时命令成功返回,无报错。

  7. 打开 WEB_ORIGIN 创建主账号

    首次访问会进入引导页,创建唯一的主账号(无邮箱验证、无二次验证,这是一个单操作员应用)。创建完成后,再访问 `/onboarding` 会跳转到登录页;忘记密码需要用运维命令重置:

    shelldocker compose run --rm app bun run reset-password

    预期输出:浏览器能打开面板并成功登录;`/onboarding` 之后改为跳转登录页。

  8. 给币安 API Key 开启 IP 白名单

    在币安 API 管理界面编辑这把 Key:勾上「Restrict access to trusted IPs only」,并把服务器的出口 IP 加进去(在 VM 上执行 `curl -s ifconfig.me` 获取);同时确认「Enable Spot & Margin Trading」已勾选。官方特别强调:因为密钥在数据库中是明文存储,这一步是整个威胁模型里的主要缓解措施。

    预期输出:服务器出口 IP 出现在币安的白名单里;然后回到面板的账户页粘贴 Key 与 Secret,状态会变绿。

  9. 验收:确认服务真的活着

    shelldocker compose ps                              # 每个服务都应为 (healthy)
    curl -fsS http://127.0.0.1:9100/healthz        # api 存活
    curl -fsS http://127.0.0.1:9100/readyz         # api 就绪(DB + Redis ping)

    管理端点绑定在 `127.0.0.1:$ADMIN_PORT`(默认 9100),公网访问不到它们,所以必须在 VM 上执行。

    预期输出:三个命令全部成功(HTTP 200)。若 `/readyz` 返回 503,按最后一节的失败对照表排查。

这套 runbook 的边界:它覆盖的是「跑起来」。账号结构、策略参数、回测放行与风控设置不在本页,见账户页、策略页与风控页。以官方 deploy / install 文档为准。
Roles and ports

三个进程角色与端口归属

整套应用发布成一个镜像,由 `ROLE` 决定这个容器扮演什么角色。默认的单机部署把三个角色放进一个容器与一个事件循环,这也是「worker 调参默认值偏保守」的原因。

ROLE跑什么什么时候用注意点
`all`(默认)api + 实时 worker + study,同一进程、同一事件循环单机最省事,组件最少因为共享事件循环,worker 的并发与限额默认值都偏保守
`api`HTTP api + SPA(也负责提供已构建前端)把接口层单独放每个应用角色启动前都会先跑带 advisory lock 的迁移器
`worker`只跑实时交易循环(cron、生命周期、tick 与流水线)把交易循环与回测分开目前**只允许 1 个副本**;多副本扩展虽已合并但处于休眠状态
`study`只跑回测回放消费者把长时间回测移出交易循环`ROLE=all` 下 study 会按 `STUDY_CPU_SHARE`(默认 0.5)限速,避免饿死实时 tick
拆分方式`deploy/compose/docker-compose.scale.yml`:停掉 `app`,用**同一个镜像**起 `api` / `worker` / `study`需要隔离回测算力或单独重启某一层时三个角色必须用同一版本镜像,否则会出现构建版本偏移
端口由谁提供提供什么默认绑定
3000api公共入口:`/api` 与 SPA 同源宿主端口由 `APP_HTTP_PORT` 决定,生产默认 80
9100api管理端点:`/healthz`、`/readyz`、`/metrics``ADMIN_HOST` 默认 127.0.0.1,公网不可达
9101workerworker 自己的健康与指标端点`WORKER_ADMIN_HOST` 默认 127.0.0.1;与 api 同宿主时必须与 9100 不同
无独立 web 容器前端是构建产物,由 api 同源提供所以浏览器只和一个主机通信,CSP 也保持一致
docker compose ps每个服务应为 (healthy)
/healthzapi 存活探针
/readyzapi 就绪:DB + Redis ping
503 分支redis ping failed / db ping failed

起服务后的验收顺序示意图(依据官方 install / deploy 文档绘制)。/readyz 返回 503 时,先看是 Redis 还是 Postgres 的 ping 失败。

Secrets

生产环境的三个密钥,注入方式各不相同

官方给生产部署准备了三份独立密钥,但它们的注入机制并不统一——其中有一个必须手工写进 `.env`。把三者的差异先看清楚,能省掉一轮「为什么 api 连不上 Redis」。

密钥文件消费方注入方式注意点 / 适用场景
`deploy/secrets/postgres_password`postgres 容器原生 Docker secret 的 `_FILE` 间接:`POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password`最标准的一路,容器直接读挂载文件,不必把密码写进环境变量
`deploy/secrets/redis_password`redis 容器entrypoint 内 `$(cat /run/secrets/redis_password)` 作为 `--requirepass` 传给 redis-server生产 redis 强制 `--requirepass`;**`REDIS_URL` 必须改成带密码的形式**,否则 api 与 worker 连不上
`deploy/secrets/session_secret`app 容器api **不支持** `_FILE` 间接,必须把值 sed 注入 `.env` 的 `AUTH_SECRET`官方用 sed 而非 `>>`,是为了让这段脚本可以重复执行而不重复写键
目录权限宿主机`chmod 600 deploy/secrets/*`;首次运行后再 `chmod 700 ./backups deploy/secrets`这两个目录里含有可提走账户的凭据,权限必须收紧
不在 `.env` 里的配置app 容器备份计划(开关、间隔小时数、保留份数)在应用内「账户 → Backup」里配置官方明确说明:不要把保留策略另放一份到环境变量,否则两个所有者会互相打架
shell · 生成三份密钥并注入mkdir -p deploy/secrets
openssl rand -hex 32 > deploy/secrets/session_secret
openssl rand -hex 32 > deploy/secrets/postgres_password
openssl rand -hex 32 > deploy/secrets/redis_password
chmod 600 deploy/secrets/*

REDIS_PW=$(cat deploy/secrets/redis_password)
sed -i.bak \
  -e "s|^AUTH_SECRET=.*|AUTH_SECRET=$(cat deploy/secrets/session_secret)|" \
  -e "s|^REDIS_URL=.*|REDIS_URL=redis://:${REDIS_PW}@redis:6379|" \
  .env \
  && rm .env.bak
chmod 600 .env
密钥是明文存储的:官方在技术栈文档的「Deliberate omissions」里明确写着不做静态加密——币安 API Key 与通知器密钥都以明文存放在数据库里。缓解手段是单租户部署加上币安侧的 IP 白名单。请把 `./backups` 与 Postgres 数据卷当成敏感存储对待。
TLS at the edge

给面板加 TLS 的三种方案

compose 栈刻意不绑定任何一种边缘方案:它只提供明文 HTTP,把 TLS 交给你现有的基础设施。官方给出三条参考路线,按你手上已有什么来选。

方案做法适合要改什么 / 注意点
Cloudflare Tunnel在 VM 上再跑一个 `cloudflared` 容器加入同一 docker 网络,用 token 建立隧道;在 Cloudflare 控制台把公网域名指到 `http://app:3000`不想在 VM 上暴露任何公网入口不需要开公网端口;要自己保管隧道 token 并让容器 `--restart unless-stopped`
nginx(主机层)主机上既有 nginx 做 443 反代,upstream 指向宿主机 `http://127.0.0.1:${APP_HTTP_PORT}`已经有反代与证书管理流程必须转发 `Host`、`X-Real-IP`、`X-Forwarded-For`、`X-Forwarded-Proto`,并透传 `Upgrade`/`Connection` 让 WebSocket 通过;`proxy_read_timeout` 官方示例给到 300s
Traefik(主机层)动态配置里加一个 router + service,service 指向宿主机端口已经在用 Traefik 与自动证书与 nginx 同理;upstream 依旧指向 `app:3000`(同网络)或宿主机端口
Vercel(托管边缘)把 `apps/web` 的构建产物部署到 Vercel,配置 rewrite 让 `/api/*` 经隧道或 Tailscale 回源到 VMVM 没有公网入口,但需要一个 HTTPS 的对外门户必须把 `WEB_ORIGIN` 改成 Vercel 域名,因为它是 CORS、CSRF 与 WebSocket 升级的允许清单,且**不支持 `*` 通配**
Fly.io(托管边缘)`flyctl proxy` 配 `[[services]]` 块,经 Tailscale 或 WireGuard 回源到 VM同 Vercel,已有 Fly 账号与网络同样要同步改 `WEB_ORIGIN`
nginx fragment(官方示例)location / {
  proxy_pass         http://127.0.0.1:80;   # app 服务,宿主端口 = APP_HTTP_PORT
  proxy_set_header   Host              $host;
  proxy_set_header   X-Real-IP         $remote_addr;
  proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
  proxy_set_header   X-Forwarded-Proto https;
  proxy_set_header   Upgrade           $http_upgrade;
  proxy_set_header   Connection        $http_connection;
  proxy_read_timeout 300s;
}
Cloudflare Tunnel(官方示例)docker run -d --network=binance-trading-bot_internal \
  --name cloudflared --restart unless-stopped \
  cloudflare/cloudflared:latest tunnel \
  --no-autoupdate run --token <你的隧道令牌>
为什么必须加 TLS:默认栈在 `APP_HTTP_PORT` 上提供明文 HTTP,而登录 Cookie、会话与币安凭据都会经过这条链路。官方明确要求生产部署必须在这套栈前面加上 TLS。
Backup

备份与恢复:先把「备份里有什么」说清楚

栈里自带一个每晚执行 `pg_dump` 的备份服务,官方同时给出把备份推到异地存储的参考配方。要做异地备份之前,先理解备份文件里装的是什么。

本地备份服务

compose 内置 `backup` 服务,每晚把数据库导出成 `backup-<epochms>.dump` 写进 `./backups`(该目录同时挂载给 postgres 容器)。本地默认保留 14 天,保留份数也可以在应用内「账户 → Backup」调整。

异地备份(restic)

官方推荐用 restic 把 `./backups` 推到 S3/MinIO、Backblaze B2 或 Storj,并给出初始化、每日 cron 与恢复的完整命令。密码文件必须放在 VM 之外(例如密码管理器),否则主机被攻破时异地备份也一并失守。

恢复的动作

`./backups` 已挂载进 postgres 容器,所以把恢复出来的 dump 放进宿主机的 `./backups`,再执行 `pg_restore` 即可,不需要额外的 `docker cp`。

备份文件里含明文密钥:官方明确说明 `./backups/*.dump` 与 `postgres_data/` 都含有明文币安 API Key(设计上接受这一点,用币安侧 IP 白名单来交换)。因此:首次运行后把 `./backups` 与 `deploy/secrets` 权限收紧到 700;异地仓库用独立密码;一旦这两个目录中的任何一个被暴露,就必须轮换币安 API Key。
Troubleshooting

常见部署失败对照表

下面这些都是官方文档里点名写过、或者部署路径上必然会遇到的症状。先按症状定位原因,再执行对应动作。

症状原因处置动作
`/readyz` 返回 503,日志写着 `redis ping failed`Redis 容器还没进入健康状态,通常是宿主机端口冲突`docker compose logs redis` 看端口占用;换掉冲突的宿主端口后重起
`/readyz` 返回 503,日志写着 `db ping failed`Postgres 的健康检查仍在等待;首次启动时扩展初始化可能需要 10 秒以上`docker compose logs postgres` 观察进度,等 initdb 与扩展安装完成
浏览器里接口请求报 CORS 错误`WEB_ORIGIN` 与你实际访问的地址不一致改 `.env` 的 `WEB_ORIGIN`(精确的 `scheme://host:port`,不支持 `*`),再 `up -d` 重起 api
`docker compose pull` 报 Docker Hub 限流匿名拉取配额用完了先 `docker login`(任意 Docker Hub 账号即可),再重试 pull
`backup` 服务日志出现 `dump failed (exit 1)``.env` 里的 `POSTGRES_PASSWORD` 与容器实际使用的不一致核对 `.env` 与 `deploy/secrets/postgres_password`,使两者一致后重起
币安返回 `-2015`(rejected by config)Key 的 IP 白名单没加服务器出口 IP,或缺少现货交易权限回到第 8 步:用 `curl -s ifconfig.me` 取出口 IP 加进白名单,并确认「Enable Spot & Margin Trading」已勾选
在仓库根目录执行 `docker compose up` 报找不到 compose 文件compose 文件位于 `deploy/compose/`,仓库根目录没有始终带 `-f deploy/compose/docker-compose.yml`(生产再加 `-f .../docker-compose.prod.yml`)与 `--env-file .env`
还没解决?排错页按「机器人不交易 / Key 状态异常 / 订单被拒 / 面板打不开 / 需要立刻停止」分节给出中文步骤,并包含 `GET /status` 的构建版本偏移检查与配置超限的只读 SQL 自查。
FAQ

部署相关常见问题

部署这套机器人要花多少钱?

代码按 Apache-2.0 开源、没有订阅费,但运行成本全部由你承担:一台满足 2 vCPU / 4 GB / 20 GB 的 Linux 服务器、可能的带宽与跨区网络费用,以及币安账户本身。官方文档没有给出任何托管或代运维方案,具体前置与规格以官方 deploy 文档为准。如果只是想先看数据、不想承担服务器成本,EasyClaw 的研究路线不需要部署,但它也不会替你下单。

能在 Windows、群晖 NAS 或树莓派上部署吗?

官方文档给出的前置是一台装有 Docker 与 Compose 的 Linux VM,并以 `docker compose` 命令为唯一完整路径;文档里没有对 Windows、NAS 套件或 ARM 单板机做支持说明。因此本站不宣称这些环境可用,请以官方文档与仓库 issue 为准。若只想验证研究结论而不部署,可以走 EasyClaw 路线。

没有服务器,还有别的办法吗?

官方没有提供托管服务,代码只发布了自托管镜像,所以严格意义上没有「不用服务器」的官方路径。一个折中做法是先按官方文档在本地开发环境跑起来熟悉界面(`bun install && bun run setup && bun run dev`,需要 Bun 1.4.2 与本地 Postgres/Redis),但那不是生产部署。若你的目标是做数据研究与回测结论,EasyClaw 路线不需要服务器,只是能力边界限于研究。

中国大陆的网络环境能不能直连币安接口?

官方只声明运行需要能出站访问 `api.binance.com` 与 `wss://stream.binance.com`,没有讨论特定国家或地区的网络与合规情况。也就是说,能否连通、是否合规都需要你自己确认——本站不做可用性承诺,也不提供规避网络限制的做法。如果你的环境无法稳定访问这些接口,这台机器人无法正常工作;此时 EasyClaw 的研究路线能回答数据分析类问题,但同样不能替代实盘执行。

升级版本会不会丢数据?

数据库中持久化的状态在容器外(Postgres 数据卷),因此按官方方式升级镜像并重启不会清空数据:迁移器在启动时自动执行且是幂等的,会把表结构升到新版本。风险点在两处:一是迁移失败会让容器以非零码退出,需要看日志处理;二是不要用不同版本镜像混跑 api / worker / study,否则会出现构建版本偏移。稳妥做法是升级前先按官方配方做一次备份,升级步骤以官方 deploy 文档为准。

官方有没有一键安装脚本?

没有。官方的部署路径就是这份 9 步 runbook:克隆、配 `.env`、生成密钥、拉镜像、`up -d`,没有提供 `curl | bash` 之类的单命令安装器。任何声称「一键部署该机器人」的第三方脚本都不属于官方资料,请自行判断风险。如果你要的是「不部署、直接用」的体验,那是另一条路线:EasyClaw 提供免安装的研究类任务,但它与本项目没有已证实集成。

服务起来了,下一步是把它安全地接到币安

部署完成只代表服务在跑。要让它的状态灯变绿并开始按规则工作,还需要按最小权限创建 API Key、配好 IP 白名单,再建首个 Profile。