AI 摘要
AI
正在生成摘要...

环境信息

说明: 本文为配置思路与示例整理,不代表作者已在自己的服务器上逐项验证全部命令。执行涉及公网暴露、账户权限、数据删除或服务重启的操作前,请先备份,并结合官方文档与实际环境核验。

组件 版本/说明
GitHub Actions Ubuntu latest runner
Docker Buildx 0.24+
VPS 系统 Debian 12 / Ubuntu 22.04
Docker Compose v2.x

为什么需要 CI/CD

手动部署的典型流程:

  1. 本地 docker build → 2. docker push 到镜像仓库 → 3. SSH 到 VPS → 4. docker pull → 5. docker-compose up -d

这套流程每一步都可能出错,而且每次发版都要重复。GitHub Actions 可以把这 5 步压缩成一次 git push

基础 Workflow:构建 + 推送到 GitHub Container Registry

在项目根目录创建 .github/workflows/deploy.yml

YAML
name: Build and Deploy

on:
  push:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha
            type=raw,value=latest

      - name: Build and push Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

关键点

  • GITHUB_TOKEN 是 GitHub 自动提供的,不需要手动配置
  • tagssha + latest,既保留版本追溯又方便部署
  • permissions.packages: write 是推送 ghcr.io 的必要权限

添加 VPS 自动部署

镜像推送成功后,通过 SSH 连到 VPS 拉取新镜像并重启容器:

YAML
jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    outputs:
      image_tag: ${{ steps.meta.outputs.version }}
    steps:
      # ... (上面的构建步骤) ...

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to VPS
        uses: appleboy/ssh-action@v1.2.0
        with:
          host: ${{ secrets.VPS_HOST }}
          username: root
          key: ${{ secrets.VPS_SSH_KEY }}
          port: 22
          script: |
            cd /opt/myapp
            docker compose pull
            docker compose up -d
            docker image prune -f
            echo "Deployed $(date)" >> /var/log/deploy.log

配置 Secrets

在 GitHub 仓库 → Settings → Secrets and variables → Actions 中添加:

Secret 说明 示例
VPS_HOST VPS IP 或域名 192.0.2.1
VPS_SSH_KEY 私钥内容(完整复制) -----BEGIN OPENSSH PRIVATE KEY-----...

踩坑:SSH 私钥必须包含头尾行,复制时容易丢换行符。建议用 cat ~/.ssh/id_ed25519 | pbcopy 粘贴。

优化:构建缓存加速

Docker 构建慢?用 GitHub Actions cache 把构建层缓存起来:

YAML
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

mode=max 可以缓存更多构建层,但实际构建耗时取决于项目内容、缓存命中情况与 runner 环境,需在自己的流水线中观察。

进阶:Matrix 多平台构建

如果需要同时构建 amd64 和 arm64(比如自用 NAS 是 ARM 架构):

YAML
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

注意:多平台构建耗时会翻 2-3 倍(ARM 用 QEMU 仿真),如果不是必须,建议分开构建。

踩坑记录

1. docker compose vs docker-compose

GitHub Actions runner 上预装的是 Docker Compose v2(docker compose)。如果你的部署脚本用的是旧版 docker-compose(v1),会报 command not found

解决:部署脚本统一用 docker compose(空格),或在 workflow 中先安装:

YAML
      - name: Install Docker Compose v2
        run: |
          mkdir -p ~/.docker/cli-plugins/
          curl -SL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose
          chmod +x ~/.docker/cli-plugins/docker-compose

2. SSH 私钥格式

GitHub Actions 的 ssh-action 对私钥格式比较严格。如果用的是传统 RSA 密钥:

TEXT
-----BEGIN OPENSSH PRIVATE KEY-----

和新版格式:

TEXT
-----BEGIN RSA PRIVATE KEY-----

两种都能用,但 不能有额外的 passphrase。部署用的密钥建议专门生成一对:

BASH
ssh-keygen -t ed25519 -f deploy_key -N "" -C "github-actions-deploy"

私钥放 Secrets,公钥加到 VPS 的 ~/.ssh/authorized_keys

3. 网络问题导致 VPS 拉取 ghcr.io 镜像超时

国内 VPS 访问 ghcr.io 经常超时。解决思路:

BASH
# 在 VPS 上配置 registry mirror
cat > /etc/docker/daemon.json << 'EOF'
{
  "registry-mirrors": [
    "https://mirror.ccs.tencentyun.com",
    "https://docker.m.daocloud.io"
  ]
}
EOF
systemctl restart docker

如果 mirror 也不稳定,可以在 CI 中构建后直接 docker save + scp 到 VPS,避免依赖外网仓库。

4. 触发条件:避免重复运行

默认 push 到 main 会触发 workflow。但如果同时推送多个 commit,可能触发多次部署。加个并发控制:

YAML
concurrency:
  group: deploy-production
  cancel-in-progress: false

cancel-in-progress: false 表示不取消正在运行的部署(取消可能导致部署到一半的脏状态),而是排队执行。

完整 Workflow 模板

YAML
name: Build and Deploy

on:
  push:
    branches: [main]

concurrency:
  group: deploy-production
  cancel-in-progress: false

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    outputs:
      image_tag: ${{ steps.meta.outputs.version }}
    steps:
      - uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha
            type=raw,value=latest

      - name: Build and push Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to VPS
        uses: appleboy/ssh-action@v1.2.0
        with:
          host: ${{ secrets.VPS_HOST }}
          username: root
          key: ${{ secrets.VPS_SSH_KEY }}
          script: |
            export IMAGE_TAG=${{ needs.build-and-push.outputs.image_tag }}
            cd /opt/myapp
            docker compose pull
            docker compose up -d --remove-orphans
            docker image prune -f
            echo "[$(date '+%Y-%m-%d %H:%M:%S')] Deployed tag: $IMAGE_TAG" >> /var/log/deploy.log

小结

GitHub Actions 的 CI/CD 核心就是三件事:构建镜像、推送到仓库、SSH 部署到服务器。用好缓存和并发控制,日常发版从手动 5 分钟变成一次 git push。对于个人项目和小团队来说,这套方案零成本、免维护,比 Jenkins 之类的自建 CI 省心太多。

本文示例基于 GitHub Actions latest runner + Docker 27.x 环境整理,实际以项目具体需求为准。

评论