前端自动部署的几种方式
维护项目的一大痛点就是希望推送代码后自动触发更新部署,对此可以采用 GitHub Actions 设置自动部署的工作流(Workflow),对于前端来说有三种主流做法:
| 直传文件 | 服务器构建 | 推送仓库拉取 | |
|---|---|---|---|
| 构建位置 | CI | 服务器 | CI |
| 传输内容 | 静态产物 | 完整源码 | 镜像 |
| 服务器需装 | Web 服务 | Docker + 构建工具 | 仅 Docker |
| 多服务器部署 | 逐个上传 | 逐个构建 | 各自拉取同一镜像 |
| 回滚方式 | 切备份目录 | 切旧镜像 | 拉指定 tag 镜像 |
| 主要局限 | 只能静态文件 | 服务器负担重、多台不一致 | 需维护镜像仓库 |
三种模式遵循同一个核心逻辑:构建 → 替换线上 → 出问题可回退。区别只在于构建在哪发生、中间产物是什么。
模式一:CI 构建 + 直传文件
思路
这是最直接的方式。在 CI 里装依赖、跑构建,把产出的静态文件传到服务器站点目录,用新旧目录互换上线。但它的前提是:构建产物是静态文件,丢到 Web 服务器就能跑。如果项目需要以容器方式运行,光传文件没用,得走 Docker 构建。
流程
推送代码 → CI 构建 → SCP 传文件到服务器 → 备份旧目录 → 替换为新文件 → 刷新服务
示例
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run build
- uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.PRIVATE_KEY }}
source: 'dist/*'
target: '/www/site/dist_new/'
strip_components: 1
- uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.PRIVATE_KEY }}
script: |
cd /www/site
BACKUP="backup_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP"
find . -maxdepth 1 -mindepth 1 ! -name 'dist_new' ! -name 'backup_*' \
-exec mv {} "$BACKUP"/ \;
cp -r dist_new/* . && rm -rf dist_new
ls -dt backup_* | tail -n +6 | xargs -r rm -rf
nginx -s reload
模式二:传源码 + 服务器构建
思路
模式一只能传最终的静态产物,碰上需要完整源码环境才能构建的项目(比如容器化部署)就行不通。模式二把整个仓库源码传到服务器,在服务器上现场构建、打包成镜像再启动。但这种方式的问题是:服务器必须装上全套构建工具,而且如果有好几台服务器,每台都得各自构建一遍,费时且结果可能不一致。
流程
推送代码 → SCP 传源码 → 服务器上构建镜像 → 停旧容器 → 启新容器
示例
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.PRIVATE_KEY }}
source: '.'
target: '/opt/project/build/'
strip_components: 1
- uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.PRIVATE_KEY }}
script: |
cd /opt/project/build
docker build -t my-app:latest .
docker stop my-app 2>/dev/null || true
docker rm my-app 2>/dev/null || true
docker run -d --name my-app -p 3000:80 --restart=always my-app:latest
docker image prune -f
模式三:CI 构建镜像 + 推送仓库 + 服务器拉取
思路
模式二让服务器承担了构建任务,服务器多了就成问题。模式三把构建和部署彻底分开:CI 里构建好镜像,推到镜像仓库;服务器不再参与构建,只需要拉取镜像、替换容器。这样每台服务器拿到的都是同一个镜像,构建环境统一在 CI 里,服务器只管运行。
可以考虑 GitHub Container Registry 托管镜像,其与 Actions 无缝集成,登录直接用
GITHUB_TOKEN即可。
流程
推送代码 → CI 构建镜像 → 推送到镜像仓库 → 服务器拉取镜像 → 停旧容器 → 启新容器
示例
name: Deploy
on:
push:
branches: [main]
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.PRIVATE_KEY }}
script: |
docker pull ghcr.io/${{ github.repository }}:latest
docker stop my-app 2>/dev/null || true
docker rm my-app 2>/dev/null || true
docker run -d \
--name my-app \
-p 3000:80 \
--restart=always \
ghcr.io/${{ github.repository }}:latest
docker image prune -f