Chapter 40: CI/CD with GitHub Actions
Continuous Integration verifies every change. Continuous Deployment ships verified changes to production. This chapter sets up a complete pipeline.
Pipeline Architecture​
Push/PR → Lint → Typecheck → Unit Tests → Build → E2E Tests → Deploy
↓
(main branch only)
Complete Workflow​
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint & Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm typecheck
test:
name: Unit & Integration Tests
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm test:run --coverage
- uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage/
build:
name: Build
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm build
- uses: actions/upload-artifact@v4
with:
name: build
path: dist/
retention-days: 7
e2e:
name: E2E Tests
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm playwright install --with-deps chromium
- run: pnpm test:e2e
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
deploy:
name: Deploy
runs-on: ubuntu-latest
needs: [test, build, e2e]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
environment: production
steps:
- uses: actions/download-artifact@v4
with:
name: build
path: dist/
# Deploy to your hosting provider
# Example: Deploy to Cloudflare Pages
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist/ --project-name=taskforge
security:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm audit --audit-level=high
continue-on-error: true # Don't block on audit warnings
Key Practices​
pnpm install --frozen-lockfile​
In CI, always use --frozen-lockfile (equivalent to npm ci). This:
- Installs exact versions from the lockfile
- Fails if the lockfile is out of date
- Ensures reproducible builds
Concurrency Controls​
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
If a new push arrives while CI is running on the same branch, the old run is cancelled. No wasted compute.
Caching​
actions/setup-node with cache: "pnpm" caches the pnpm store between runs. First run installs from network; subsequent runs are near-instant.
Environment Protection​
deploy:
environment: production # Requires approval for production deploys
Configure environment protection rules in GitHub Settings → Environments → production → Required reviewers.
Summary​
- ✅ Pipeline stages: lint → typecheck → test → build → e2e → deploy
- ✅ Concurrency controls cancel superseded runs
- ✅ Frozen lockfile ensures reproducible installs
- ✅ Artifact upload preserves build outputs and failure reports
- ✅ Conditional deployment — only main branch, only on push
- ✅ Security audit runs alongside the pipeline
- ✅ Environment protection for production deployments