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.