102 lines
2.9 KiB
Markdown
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.
|