CI/CD & DevOps
GitHub Actions from Scratch
Build a complete CI/CD pipeline with GitHub Actions — from first workflow to production deployment.
What GitHub Actions Is
GitHub Actions is CI/CD built directly into GitHub. When you push code, open a pull request, or create a release, GitHub can automatically run any workflow you define.
Workflows are YAML files stored in .github/workflows/ in your repository.
Core Concepts
Workflow — A YAML file that defines an automated process. You can have multiple workflows (CI tests, deployment, dependency updates, etc.).
Event — What triggers the workflow. Common events: push, pull_request, schedule (cron), workflow_dispatch (manual trigger).
Job — A set of steps that runs on a virtual machine (runner). Multiple jobs can run in parallel.
Step — An individual task within a job. Can be a shell command or a reusable action from the Marketplace.
Runner — The virtual machine that executes the job. GitHub provides Ubuntu, Windows, and macOS runners.
Your First Workflow
# .github/workflows/ci.yml
name: CI
# Trigger on push to main and all pull requests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
# Step 1: Check out the repository code
- uses: actions/checkout@v4
# Step 2: Set up Node.js
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # Cache node_modules for faster subsequent runs
# Step 3: Install dependencies
- run: npm ci # ci is faster and more reliable than npm install
# Step 4: Run quality checks
- run: npm run lint
- run: npm run typecheck
# Step 5: Run tests
- run: npm test
# Step 6: Build
- run: npm run buildUsing Secrets
Store sensitive values in GitHub repository settings (Settings → Secrets → Actions) and reference them in workflows:
steps:
- name: Deploy to Vercel
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
run: vercel --prod --token $VERCEL_TOKENSecrets are never visible in logs or to forks.
Matrix Builds
Test against multiple versions in parallel:
jobs:
test:
strategy:
matrix:
node-version: [18, 20, 22]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci && npm testBranch Protection Rules
Require CI to pass before merging. In your repository: Settings → Branches → Add branch protection rule. Enable "Require status checks to pass before merging" and select your CI workflow.
Now it is impossible to merge code that fails tests.
Complete Production Workflow
name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
ci:
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 run typecheck
- run: npm test -- --coverage
- run: npm run build
deploy-staging:
needs: ci
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}Key Takeaways
- GitHub Actions workflows are YAML files in
.github/workflows/that trigger on GitHub events - Jobs run in parallel by default; use
needsto create sequential dependencies - Store secrets in repository settings and access them with
${{ secrets.NAME }} - Use
npm ciinstead ofnpm installin CI — it is faster and uses the lockfile exactly - Protect your main branch to require CI to pass before any code can be merged
Example
# .github/workflows/ci.yml — production-ready workflow
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
quality:
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 run typecheck
- run: npm test -- --coverage --reporter=verbose
- run: npm run build
security:
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 audit --audit-level=high