记录一下 blog 重新部署的过程,从 pm2 踩坑到改用 Docker 镜像,再接上 GitHub Actions 自动发布。
2026-06-06 00:30:26
之前那台服务器换了手机号,我也把续费这件事忘了,结果服务器直接到期,数据全丢了,只能重新部署一遍。我对不起各位 !!!
最开始我用的是 pm2,图的就是简单省事。本地 build 完之后,把 .output 用 scp 丢到服务器,再执行一次 pm2 restart。
结果遇到了: nuxt 刷新直接 404。
查了一圈之后,基本可以确定问题还是出在平台差异上。本地正常,服务器异常;再看输出目录里还带着 node_modules
,就更容易怀疑到环境差异了。毕竟本地和服务器环境不一样,依赖细节也会有差别。
到这里就很自然了,这不就是 docker 最擅长解决的事情吗。 所以直接把应用打成镜像,统一按镜像部署,避免环境差异。
不过想想每次都要:
- 本地构建镜像
- 手动推镜像
- 登录服务器重启服务
那其实还是重复劳动。
# 1.
docker build --platform linux/amd64 -t blog:test .
# 2.
docker tag blog:test ACR_REGISTRY/ACR_NAMESPACE/blog:latest
# 3.
docker push ACR_REGISTRY/ACR_NAMESPACE/blog:latest
所以顺手把这套流程也一起处理吧,直接接 CI/CD。
整个流程其实就三步:
master分支有更新时,自动构建镜像- 把构建好的镜像推到阿里云
ACR - 通过
SSH登录服务器,在现有的docker-compose目录里重启blog服务
用 GitHub Actions 自动构建
分支一更新就触发
GitHub Actions 处理
on:
# 推送到 对应分支 时自动部署。
push:
branches:
- master
# 允许在 GitHub Actions 页面手动触发
workflow_dispatch:
镜像推到阿里云 ACR
镜像仓库我放在阿里云 ACR 私仓里,所以首先将仓库地址和镜像名配好。
env:
# 私有镜像仓库地址
REGISTRY: ${{ secrets.ACR_REGISTRY }}
# 镜像名
IMAGE_NAME: ${{ secrets.ACR_NAMESPACE }}/blog
Action 里具体做什么
触发条件和镜像地址准备好之后,就可以进行: 拉代码、登录仓库、构建镜像、推送镜像。
jobs:
docker:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
# 拉取仓库代码
- name: Checkout
uses: actions/checkout@v6
# Docker Buildx,构建多平台镜像。
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
# 登录阿里云 ACR
- name: Login To Aliyun ACR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}
# # 生成镜像元数据和 tag。
# - latest: 服务器部署时使用
# - 提交的 SHA: 方便进行版本回溯
- name: Extract Docker Metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest
type=raw,value=${{ github.sha }}
# 基于当前镜像,并推送到 ACR。
- name: Build And Push Image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# 使用 GitHub Actions 缓存加速重复构建。
cache-from: type=gha
cache-to: type=gha,mode=max
服务器上更新服务
镜像推上去之后,最后一步就是连到服务器,把服务更新起来。
- name: Pull And Rebuild On Server
uses: appleboy/ssh-action@v1.2.2
env:
ACR_REGISTRY: ${{ secrets.ACR_REGISTRY }}
ACR_USERNAME: ${{ secrets.ACR_USERNAME }}
ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }}
# 服务器上存在 docker-compose.yml 的目录
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
password: ${{ secrets.SSH_PWD }}
envs: ACR_REGISTRY,ACR_USERNAME,ACR_PASSWORD,DEPLOY_PATH
script: |
# `-e` 遇错退出,`-u` 遇到未定义变量直接报错,避免静默失败。
set -eu
cd "$DEPLOY_PATH"
# 拉新的镜像
docker compose pull blog
# 重启
docker compose up -d --force-recreate blog
# 删除空的镜像
docker image prune -f
额外踩坑记录
有些镜像因为网络原因,服务器上没法直接从 Docker Hub 拉。
我选择的是: 服务器拉不下来,那我本地拉好再传上去。
- 拉取
amd64镜像docker pull --platform linux/amd64 artalk/artalk-go:latest - 导出镜像
docker save -o artalk-go-amd64.tar artalk/artalk-go:latest - 上传到服务器
scp artalk-go-amd64.tar name@ip:path - 服务器上导入
docker load -i path/artalk-go-amd64.tar docker manifest inspect artalk/artalk-go:latest # 查看当前架构支持哪些镜像 docker inspect artalk/artalk-go:latest --format='{{.Architecture}}' # 验证镜像架构 docker pull artalk/artalk-go@sha256:34c9287650c6a662fb1bba7435b6a02f992f96aa0d2a7fa2bac4e5bfe4fa4f97 # 拉取对应的镜像 - 只要镜像已经落到服务器上,后面怎么配就都简单了