目录
FastAPI 后端 Docker + GHCR + GitHub Actions 自动部署实践
作者:文文
前言
在给 FlyingrtxFast 做后端部署时,我希望把这条链路打通:
- 本地提交代码
- GitHub Actions 自动构建 Docker 镜像
- 镜像推送到 GHCR
- 服务器自动拉取镜像并通过 Docker Compose 重启服务
目标很直接:把后端发布从手工操作改造成可重复执行的标准流程。
这篇文章主要整理这次实战中的部署结构、关键配置思路,以及几个典型问题的排查结论。
一、最终部署链路
最终落地后的链路如下:
本地开发 -> git push -> GitHub Actions
-> 构建 backend Docker 镜像
-> 推送到 GHCR
-> SSH 到服务器
-> docker compose pull / up -d
-> 宿主机 Nginx 转发到 backend 服务
核心组件只有四个:
- Docker
- GitHub Actions
- GHCR(GitHub Container Registry)
- 服务器上的 Docker Compose
这套组合的优点很明确:
- 服务器不再承担复杂构建逻辑
- 部署链路清晰,职责边界明确
- 后续更容易做回滚、多环境扩展和服务拆分
二、后端容器化基础
要实现自动部署,前提是后端能够稳定打包成镜像。
这次补齐的基础文件包括:
backend/Dockerfilebackend/.dockerignorebackend/docker-compose.ymlbackend/.env.example
其中最核心的是 Dockerfile。简化后的思路如下:
FROM python:3.12-slim AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "59888"]
这里采用多阶段构建,主要是为了:
- 分离构建环境与运行环境
- 减少最终镜像体积
- 保持镜像结构更清晰
而 docker-compose.yml 的职责,则是在服务器上以统一方式启动和管理容器。
三、GitHub Actions 工作流设计
自动部署真正跑起来,关键在 workflow。
这条链路的核心步骤可以概括为四步:
1. 检出代码
- uses: actions/checkout@v4
2. 登录 GHCR
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
3. 构建并推送后端镜像
- uses: docker/build-push-action@v6
with:
context: ./backend
file: ./backend/Dockerfile
push: true
4. 通过 SSH 到服务器执行部署命令
服务器侧部署逻辑保持尽量简单:
- 登录 GHCR
- 拉取最新镜像
- 执行
docker compose up -d
这样可以把部署动作压缩为一套稳定、可重复执行的命令,而不是依赖人工逐步操作。
关键配置清单
如果要把这套链路真正跑通,至少要先准备好下面这些配置:
- GitHub Actions Secrets:
GITHUB_TOKEN、服务器 SSH 私钥、服务器地址、部署用户名 - 镜像仓库权限:确认目标仓库允许 CI 推送,服务器也能正常拉取镜像
- 服务器运行环境:已安装 Docker、Docker Compose,并具备镜像拉取权限
- 部署目录文件:至少包含
docker-compose.yml、.env或.env.example - 反向代理链路:宿主机 Nginx 已正确转发到后端服务端口
如果这几项没有先准备好,workflow 即使运行,也很容易卡在登录仓库、拉镜像、端口冲突或容器无法启动这些环节。
四、服务器侧职责划分
在这套方案里,服务器只负责三件事:
- 保存运行时
.env - 拉取镜像
- 用 Docker Compose 启动容器
这种职责划分的好处是:
- 不需要服务器每次都
git pull - 不需要服务器本地重新安装和构建 Python 依赖
- 线上运行环境更容易保持一致
服务器只接收已经构建好的镜像,部署速度和环境可控性都会更好。
五、环境变量处理方式
这次部署里,环境变量分为两类。
1. 构建与部署阶段变量
例如:
- 镜像名
- GHCR 用户名
- GHCR Token
- 服务器地址
- SSH 私钥
这类变量适合放在 GitHub Actions Secrets 中。
2. 应用运行时变量
例如:
- 数据库连接
JWT_SECRET- COS 配置
- 第三方 API Key
- 域名配置
这类变量不应该写进镜像,而应该由服务器上的 .env 提供。
因此,实际运行时容器中的业务配置来源是服务器部署目录下的 .env 文件。
workflow 可以同步 .env.example,但不应该在每次部署时强行覆盖现有 .env。
六、实战中踩到的几个典型问题
这条链路并不是一次就跑通的。真正排查下来,比较典型的问题主要有三个。
1. Docker 问题不一定是业务代码问题
部署阶段最容易出现的误判是:只要容器起不来,就先怀疑代码。
但很多问题其实发生在更外层,例如:
- 端口被占用
- Docker 状态异常
- 容器名冲突
- 配置文件挂载不正确
更合理的排查顺序应该是:
- 查看容器状态
- 查看容器日志
- 查看
docker inspect - 最后再怀疑业务代码
这个顺序很重要,能明显减少排障绕路。
2. 宿主机端口规划必须提前设计
后续在尝试多容器结构时,曾将容器 Nginx 映射到宿主机 18080,结果容器一直无法启动。
最后定位到的根因并不是 Nginx 配置错误,而是宿主机端口冲突:
address already in use
这类问题在 Docker 部署中很常见。表面看像是容器异常,实际只是宿主机端口已经被其他进程占用。
所以部署方案里,端口职责一定要先设计清楚。
3. GitHub Actions 能连服务器,不代表链路一定正确
自动部署的后半段依赖 SSH/SCP,这一段常见问题包括:
- SSH 端口配置错误
- 安全组未放通
- GitHub Runner 无法访问服务器
- 私钥与公钥不匹配
其中一个常见误区是:
- 本地机器可以 SSH 登录服务器
- 不代表 GitHub Actions Runner 也一定可以访问
因为 Runner 运行在公网环境,它和本地网络条件完全不同。这一点在自动部署排障时必须单独考虑。
七、这套链路打通后的价值
这套自动部署跑通后,最大的变化有三点。
1. 发布动作从人工操作变成标准流程
原来的发布方式往往是:
- 登录服务器
- 拉代码
- 手工调整
- 手工重启
而现在变成:
- 提交代码
- push
- 等待 workflow 完成
部署动作被标准化之后,重复劳动会明显减少。
2. 发布过程更容易追踪
每次部署都可以对应到:
- 某次提交
- 某个镜像 tag
- 某次 workflow run
当线上出现问题时,回看、定位和回滚都会更方便。
3. 为后续扩展打下基础
虽然这次重点只是把单后端自动部署打通,但链路建立起来之后,后续继续扩展会轻松很多,例如:
- 多后端拆分镜像
- 多环境部署
- 更细的回滚策略
- 更清晰的服务边界
自动部署的意义不只是节省操作时间,更重要的是让交付过程开始具备工程化基础。
八、经验总结
如果把这次实践压缩成几条最值得保留的结论,我会总结为:
- 服务器尽量只负责拉镜像和启动,不承担复杂构建逻辑。
- 运行时配置放在服务器
.env,不要写进镜像。 - GitHub Actions + GHCR + Docker Compose 是一套适合中小项目的自动部署组合。
- 排障时先检查端口、容器状态、挂载和网络,再去怀疑业务代码。
- 先把单服务自动部署跑通,再考虑复杂架构演进。
结语
这次把 FlyingrtxFast 后端 Docker + GHCR + GitHub Actions 自动部署真正打通之后,最大的变化不是“部署更方便了”,而是整个发布过程开始有了清晰边界和统一方式。
很多时候,自动部署并不是锦上添花,而是项目进入稳定工程化阶段的重要一步。
如果你还在手工传代码、手工重启服务,那么尽早把下面这四件事补齐,部署这件事就能先稳定下来:
- 能构建
- 能推镜像
- 能自动拉起
- 出问题能排查
把这四件事做好,后续再扩展复杂架构,成本会低很多。
文文,2026 年 3 月
