摆摊AI 产品实战手册

发布与回滚

一条可重复的发布流程,以及在出问题时三分钟内退回上一个可用版本的准备工作。

发布不难,难的是出问题时能快速退回去。

大部分个人项目的发布流程是"拉代码、重新构建、重启"。这个流程能用,但它有一个隐藏问题:重新构建会覆盖上一个版本,你没有可以退回的东西。

这一页给一条可重复的发布流程,重点是回滚的准备工作。

快速版

  1. 发布前记下当前正在运行的版本,这是你的回滚目标。
  2. 给镜像打带版本的标签,不要只用 latest——否则重新构建就覆盖了上一版。
  3. 发布前备份数据库,且备份要在迁移之前。
  4. 数据库迁移按"扩展—迁移—收缩"做,让应用和数据库能分别回滚。
  5. 发布后按固定清单验证,不只看首页。
  6. 回滚不是失败,是把影响时间压到最短。
  7. 出问题先回滚,再查原因。不要在生产上边查边改。

发布前

四件事,都不能跳:

# 1. 记下当前运行的版本(这是回滚目标)
git rev-parse --short HEAD

# 2. 备份数据库(必须在迁移之前)
docker compose exec -T db pg_dump -U postgres mvp01 | gzip > ~/backups/db-$(date +%F-%H%M).sql.gz

# 3. 备份当前应用目录和配置
tar czf ~/backups/app-pre-$(git rev-parse --short HEAD).tgz .env docker-compose.yml

# 4. 确认没有正在进行的危险操作(数据导入、批量任务)
docker compose logs --tail 50 app

第 1 步最容易被跳过,也最要紧。出问题时你需要知道"退回到哪",而不是在慌乱中翻历史记录。

pg_dump 那条命令里的 -T 不能省,它阻止 Docker 分配伪终端,否则管道里的数据会被破坏。

让自己有东西可以退回

这是我们自己配置里的一个真实缺口,值得单独说。

常见的发布命令是这样:

git pull && docker compose up -d --build

它能工作,但 --build 会就地重建镜像。构建完成后,上一个版本的镜像就没有标签指向它了。这时候要回滚,只能重新用旧代码再构建一次——在故障时多花几分钟构建,这几分钟全是用户在受影响。

改进做法是发布前给当前镜像打个标签:

# 发布前:给当前镜像打上版本标签
docker tag <当前镜像> myapp:$(git rev-parse --short HEAD)

# 之后回滚就是改一下用哪个标签,不需要重新构建

或者更简单的思路:构建产物和运行分开。 先构建并打标签,确认构建成功,再切换运行的版本。这样"构建失败"和"新版本有问题"是两个独立的阶段,不会互相拖累。

如果你现在的发布方式是 up -d --build,至少要知道你的回滚时间等于一次完整构建时间。测一下这个时间有多长,然后判断能不能接受。接受不了就改成打标签的方式。

数据库迁移的特殊性

代码可以随便回滚,数据库不行。

删掉一列的迁移执行完,旧数据就没了。这时候即使把代码退回上一版,应用也跑不起来——它要读那一列。

所以迁移要按三步走,让每一步都可以单独回滚:

步骤做什么这一步能回滚吗
扩展加新列/新表,允许为空,旧代码不受影响能,直接删掉新增的
迁移新代码同时写新旧两处,回填历史数据能,退回旧代码即可
收缩确认稳定后再删旧列不能

关键在于**"收缩"这一步要单独发布,而且要等前面稳定运行一段时间之后**。把三步压在一次发布里,就失去了分别回滚的能力。

详见数据库迁移、备份与恢复。

发布

# 拉取目标版本
git fetch && git checkout <目标版本>

# 构建并启动
docker compose up -d --build

# 看日志确认启动成功,不要看到"启动中"就走
docker compose logs -f app

日志要看到应用真的开始监听端口,而不是只看到构建成功。构建成功和运行成功是两件事。

发布后验证

固定清单,每次都跑同样几项:

  • 首页能打开
  • 一个需要读数据库的页面能打开
  • 登录流程能走通
  • 一个受保护的页面在未登录时正确跳转
  • 第三方回调地址返回预期状态
  • 日志里没有新出现的报错

第二项是重点。首页是静态页的话,它返回 200 几乎说明不了什么。 数据库连不上、迁移没跑完、连接池配错了,首页照样正常。

第五项容易漏。支付回调、OAuth 回调这些路径平时没人访问,出问题要等到真实用户触发才会发现——那时候是订单已经付款但没发货。

回滚

判断标准要提前定,不要在故障时临时讨论。

出现下面任一情况,直接回滚:

  • 核心流程(登录、支付、主要功能)不可用
  • 出现数据写错的风险
  • 日志里有持续增长的报错
  • 你在五分钟内找不到原因

最后一条是最重要的一条。"再看五分钟就能查出来"这个判断通常是错的,而这五分钟里用户一直在受影响。

回滚步骤:

# 1. 切回上一个版本(发布前记下的那个)
git checkout <发布前记下的版本>

# 2. 启动
docker compose up -d --build

# 3. 验证核心流程恢复
# 4. 通知受影响的用户

如果这次发布包含了不可逆的迁移(删列、改类型),代码回滚不够,需要从备份恢复数据库。这就是为什么备份必须在迁移之前做。

回滚之后

回滚不是终点,是止损。之后还要做:

  1. 在本地或测试环境复现问题,不要在生产上试
  2. 找到根因,不是找到"哪一行改了就好了"
  3. 修好之后重新走完整发布流程
  4. 把这次的教训加进发布清单

不要把"以后更加小心"当成整改措施。它不可执行、不可验证,下次一定会再犯。有效的整改是往清单里加一项具体检查,或者加一个自动化校验。

什么时候不该发布

  • 你接下来两小时不在电脑前
  • 数据库备份没做或没验证过
  • 这次改动你自己没在生产构建下跑过
  • 正在进行数据导入、批量任务这类不能中断的操作
  • 你已经很累了

最后一条不是玩笑。发布本身很快,处理发布出的问题很慢。 在没有精力排障的时候发布,等于把小问题变成长时间故障。

常见卡点

卡在哪可能原因试试这样
要回滚但没有旧镜像--build 就地覆盖了发布前给镜像打版本标签
代码回滚了但应用还是起不来迁移不可逆从迁移前的备份恢复数据库
不知道该退回哪个版本发布前没记发布前第一件事就是记下当前版本
首页正常但功能全挂只验证了静态页验证一个读数据库的页面
支付回调坏了很久才发现回调路径没纳入验证加进发布后清单
在生产上边查边改没有先止损先回滚,再在本地复现
同样的问题反复出现整改措施不可执行往清单里加具体检查项
备份是在迁移之后做的顺序错了备份必须在迁移之前

验收标准

  • 有一条写下来的发布流程,每次照着走
  • 发布前会记录当前运行的版本
  • 知道自己的回滚需要多长时间,并且能接受
  • 备份在迁移之前,且验证过能恢复
  • 迁移按扩展—迁移—收缩拆开,收缩单独发布
  • 发布后验证清单包含一个读数据库的页面和一个回调地址
  • 回滚判断标准提前写好了,不在故障时讨论
  • 至少完整演练过一次回滚

本章动作

演练一次回滚,在没有故障的时候。

计时:从"决定回滚"到"核心流程恢复"用了多久。如果超过十分钟,说明缺少准备——通常是缺一个可以直接切换的旧版本镜像。

下一步

  1. 数据库迁移、备份与恢复——迁移和备份的完整做法
  2. 监控、日志与故障处理——怎么发现该回滚了
  3. 生产环境验收——第一次上线前的完整检查
  4. 正式发布计划——对外发布的节奏安排

On this page