Setting up a CI/CD pipeline from scratch with GitHub Actions: test, lint, build stages, deployment gates, environment secrets, and caching.
Setting up a CI/CD pipeline from scratch with GitHub Actions: test, lint, build stages, deployment gates, environment secrets, and caching.
BeforeMerge offers hundreds of code review rules, guides, and detection patterns to help your team ship better code.
This tutorial walks through building a production-ready CI/CD pipeline using GitHub Actions for a Node.js/Next.js project.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm lint
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm tsc --noEmit
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm test
build:
needs: [lint, typecheck, test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm build deploy-staging:
needs: [build]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- name: Deploy to Vercel
run: npx vercel --prod --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
deploy-production:
needs: [deploy-staging]
runs-on: ubuntu-latest
environment:
name: production
url: https://yourapp.com
steps:
- uses: actions/checkout@v4
- name: Deploy to production
run: npx vercel --prod --token=${{ secrets.VERCEL_TOKEN }}Set secrets in GitHub: Settings > Secrets and variables > Actions.
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}Rules:
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"For more control:
- uses: actions/cache@v4
with:
path: ~/.pnpm-store
key: pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: pnpm-Configure in GitHub Settings > Branches:
Jobs without needs run in parallel. Structure for speed:
lint ──────┐
typecheck ──┼── build ── deploy-staging ── deploy-production
test ──────┘