本地备份服务
compose 内置 `backup` 服务,每晚把数据库导出成 `backup-<epochms>.dump` 写进 `./backups`(该目录同时挂载给 postgres 容器)。本地默认保留 14 天,保留份数也可以在应用内「账户 → Backup」调整。
binance-trading-bot 部署 · 官方 operator runbook 中文化
官方把首次部署的目标写成一句话:在一台干净的 Linux VM 上,用官方给出的步骤把它跑起来。下面把前置条件、每一步的真实命令、预期输出、容器角色、密钥注入与 TLS 方案集中在一页,遇到问题可直接跳到最后一节的失败对照表。
这些条件来自官方 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 的顺序执行,每一步都给出真实命令与你应该看到的结果。官方给这条路径的目标是:一台干净的 VM 上完成部署。
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`。
shellcp .env.example .env把 `WEB_ORIGIN` 改成你实际访问这个面板的地址(例如 `WEB_ORIGIN=https://bot.<你的域名>`)。用 compose 启动时,`DATABASE_URL` 与 `REDIS_URL` 用容器内 DNS 名,或者直接删掉这两行走 compose 文件里的默认值。
预期输出:仓库根目录出现 `.env`;其余变量都有可用默认值。
shellsed -i.bak "s|^AUTH_SECRET=.*|AUTH_SECRET=$(openssl rand -hex 32)|" .env \
&& rm .env.bak用 sed 就地替换而不是追加,重复执行不会在 `.env` 里写重复的键。
预期输出:`.env` 中 `AUTH_SECRET=` 后面变成 64 位十六进制字符串。
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` 从源码构建。
shelldocker compose -f deploy/compose/docker-compose.yml \
-f deploy/compose/docker-compose.prod.yml \
--env-file .env up -dcompose 用 `depends_on: condition: service_healthy` 串起启动顺序,app 会等 Postgres 与 Redis 真的可连接后才启动,所以新机器首次启动不会陷入重试循环。
预期输出:`postgres`、`redis` 先进入 healthy,随后 `app` 启动。注意:在仓库根目录直接跑裸的 `docker compose up` 不生效,因为根目录没有 compose 文件。
容器入口 `apps/server/docker-entrypoint.sh` 会在启动应用之前先跑迁移器,因此没有什么要你手动执行的。迁移器是幂等的,并用 Postgres advisory lock 串行化并发调用,所以 api / worker / study 每个角色都会在引用表结构之前先完成迁移;迁移失败会让容器以非零码退出、就绪检查永远不就绪。
shell(仅在离线维护路径才需要)docker compose exec app bun /app/dist/migrate.js预期输出:正常启动时无额外输出;手动执行时命令成功返回,无报错。
首次访问会进入引导页,创建唯一的主账号(无邮箱验证、无二次验证,这是一个单操作员应用)。创建完成后,再访问 `/onboarding` 会跳转到登录页;忘记密码需要用运维命令重置:
shelldocker compose run --rm app bun run reset-password预期输出:浏览器能打开面板并成功登录;`/onboarding` 之后改为跳转登录页。
在币安 API 管理界面编辑这把 Key:勾上「Restrict access to trusted IPs only」,并把服务器的出口 IP 加进去(在 VM 上执行 `curl -s ifconfig.me` 获取);同时确认「Enable Spot & Margin Trading」已勾选。官方特别强调:因为密钥在数据库中是明文存储,这一步是整个威胁模型里的主要缓解措施。
预期输出:服务器出口 IP 出现在币安的白名单里;然后回到面板的账户页粘贴 Key 与 Secret,状态会变绿。
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,按最后一节的失败对照表排查。
整套应用发布成一个镜像,由 `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` | 需要隔离回测算力或单独重启某一层时 | 三个角色必须用同一版本镜像,否则会出现构建版本偏移 |
| 端口 | 由谁提供 | 提供什么 | 默认绑定 |
|---|---|---|---|
| 3000 | api | 公共入口:`/api` 与 SPA 同源 | 宿主端口由 `APP_HTTP_PORT` 决定,生产默认 80 |
| 9100 | api | 管理端点:`/healthz`、`/readyz`、`/metrics` | `ADMIN_HOST` 默认 127.0.0.1,公网不可达 |
| 9101 | worker | worker 自己的健康与指标端点 | `WORKER_ADMIN_HOST` 默认 127.0.0.1;与 api 同宿主时必须与 9100 不同 |
| — | 无独立 web 容器 | 前端是构建产物,由 api 同源提供 | 所以浏览器只和一个主机通信,CSP 也保持一致 |
起服务后的验收顺序示意图(依据官方 install / deploy 文档绘制)。/readyz 返回 503 时,先看是 Redis 还是 Postgres 的 ping 失败。
官方给生产部署准备了三份独立密钥,但它们的注入机制并不统一——其中有一个必须手工写进 `.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
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 回源到 VM | VM 没有公网入口,但需要一个 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 <你的隧道令牌>
栈里自带一个每晚执行 `pg_dump` 的备份服务,官方同时给出把备份推到异地存储的参考配方。要做异地备份之前,先理解备份文件里装的是什么。
compose 内置 `backup` 服务,每晚把数据库导出成 `backup-<epochms>.dump` 写进 `./backups`(该目录同时挂载给 postgres 容器)。本地默认保留 14 天,保留份数也可以在应用内「账户 → Backup」调整。
官方推荐用 restic 把 `./backups` 推到 S3/MinIO、Backblaze B2 或 Storj,并给出初始化、每日 cron 与恢复的完整命令。密码文件必须放在 VM 之外(例如密码管理器),否则主机被攻破时异地备份也一并失守。
`./backups` 已挂载进 postgres 容器,所以把恢复出来的 dump 放进宿主机的 `./backups`,再执行 `pg_restore` 即可,不需要额外的 `docker cp`。
下面这些都是官方文档里点名写过、或者部署路径上必然会遇到的症状。先按症状定位原因,再执行对应动作。
| 症状 | 原因 | 处置动作 |
|---|---|---|
| `/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` |
代码按 Apache-2.0 开源、没有订阅费,但运行成本全部由你承担:一台满足 2 vCPU / 4 GB / 20 GB 的 Linux 服务器、可能的带宽与跨区网络费用,以及币安账户本身。官方文档没有给出任何托管或代运维方案,具体前置与规格以官方 deploy 文档为准。如果只是想先看数据、不想承担服务器成本,EasyClaw 的研究路线不需要部署,但它也不会替你下单。
官方文档给出的前置是一台装有 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 提供免安装的研究类任务,但它与本项目没有已证实集成。