Files
Hugo/content/blog/cicd-github-actions.md
T

102 lines
2.9 KiB
Markdown

---
title: "CI/CD Patterns with GitHub Actions"
date: 2026-05-15T09:00:00+08:00
draft: false
poet: |
大音希声,大象无形。
——老子,中国,约前571–约前471
description: "Build matrices, caching strategies, environment-specific deployments, and reusable workflows. Patterns that scale from solo projects to team repos."
tags:
- CI/CD
- GitHub
- DevOps
---
GitHub Actions is now the default CI for most open-source projects. Build matrices save config lines, caching `node_modules` and Go modules saves minutes per run. Reusable workflows keep things DRY across repos.
## Build Matrices
Don't copy-paste job definitions for different Node versions or OS targets. Use a matrix:
```yaml
jobs:
test:
strategy:
matrix:
node: [18, 20, 22]
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm test
```
This single definition expands to 6 parallel jobs. Add `fail-fast: false` if you want all runs to complete even when one fails — helpful for catching platform-specific bugs in one CI run.
## Smart Caching
Caching is the easiest way to cut CI minutes:
```yaml
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: npm-${{ runner.os }}-
```
The `restore-keys` fallback is important: even a partial cache hit saves time. For Go modules, cache `~/go/pkg/mod`. For Docker builds, use BuildKit's registry cache (`--cache-from type=registry`) for layer caching across CI runs.
## Environment-Specific Deployments
Use environments to gate production deploys:
```yaml
deploy-prod:
needs: [test, build]
environment: production
steps:
- run: ./deploy.sh production
```
The `environment: production` lets you set required reviewers, wait timers, and environment-specific secrets in the GitHub UI. No more accidentally deploying to prod from a feature branch.
## Reusable Workflows
When you maintain multiple repos, reusable workflows prevent drift:
```yaml
# .github/workflows/deploy.yml in a shared repo
on:
workflow_call:
inputs:
environment:
required: true
type: string
secrets:
AWS_ROLE:
required: true
```
Call it from any repo:
```yaml
jobs:
deploy:
uses: org/shared-workflows/.github/workflows/deploy.yml@main
with:
environment: staging
secrets:
AWS_ROLE: ${{ secrets.AWS_ROLE }}
```
Update the shared workflow once, and every repo benefits. This pattern scales well for orgs with 10+ services.
## Practical Tips
- Set `timeout-minutes` on every job — the default is 360 minutes, and you don't want a hung test burning your quota.
- Use `concurrency` to cancel redundant runs when pushing to the same PR multiple times.
- Pin action versions to SHA hashes for supply-chain security, not just tags.