CI/CD Pipelines with GitHub Actions

GitHub Actions has become the dominant CI/CD platform. This guide covers production patterns beyond the basic "run tests" workflow.

1. Workflow Structure

name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  NODE_VERSION: '20'

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: ${{ env.NODE_VERSION }} }
      - run: npm ci
      - run: npm run lint

  test:
    needs: lint
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18, 20, 22]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: ${{ matrix.node-version }} }
      - run: npm ci
      - run: npm test

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build

  deploy:
    needs: build
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh

2. Matrix Builds

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20, 22]
      fail-fast: false
      max-parallel: 6
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: ${{ matrix.node-version }} }
      - run: npm ci
      - run: npm test

3. Caching

steps:
  - uses: actions/cache@v4
    with:
      path: ~/.npm
      key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
      restore-keys: |
        npm-${{ runner.os }}-
  - run: npm ci

Cache tips:

  • Use hashFiles() with the lock file
  • Use restore-keys for fallback
  • Cache Docker layers with docker/build-push-action@v5 and cache-from: type=gha

4. Docker Build and Push

jobs:
  docker:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-qemu-action@v3
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v5
        with:
          context: .
          push: ${{ github.ref == 'refs/heads/main' }}
          tags: ghcr.io/${{ github.repository }}:latest,${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          platforms: linux/amd64,linux/arm64

5. Deployment Environments

jobs:
  staging:
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.example.com
    steps:
      - uses: actions/checkout@v4
      - run: ./deploy-staging.sh

  production:
    needs: staging
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://example.com
    # Environment requires manual approval
    steps:
      - uses: actions/checkout@v4
      - run: ./deploy-production.sh

6. Reusable Workflows

# .github/workflows/node-ci.yml
on:
  workflow_call:
    inputs:
      node-version: { required: false, type: string, default: '20' }
    secrets:
      NPM_TOKEN: { required: false }

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: ${{ inputs.node-version }} }
      - run: npm ci
      - run: npm test
# consumer repo
jobs:
  ci:
    uses: org/shared-workflows/.github/workflows/node-ci.yml@v1
    with:
      node-version: '22'

7. Common Pitfalls

Issue Fix
5min+ installs Add actions/cache@v4
Pipeline too long Use matrix builds, parallel jobs
Hardcoded secrets Use GitHub Environments
Duplicate deployments Use concurrency group
Workflow hard to maintain Split into reusable workflows
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

A production-grade pipeline has: matrix builds, caching, conditional deployment with environment gates, and reusable workflows. Treat your workflow file like code.