跳至内容

CI/CD Patterns with GitHub Actions

2026-05-15

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:

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:

- 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:

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:

# .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:

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.