ARTICLE · 1349612
改过源码的开源项目怎么升级?一套不丢数据、可回滚的流程
开源项目发了新版本,你很想升级。
可你改过它的代码,服务还在线上跑着,数据库里存的是真实数据。直接 git pull?要么冲突一大片,要么把自己的修改冲掉。

我最近在一台 4G 内存的小服务器上升级了一个自部署的开源服务,折腾了一整晚。踩完坑后,我把过程整理成一套通用流程,适用于大多数“用源码构建镜像 + Docker Compose 部署”的项目。
文中的
/data/app-src、myapp:custom、app、postgres都是示例名称,请换成你自己的目录、镜像名和服务名。
一、先备份:源码和数据分开备份
cp -a /data/app-src /data/app-src.bak-$(date +%Y%m%d)
-a 会把 .git 目录、隐藏文件、文件权限和还没提交的修改一起复制。后面的 Git 操作就算改乱了,也能用备份恢复。
但备份了源码不等于备份了数据。数据库正在运行时直接复制它的文件,得到的副本很可能不完整。数据库要单独导出:
cd /data/app-src
mkdir -p backups
docker compose exec -T postgres sh -c \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB"' \
> backups/db-$(date +%Y%m%d).sql
ls -lh backups/
导出文件大小明显不为 0,才算导出成功。MySQL 改用 mysqldump,做法相同。
二、把自己的修改提交到本地分支
很多人以为:这是别人的项目,我没权限,不能 commit。
其实 commit 只写入你本地的仓库,不需要任何权限;需要权限的是 push。整个升级过程都不需要 push。
git switch -c local/custom-$(date +%Y%m%d)
git status
git add 你改过的文件1 你改过的文件2
git commit -m "Preserve local customizations"
两个提醒:
- 不要用 `git add .`
。 git status里那些红色的数据目录、.env和 Compose 配置文件,大多是部署时生成的未跟踪文件。全部添加的话,密钥和数据库文件都会被提交进去。 如果提示 Author identity unknown,可以只给这一条命令临时指定身份,不必修改全局配置:
git -c user.name="你的名字" -c user.email="you@example.com" \
commit -m "Preserve local customizations"
三、合并上游:冲突的地方两边都要保留
合并前,先看一眼上游的 Release Notes 或 CHANGELOG,重点留意三件事:数据库迁移、配置项变更、被废弃的接口。
git fetch origin
git merge --no-commit --no-ff origin/main
git status
--no-commit 会让合并停在提交之前,你可以先检查结果,再决定是否提交。
处理冲突只有两条原则:
只修改 <<<<<<<` 和 `>>>>>>>之间的冲突块,不要让整个文件直接采用你的版本或上游的版本;- 两边的有效内容都保留:你的定制要留着,上游新增的字段和参数也要留着。
改完后检查有没有残留的冲突标记,命令没有任何输出才算清理干净:
git grep -nE '^(<<<<<<<|=======|>>>>>>>)'
我在这里吃过亏:复制冲突内容时漏掉了半行,合并照样提交成功了,直到构建时才报语法错误。合并成功只说明冲突标记清理干净了,不代表代码能编译。 条件允许的话,提交前先在本地跑一次构建或类型检查。
四、改动线上之前,先准备好回滚
构建新镜像时,myapp:custom 这个标签会被挪到新镜像上。所以动手前,先给当前正在运行的镜像另外打一个回滚标签:
docker tag "$(docker inspect "$(docker compose ps -q app)" --format '{{.Image}}')" \
myapp:rollback-$(date +%Y%m%d)
有两点需要知道:
- 构建新镜像不会影响正在运行的容器。容器一直使用旧镜像,只有重建容器后才会换成新镜像。
- 新版本启动时可能会自动迁移数据库。迁移之后,只换回旧镜像不一定能恢复原样,所以第一步的数据库导出必不可少。
五、构建镜像:别凭感觉判断是否成功
如果 docker compose build 提示 No services to build,说明 Compose 文件里只写了 image:,没有 build: 配置。这时直接用 docker build 构建。
构建耗时较长,建议放到后台运行,这样 SSH 断开也不会中断构建:
nohup sh -c 'docker build --pull --progress=plain -t myapp:custom . ; echo "BUILD_EXIT=$?"' \
> build.log 2>&1 &
tail -f build.log
日志末尾出现 BUILD_EXIT=0 才算构建成功。按 Ctrl+C 只会退出日志查看,不会停止构建。
然后对比新旧镜像的 ID 和创建时间:
docker image inspect myapp:custom myapp:rollback-日期 \
--format '{{.Id}} {{.Created}}'
两个 ID 不同,而且新镜像的创建时间是刚才,才说明新镜像真的生成了。我第一次构建时服务器卡死了,重启后一查,两个 ID 一模一样,说明那次根本没构建成功。
六、内存小的服务器,前端构建最容易出问题
带前端的项目,tsc、vite build 这类步骤非常吃内存。我那台 3.6G 内存的机器,Node 用到 1.8G 左右就报了 JavaScript heap out of memory(内存不足)。
只加 swap 还是不够,最后三招一起用才构建成功。
1. 暂停服务,腾出内存(先确认当前没人在用):
docker compose stop
只用 stop,不要用 down -v,-v 会删除数据卷。
2. 添加临时 swap,先用 df -h 确认磁盘空间足够:
dd if=/dev/zero of=/data/swap-build bs=1M count=4096
chmod 600 /data/swap-build
mkswap /data/swap-build && swapon /data/swap-build
free -h
3. 调高 Node 的堆内存上限,把 Dockerfile 里的前端构建命令改成:
RUN NODE_OPTIONS=--max-old-space-size=3072 npm run build
数值不要超过机器实际可用的内存加 swap。改完的 Dockerfile 也记得提交到本地分支。
七、替换容器,并确认真的换上了新镜像
如果上一步停掉了所有服务,要先启动数据库等依赖服务,再替换应用:
docker compose up -d --no-build --pull never postgres redis
docker compose up -d --no-deps --no-build --pull never --force-recreate app
--no-deps | |
--no-build | |
--pull never | |
--force-recreate |
替换后检查:
docker compose ps
docker compose logs --tail=100 app
docker image inspect myapp:custom --format '{{.Id}}'
docker inspect "$(docker compose ps -q app)" --format '{{.Image}}'
同时满足三点,才算替换完成:容器在运行、日志没有报错、最后两条命令输出的镜像 ID 一致。
八、升级后出问题,先分层定位
服务起来了,接入它的应用却报错,先别急着回滚,用 curl 一层一层测试:
- 服务层
:健康检查接口能不能访问; - 认证层
:带上密钥访问一个只读接口,能不能正常返回; - 业务层
:发一个真实请求,看具体的错误信息。
我这次遇到的几个报错,最后发现都不是升级本身造成的:一个是后台没有开启对应的协议开关,一个是客户端还在用已经被上游下线的旧模型,还有一个是上游拦截了测试内容。
升级后出现的报错,不一定是升级导致的。 先看清报错出自哪一层,再对症处理。
另外,测试时别把密钥明文留在截图或命令历史里。一旦泄露,立即在后台作废,重新生成一个。
九、收尾,附一张可收藏的清单
确认运行稳定后,删除临时 swap:
swapoff /data/swap-build && rm /data/swap-build
回滚镜像和数据库备份再保留几天,确认没问题后再清理。
最后,把整套流程压缩成一张清单:
用 cp -a备份源码,并单独导出数据库把本地修改提交到本地分支,不用 git add .先看更新说明,再用 merge --no-commit合并- 冲突块两边都保留,检查残留标记,并先编译验证
- 给旧镜像打回滚标签
后台构建,确认 BUILD_EXIT=0和新旧镜像 ID 不同- 内存小:暂停服务、加临时 swap、调高 Node 堆内存上限
用 --no-deps --force-recreate替换,核对容器的镜像 ID- 升级后分层排查,别急着回滚
升级本身不难,难的是每一步都留好退路。
如果你也在自己的服务器上跑开源项目,不妨收藏这份清单,下次升级前对照着过一遍。觉得有用的话,欢迎点赞,或转发给同样在折腾自部署的朋友。















































































