Understanding CI/CD Pipelines with GitHub Actions

A CI/CD pipeline turns “run the tests and deploy” from a manual checklist someone forgets half the time into something that happens automatically, consistently, on every single push. GitHub Actions made this accessible to any repo with zero external infrastructure to set up.

A Basic Test-and-Lint Workflow

name: CI

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

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npm run lint
      - run: npm test

Adding a Deployment Stage

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    needs: test          # only deploys if the test job succeeds
    steps:
      - uses: actions/checkout@v4

      - name: Deploy to production
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
        run: ./scripts/deploy.sh

Secrets: Never Hardcode Credentials

Repository secrets (Settings > Secrets and variables > Actions) are encrypted and injected as environment variables at runtime, never exposed in logs or visible in the workflow file itself — the only correct place for deploy keys, API tokens, and credentials in a pipeline.

Matrix Builds: Testing Multiple Configurations at Once

strategy:
  matrix:
    node-version: [18, 20, 22]
    os: [ubuntu-latest, windows-latest]
# Runs the full job 6 times — once per node-version/os combination

Once this is set up, “does it actually pass tests, on the versions we claim to support, before it merges” stops being a matter of trust and becomes something enforced automatically on every pull request.