GitHub
06 / 10

GitHub Actions & CI/CD

GitHub Actions & CI/CD

GitHub Actions automates workflows triggered by GitHub events. Workflows are YAML files in .github/workflows/. Each workflow has jobs, jobs have steps.

Workflow Structure

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
    paths-ignore: ['**.md', 'docs/**']
  pull_request:
    branches: [main]
    types: [opened, synchronize, reopened]
  schedule:
    - cron: '0 9 * * 1'      # Every Monday 9am UTC
  workflow_dispatch:           # Manual trigger
    inputs:
      environment:
        description: 'Target environment'
        type: choice
        options: [staging, production]
        default: staging

env:
  NODE_VERSION: '20'

jobs:
  test:
    name: Run Tests
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: postgres
        ports: ['5432:5432']
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - run: npm ci

      - run: npm run lint

      - run: npm test -- --coverage
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: coverage-report
          path: coverage/
          retention-days: 7

Secrets & Contexts

# Secrets: Settings → Secrets and variables → Actions
# Access: ${{ secrets.SECRET_NAME }}

# Useful built-in contexts
# ${{ github.sha }}          — commit SHA
# ${{ github.ref_name }}     — branch name (main)
# ${{ github.actor }}        — who triggered the workflow
# ${{ github.repository }}   — owner/repo
# ${{ github.event_name }}   — push, pull_request, etc.
# ${{ runner.os }}           — Linux, Windows, macOS
# ${{ job.status }}          — success, failure, cancelled

steps:
  - name: Deploy to production
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    run: npx vercel --prod --token ${{ secrets.VERCEL_TOKEN }}

  - name: Notify on failure
    if: failure()
    uses: slackapi/slack-github-action@v1
    with:
      payload: |
        {"text": "Build failed on ${{ github.ref_name }} by ${{ github.actor }}"}
    env:
      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

Matrix Builds

jobs:
  test:
    strategy:
      fail-fast: false
      matrix:
        node: [18, 20, 22]
        os: [ubuntu-latest, windows-latest]
        exclude:
          - os: windows-latest
            node: 18
        include:
          - os: ubuntu-latest
            node: 20
            coverage: true

    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm ci && npm test
      - if: matrix.coverage
        run: npm run coverage:upload

Caching

- uses: actions/cache@v4
  with:
    path: |
      ~/.npm
      .next/cache
    key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}-${{ hashFiles('**/*.ts','**/*.tsx') }}
    restore-keys: |
      ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}-
      ${{ runner.os }}-node-

Reusable Workflows & Composite Actions

# .github/workflows/reusable-deploy.yml
on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
    secrets:
      VERCEL_TOKEN:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v4
      - run: npx vercel --prod --token ${{ secrets.VERCEL_TOKEN }}

# Call from another workflow:
# jobs:
#   deploy:
#     uses: ./.github/workflows/reusable-deploy.yml
#     with:
#       environment: production
#     secrets:
#       VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}

Releases & Tags

# .github/workflows/release.yml
on:
  push:
    tags: ['v*']

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build
      - uses: softprops/action-gh-release@v2
        with:
          files: dist/*.zip
          generate_release_notes: true

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free