2293 字
11 分钟

GitHub Actions CI/CD 完全指南 2026:自动化构建 + 测试 + 部署实战

手动 git push 之后,登到服务器 git pulldocker compose restart……每次发布都要重复这套动作,既低效又容易出错。GitHub Actions 让这一切自动化:代码推送 → 自动测试 → 自动构建镜像 → 自动部署上线,全程无需人工介入。

本文从语法基础到生产级实战,覆盖 GitHub Actions 所有核心场景。


一、Workflow 语法基础#

.github/workflows/ci.yml
name: CI/CD Pipeline # Workflow 名称(显示在 Actions 标签页)
# 触发条件
on:
push:
branches: [main, develop]
paths-ignore: # 这些文件变更不触发
- "**.md"
- ".gitignore"
pull_request:
branches: [main]
schedule:
- cron: "0 2 * * *" # 每天凌晨2点定时运行
workflow_dispatch: # 允许手动触发(Actions 页面有按钮)
inputs:
environment:
description: "部署环境"
required: true
default: "staging"
type: choice
options: [staging, production]
# 环境变量(所有 job 共享)
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }} # 仓库名,如 owner/repo
jobs:
# ── Job 1:代码检查 ──────────────────────────────────────────
lint:
name: Lint & Format Check
runs-on: ubuntu-24.04 # Runner 类型
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip" # 自动缓存 pip 依赖
- name: Install dependencies
run: pip install ruff mypy
- name: Run Ruff linter
run: ruff check .
- name: Run mypy type check
run: mypy src/
# ── Job 2:测试(依赖 lint 通过)────────────────────────────
test:
name: Run Tests
runs-on: ubuntu-24.04
needs: [lint] # 等 lint job 成功后才运行
services:
# 启动附加服务容器(集成测试使用)
postgres:
image: postgres:16
env:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
ports:
- 6379:6379
env:
DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
REDIS_URL: redis://localhost:6379
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
- run: pip install -r requirements.txt -r requirements-dev.txt
- name: Run pytest with coverage
run: |
pytest tests/ \
--cov=src \
--cov-report=xml \
--cov-report=term-missing \
--junitxml=test-results.xml \
-v
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
# ── Job 3:多版本矩阵测试 ────────────────────────────────────
test-matrix:
name: Test Python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false # 某个矩阵失败不停止其他
matrix:
os: [ubuntu-24.04, macos-14]
python-version: ["3.11", "3.12"]
exclude:
- os: macos-14
python-version: "3.11" # 排除特定组合
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install -e ".[dev]"
- run: pytest tests/ -x

二、Docker 镜像自动构建与发布#

.github/workflows/docker-publish.yml
name: Build & Push Docker Image
on:
push:
branches: [main]
tags: ["v*.*.*"] # 发布 tag 时触发
# 使用 OIDC 无密钥认证(推荐)
permissions:
contents: read
packages: write # 允许推送到 GHCR
id-token: write # OIDC token
jobs:
docker:
name: Build & Push
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
# 设置 QEMU(多平台构建需要)
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
# 设置 Docker Buildx
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# 登录 GitHub Container Registry(用 GITHUB_TOKEN,无需配置 Secret)
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# 同时登录 Docker Hub(可选)
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# 提取元数据(生成 tag 和 label)
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ github.repository }}
${{ secrets.DOCKERHUB_USERNAME }}/myapp
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}} # v1.2.3 → 1.2.3
type=semver,pattern={{major}}.{{minor}} # v1.2.3 → 1.2
type=sha,prefix=sha- # git commit sha
type=raw,value=latest,enable={{is_default_branch}}
# 构建并推送(多平台:amd64 + arm64)
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha # 使用 GitHub Actions 缓存层
cache-to: type=gha,mode=max # 最大化缓存
build-args: |
BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
GIT_SHA=${{ github.sha }}

三、自动化部署场景#

3.1 部署到 Vercel(静态站点/Next.js)#

.github/workflows/deploy-vercel.yml
name: Deploy to Vercel
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-24.04
environment:
name: production
url: ${{ steps.deploy.outputs.url }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"
- run: corepack enable && pnpm install --frozen-lockfile
- run: pnpm build
- name: Deploy to Vercel
id: deploy
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: "--prod"

3.2 部署到 VPS(SSH + Docker Compose)#

.github/workflows/deploy-vps.yml
name: Deploy to VPS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-24.04
environment: production
steps:
- uses: actions/checkout@v4
# 构建并推送镜像(复用上面的 Docker 发布步骤)
- name: Build & Push image
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
# SSH 连接服务器,拉取新镜像并重启
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
port: ${{ secrets.VPS_PORT }}
script: |
set -e
cd /opt/myapp
# 更新镜像版本
export IMAGE_TAG=${{ github.sha }}
# 拉取新镜像
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io \
-u ${{ github.actor }} --password-stdin
docker pull ghcr.io/${{ github.repository }}:${IMAGE_TAG}
# 滚动更新(零停机)
docker compose up -d --no-deps --build app
# 清理旧镜像
docker image prune -f
echo "Deployed image: ${IMAGE_TAG}"

3.3 部署到 Kubernetes#

.github/workflows/deploy-k8s.yml
name: Deploy to Kubernetes
on:
push:
tags: ["v*"]
jobs:
deploy:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
# 使用 OIDC 认证 AWS(无需静态 Access Key)
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/github-actions-role
aws-region: ap-northeast-1
- name: Login to ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Build & Push to ECR
run: |
IMAGE_URI=123456789.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:${{ github.ref_name }}
docker build -t $IMAGE_URI .
docker push $IMAGE_URI
echo "IMAGE_URI=$IMAGE_URI" >> $GITHUB_ENV
- name: Set up kubectl
uses: azure/setup-kubectl@v4
- name: Configure kubeconfig
run: aws eks update-kubeconfig --name my-cluster --region ap-northeast-1
- name: Deploy to K8s
run: |
kubectl set image deployment/myapp \
app=${{ env.IMAGE_URI }} \
--namespace=production
kubectl rollout status deployment/myapp \
--namespace=production \
--timeout=5m

四、Cache 加速策略#

# 完整缓存策略示例
steps:
# Node.js 依赖缓存(setup-node 内置)
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm" # 自动处理 ~/.pnpm-store
# Python 依赖缓存(setup-python 内置)
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
# Gradle / Maven 缓存
- uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
restore-keys: gradle-${{ runner.os }}-
# Rust 编译缓存
- uses: Swatinem/rust-cache@v2
with:
workspaces: ". -> target"
# 自定义路径缓存(适合任何工具)
- uses: actions/cache@v4
with:
path: ~/.cache/custom-tool
key: custom-${{ runner.os }}-${{ hashFiles('**/lockfile') }}
restore-keys: custom-${{ runner.os }}- # 前缀匹配(部分缓存命中)

五、Secrets 与 Environment 管理#

.github/workflows/deploy-multi-env.yml
name: Multi-Environment Deploy
on:
push:
branches: [main, staging]
jobs:
deploy:
runs-on: ubuntu-24.04
# 根据分支选择 Environment(不同 Environment 有不同 Secrets)
environment:
name: ${{ github.ref_name == 'main' && 'production' || 'staging' }}
steps:
- uses: actions/checkout@v4
- name: Deploy
env:
# 引用当前 Environment 的 Secrets
DB_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
run: |
echo "Deploying to ${{ vars.ENVIRONMENT_NAME }}"
# vars 是非敏感的配置变量(公开可见),secrets 是加密的
./scripts/deploy.sh \
--env ${{ vars.ENV_NAME }} \
--version ${{ github.sha }}
# production 环境需要手动审批(在 Environment 设置中配置 Required reviewers)
notify:
runs-on: ubuntu-24.04
needs: deploy
if: always() # 无论成功失败都发通知
steps:
- name: Notify Telegram
uses: appleboy/telegram-action@master
with:
to: ${{ secrets.TELEGRAM_CHAT_ID }}
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
message: |
${{ job.status == 'success' && '✅' || '❌' }} 部署完成
仓库: ${{ github.repository }}
分支: ${{ github.ref_name }}
提交: ${{ github.sha }}
状态: ${{ needs.deploy.result }}

六、可复用 Workflow(Reusable Workflow)#

# .github/workflows/_build-and-test.yml(可复用,文件名以_开头为惯例)
name: "[Reusable] Build and Test"
on:
workflow_call: # 声明为可复用 Workflow
inputs:
python-version:
required: false
type: string
default: "3.12"
run-integration-tests:
required: false
type: boolean
default: false
secrets:
CODECOV_TOKEN:
required: false
jobs:
build-test:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
cache: "pip"
- run: pip install -e ".[dev]"
- run: pytest tests/unit/ -v
- name: Integration tests
if: inputs.run-integration-tests
run: pytest tests/integration/ -v
# .github/workflows/ci.yml(调用可复用 Workflow)
jobs:
test:
uses: ./.github/workflows/_build-and-test.yml
with:
python-version: "3.12"
run-integration-tests: true
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
test-311:
uses: ./.github/workflows/_build-and-test.yml
with:
python-version: "3.11"

七、编写自定义 Action#

.github/actions/notify-deploy/action.yml
name: "Notify Deployment"
description: "发送部署结果通知"
inputs:
status:
description: "部署状态 (success/failure)"
required: true
environment:
description: "部署环境"
required: true
telegram-token:
description: "Telegram Bot Token"
required: true
telegram-chat-id:
description: "Telegram Chat ID"
required: true
outputs:
message-id:
description: "发送的消息 ID"
value: ${{ steps.send.outputs.message-id }}
runs:
using: "composite"
steps:
- name: Send Telegram notification
id: send
shell: bash
env:
STATUS: ${{ inputs.status }}
ENV: ${{ inputs.environment }}
TOKEN: ${{ inputs.telegram-token }}
CHAT_ID: ${{ inputs.telegram-chat-id }}
run: |
EMOJI=$([[ "$STATUS" == "success" ]] && echo "✅" || echo "❌")
MESSAGE="${EMOJI} *部署${STATUS}*%0A环境: ${ENV}%0A时间: $(date '+%Y-%m-%d %H:%M')"
RESPONSE=$(curl -s -X POST \
"https://api.telegram.org/bot${TOKEN}/sendMessage" \
-d "chat_id=${CHAT_ID}&text=${MESSAGE}&parse_mode=Markdown")
MESSAGE_ID=$(echo $RESPONSE | jq -r '.result.message_id')
echo "message-id=$MESSAGE_ID" >> $GITHUB_OUTPUT
# 在其他 Workflow 中使用自定义 Action
- name: Notify deployment
uses: ./.github/actions/notify-deploy
with:
status: ${{ job.status }}
environment: production
telegram-token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
telegram-chat-id: ${{ secrets.TELEGRAM_CHAT_ID }}

八、安全最佳实践#

# 权限最小化(默认关闭所有权限,按需开启)
permissions:
contents: read # 读取代码
packages: write # 推送到 GHCR(仅需要时)
id-token: write # OIDC(仅需要时)
# 使用 SHA 固定 Action 版本(防止供应链攻击)
# 不推荐:uses: actions/checkout@v4
# 推荐:固定到具体 commit SHA
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# 避免在 run 步骤中直接使用用户输入
# 错误:run: echo "${{ github.event.pull_request.title }}" # 注入风险!
# 正确:
- name: Safe echo
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: echo "$PR_TITLE" # 通过环境变量传递,避免 shell 注入
# 为 Fork PR 限制 Secrets 访问
- name: Skip for forks
if: github.event.pull_request.head.repo.full_name == github.repository
run: echo "Only runs for PRs from same repo"

九、常用技巧速查#

# ── 条件执行 ───────────────────────────────────────────────────
- run: deploy.sh
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
# ── 并行 Job(默认就是并行)────────────────────────────────────
jobs:
job-a:
runs-on: ubuntu-latest
steps: [...]
job-b:
runs-on: ubuntu-latest
steps: [...] # job-a 和 job-b 并行运行
# ── Job 间传递数据(outputs)──────────────────────────────────
jobs:
build:
outputs:
version: ${{ steps.get-version.outputs.version }}
steps:
- id: get-version
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
deploy:
needs: build
steps:
- run: echo "Deploy version ${{ needs.build.outputs.version }}"
# ── 获取 PR 信息 ────────────────────────────────────────────────
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '✅ 测试通过,可以合并!'
})
# ── 上传/下载构建产物 ──────────────────────────────────────────
- uses: actions/upload-artifact@v4
with:
name: dist-files
path: dist/
retention-days: 7
- uses: actions/download-artifact@v4
with:
name: dist-files
path: dist/
# ── 设置动态环境变量 ────────────────────────────────────────────
- name: Set version
run: |
VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "0.0.0")
echo "VERSION=$VERSION" >> $GITHUB_ENV # 后续步骤可用 ${{ env.VERSION }}
echo "BUILD_DATE=$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV

相关文章

本文基于 GitHub Actions 2026年8月最新功能编写。Actions 版本(actions/checkout@v4 等)以当前最新版为准,推荐生产环境固定至 SHA 版本号以防供应链攻击。

GitHub Actions CI/CD 完全指南 2026:自动化构建 + 测试 + 部署实战
https://971918.xyz/posts/docs/github-actions-cicd-guide/
作者
九所长
发布于
2026-08-11
许可协议
CC BY-NC-SA 4.0