GitHub Actions
GitHub Actions is a CI/CD platform built directly into GitHub. Workflows are YAML files in `.github/workflows/` that run automatically on triggers like pushes, PRs, schedules, or manual dispatch.
Workflow Anatomy
# .github/workflows/ci.yml
name: CI
# Triggers
on:
push:
branches: [main, develop]
paths-ignore: ['**.md', 'docs/**']
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
schedule:
- cron: '0 9 * * 1' # Every Monday at 9am UTC
workflow_dispatch: # Manual trigger from GitHub UI
inputs:
environment:
description: 'Target environment'
required: true
default: 'staging'
type: choice
options: [staging, production]
# Environment variables available to all jobs
env:
NODE_VERSION: '20'
REGISTRY: ghcr.io
jobs:
test:
name: Run Tests
runs-on: ubuntu-latest
# runs-on: [self-hosted, linux, x64] # Self-hosted runner
# Job-level environment
env:
DATABASE_URL: postgresql://localhost/test
# Services (Docker containers)
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:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test -- --coverage
env:
CI: true
- name: Upload coverage
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
retention-days: 7Secrets & Contexts
# Secrets are set in: Repo → Settings → Secrets and variables → Actions
# Access via ${{ secrets.SECRET_NAME }}
# Common contexts:
# ${{ github.sha }} - commit SHA
# ${{ github.ref }} - branch/tag ref (refs/heads/main)
# ${{ github.ref_name }} - branch name (main)
# ${{ github.actor }} - user 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: |
curl -X POST https://api.vercel.com/v1/integrations/deploy \
-H "Authorization: Bearer ${{ secrets.VERCEL_TOKEN }}" \
-d '{"target": "production"}'
env:
DEPLOY_ENV: production
- name: Notify Slack on failure
if: failure()
uses: slackapi/slack-github-action@v1.26.0
with:
payload: |
{
"text": "Build failed on ${{ github.ref_name }} by ${{ github.actor }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}Test Matrix
# Run tests across multiple Node versions and OSes in parallel
jobs:
test:
strategy:
fail-fast: false # Don't cancel other jobs on one failure
matrix:
node-version: [18, 20, 22]
os: [ubuntu-latest, windows-latest, macos-latest]
exclude:
- os: windows-latest
node-version: 18 # Skip Node 18 on Windows
include:
- os: ubuntu-latest
node-version: 20
coverage: true # Only upload coverage for this combo
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
- name: Upload coverage
if: matrix.coverage
uses: codecov/codecov-action@v4Deploy Workflow (Next.js to Vercel)
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
# Cache build output
- uses: actions/cache@v4
with:
path: |
.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }}-${{ hashFiles('**/*.ts', '**/*.tsx') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }}-
- run: npm run build
env:
NEXT_PUBLIC_API_URL: ${{ vars.NEXT_PUBLIC_API_URL }}
- name: Deploy to Vercel
run: |
npx vercel --prod \
--token ${{ secrets.VERCEL_TOKEN }} \
--scope ${{ vars.VERCEL_ORG_ID }}
env:
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
VERCEL_ORG_ID: ${{ vars.VERCEL_ORG_ID }}Reusable Workflows & Composite Actions
# Reusable workflow: .github/workflows/reusable-test.yml
on:
workflow_call:
inputs:
node-version:
required: false
type: string
default: '20'
secrets:
DATABASE_URL:
required: true
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 && npm test
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
---
# Caller workflow
jobs:
run-tests:
uses: ./.github/workflows/reusable-test.yml
with:
node-version: '22'
secrets:
DATABASE_URL: ${{ secrets.DATABASE_URL }}Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free