dev/notes
⌕

Spot a mistake? Highlight any text in a post and click Report — it goes straight to the author.

← all posts
intermediate · DevOps · August 17, 2026 · 2 min read

How to Set Up GitHub Actions for CI/CD

Set up GitHub Actions for continuous integration and deployment. Automate tests and deploys on every push with a reusable workflow.

CI/CD in one file

GitHub Actions runs a YAML workflow on your repo’s runners. A single file in .github/workflows/ can install dependencies, run tests, build, and deploy — all triggered by a push or pull request.

Step 1 — Create the workflow

.github/workflows/ci.yml:

name: CI

on:
  push:
    branches: [main]
  pull_request:

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

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

      - run: npm ci
      - run: npm run typecheck
      - run: npm test
      - run: npm run build

npm ci installs exactly what package-lock.json pins, which is deterministic in CI (unlike npm install).

Step 2 — Add caching and concurrency

Faster runs and no two deploys racing:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build:
    # ...steps above...
    - run: npm ci
    - run: npm run build

The cache: npm option on setup-node already restores node_modules between runs, and cancel-in-progress kills a superseded run the moment a newer push lands — you never pay twice for the same commit.

Step 3 — Test multiple versions with a matrix

If your library or CLI must work across Node versions, a matrix runs the same job for each combination:

strategy:
  matrix:
    node-version: [20, 22]

steps:
  - uses: actions/setup-node@v4
    with:
      node-version: ${{ matrix.node-version }}

You get one check per version in the PR, each with its own logs — the cheapest way to catch a version-only regression before users do.

Step 5 — Deploy to Cloudflare Pages

Add a deploy job that uses CF_API_TOKEN from repo secrets:

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          command: pages deploy dist --project-name=your-project

Step 4 — Skip CI when you don’t need it

Include [skip ci] in a commit message and GitHub Actions skips the run entirely — handy for pure documentation edits that don’t need a full test cycle. It’s a small habit that keeps the queue focused on code that actually changes behavior.

Step 6 — Add secrets

GitHub → Settings → Secrets and variables → Actions → New repository secret:

  • CF_API_TOKEN — a Cloudflare API token with Pages edit scope.

Secrets are encrypted at rest, masked in logs, and never exposed to forks’ pull requests.

Troubleshooting

  • npm ci fails on a missing lockfile — commit package-lock.json; CI must not generate it.
  • Workflow doesn’t run — YAML must live in .github/workflows/*.yml on the default branch, and the on: triggers must match.
  • Secrets empty in a PR from a fork — by design. Secrets are only available in workflows from your own branches.

Summary

Every push to main now typechecks, tests, builds, and deploys automatically. The workflow is the source of truth for “is this shippable?” — no manual steps.

The same pattern extends further: How to Build and Secure an MCP Server shows how to add a protected API route to a workflow, and Deploy Astro on Cloudflare Pages covers the Pages-side setup this deploys to. Keep the workflow’s Node version aligned with the project’s .nvmrc or engines field so local and CI builds use the same runtime.

Related posts

Comments

One comment per thread every 30 minutes · edits are unlimited.