--- name: ci-cd-pipeline-builder description: Expert guide for building CI/CD pipelines with GitHub Actions, Vercel, and other platforms. Use when automating builds, tests, deployments, or release workflows. --- # CI/CD Pipeline Builder Skill ## Overview This skill helps you build robust CI/CD pipelines for automated testing, building, and deployment. Covers GitHub Actions, Vercel integration, testing strategies, deployment patterns, and security best practices. ## CI/CD Philosophy ### Pipeline Principles 1. **Fast feedback**: Fail early, inform quickly 2. **Reproducible**: Same inputs = same outputs 3. **Secure**: Secrets protected, dependencies verified 4. **Observable**: Clear logs, status visibility ### Pipeline Stages ``` Trigger → Lint → Test → Build → Deploy → Verify ``` ## GitHub Actions Fundamentals ### Basic Workflow Structure ```yaml # .github/workflows/ci.yml name: CI on: push: branches: [main, develop] pull_request: branches: [main] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: lint: 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 test: runs-on: ubuntu-latest needs: lint steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - run: npm ci - run: npm test build: runs-on: ubuntu-latest needs: [lint, test] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - run: npm ci - run: npm run build ``` ### Complete Next.js CI/CD Pipeline ```yaml # .github/workflows/ci-cd.yml name: CI/CD Pipeline on: push: branches: [main] pull_request: branches: [main] env: NODE_VERSION: '20' VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} jobs: # =================== # Quality Checks # =================== quality: name: Code Quality runs-on: ubuntu-latest steps: - name: Checkout 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: Type check run: npm run typecheck - name: Lint run: npm run lint - name: Format check run: npm run format:check # =================== # Unit & Integration Tests # =================== test: name: Tests runs-on: ubuntu-latest needs: quality steps: - name: Checkout 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 tests run: npm run test:ci - name: Upload coverage uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} files: ./coverage/lcov.info fail_ci_if_error: false # =================== # Build # =================== build: name: Build runs-on: ubuntu-latest needs: [quality, test] steps: - name: Checkout 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: Build run: npm run build env: NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }} - name: Upload build artifact uses: actions/upload-artifact@v4 with: name: build path: .next retention-days: 1 # =================== # E2E Tests # =================== e2e: name: E2E Tests runs-on: ubuntu-latest needs: build steps: - name: Checkout 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: Install Playwright browsers run: npx playwright install --with-deps chromium - name: Download build uses: actions/download-artifact@v4 with: name: build path: .next - name: Run E2E tests run: npm run test:e2e env: NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }} - name: Upload test results if: failure() uses: actions/upload-artifact@v4 with: name: playwright-report path: playwright-report retention-days: 7 # =================== # Deploy Preview (PRs) # =================== deploy-preview: name: Deploy Preview runs-on: ubuntu-latest needs: [build, e2e] if: github.event_name == 'pull_request' environment: name: preview url: ${{ steps.deploy.outputs.url }} steps: - name: Checkout uses: actions/checkout@v4 - name: Install Vercel CLI run: npm install -g vercel - name: Pull Vercel Environment run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} - name: Build Project run: vercel build --token=${{ secrets.VERCEL_TOKEN }} - name: Deploy to Vercel id: deploy run: | url=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}) echo "url=$url" >> $GITHUB_OUTPUT - name: Comment PR with URL uses: actions/github-script@v7 with: script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: `🚀 Preview deployed to: ${{ steps.deploy.outputs.url }}` }) # =================== # Deploy Production # =================== deploy-production: name: Deploy Production runs-on: ubuntu-latest needs: [build, e2e] if: github.ref == 'refs/heads/main' && github.event_name == 'push' environment: name: production url: https://myapp.com steps: - name: Checkout uses: actions/checkout@v4 - name: Install Vercel CLI run: npm install -g vercel - name: Pull Vercel Environment run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} - name: Build Project run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} - name: Deploy to Vercel run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} ``` ## Testing Strategies ### Parallel Test Execution ```yaml # .github/workflows/test-parallel.yml name: Parallel Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: shard: [1, 2, 3, 4] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - run: npm ci - name: Run tests (shard ${{ matrix.shard }}/4) run: npm run test -- --shard=${{ matrix.shard }}/4 merge-results: needs: test runs-on: ubuntu-latest steps: - name: Report results run: echo "All test shards passed!" ``` ### Matrix Testing ```yaml # .github/workflows/matrix-test.yml name: Matrix Tests on: [push, pull_request] jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] node: [18, 20, 22] exclude: - os: windows-latest node: 18 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} cache: 'npm' - run: npm ci - run: npm test ``` ## Database Integration ### Supabase in CI ```yaml # .github/workflows/test-with-db.yml name: Tests with Database on: [push, pull_request] jobs: test: runs-on: ubuntu-latest services: postgres: image: postgres:15 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: test_db ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Setup Supabase CLI uses: supabase/setup-cli@v1 - name: Start Supabase run: supabase start - name: Run migrations run: supabase db reset env: SUPABASE_DB_URL: postgresql://postgres:postgres@localhost:54322/postgres - name: Run tests run: npm run test:integration env: DATABASE_URL: postgresql://postgres:postgres@localhost:54322/postgres SUPABASE_URL: http://localhost:54321 SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} ``` ## Release Automation ### Automated Releases ```yaml # .github/workflows/release.yml name: Release on: push: branches: [main] jobs: release: runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Create Release uses: google-github-actions/release-please-action@v4 with: release-type: node package-name: my-app - name: Publish to npm if: ${{ steps.release.outputs.release_created }} run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ``` ### Changelog Generation ```yaml # .github/workflows/changelog.yml name: Changelog on: release: types: [published] jobs: update-changelog: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: main - name: Update CHANGELOG uses: stefanzweifel/changelog-updater-action@v1 with: latest-version: ${{ github.event.release.tag_name }} release-notes: ${{ github.event.release.body }} - name: Commit changes uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: "docs: update CHANGELOG for ${{ github.event.release.tag_name }}" file_pattern: CHANGELOG.md ``` ## Security Scanning ### Dependency & Code Scanning ```yaml # .github/workflows/security.yml name: Security on: push: branches: [main] pull_request: branches: [main] schedule: - cron: '0 0 * * 0' # Weekly jobs: dependency-audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' - run: npm audit --audit-level=high codeql: runs-on: ubuntu-latest permissions: security-events: write steps: - uses: actions/checkout@v4 - uses: github/codeql-action/init@v3 with: languages: javascript - uses: github/codeql-action/analyze@v3 secrets-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: gitleaks/gitleaks-action@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` ## Caching Strategies ### Effective Caching ```yaml # .github/workflows/cached-build.yml name: Cached Build on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' # Cache Next.js build - name: Cache Next.js uses: actions/cache@v4 with: path: | .next/cache key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }} restore-keys: | ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}- # Cache Playwright browsers - name: Cache Playwright uses: actions/cache@v4 with: path: ~/.cache/ms-playwright key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }} - run: npm ci - run: npm run build ``` ## Environment Management ### Environment-Based Deployments ```yaml # .github/workflows/deploy.yml name: Deploy on: push: branches: - main - develop jobs: deploy: runs-on: ubuntu-latest environment: name: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }} url: ${{ github.ref == 'refs/heads/main' && 'https://myapp.com' || 'https://staging.myapp.com' }} steps: - uses: actions/checkout@v4 - name: Deploy to ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }} run: | echo "Deploying to ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}" env: API_URL: ${{ vars.API_URL }} API_KEY: ${{ secrets.API_KEY }} ``` ## Workflow Optimization ### Conditional Jobs ```yaml # .github/workflows/smart-ci.yml name: Smart CI on: push: branches: [main] pull_request: branches: [main] jobs: changes: runs-on: ubuntu-latest outputs: frontend: ${{ steps.filter.outputs.frontend }} backend: ${{ steps.filter.outputs.backend }} docs: ${{ steps.filter.outputs.docs }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3 id: filter with: filters: | frontend: - 'src/app/**' - 'src/components/**' backend: - 'src/api/**' - 'src/lib/**' docs: - 'docs/**' - '*.md' test-frontend: needs: changes if: ${{ needs.changes.outputs.frontend == 'true' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci - run: npm run test:frontend test-backend: needs: changes if: ${{ needs.changes.outputs.backend == 'true' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci - run: npm run test:backend deploy-docs: needs: changes if: ${{ needs.changes.outputs.docs == 'true' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm run build:docs ``` ## Notification & Reporting ### Slack Notifications ```yaml # .github/workflows/notify.yml name: CI with Notifications on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci - run: npm run build notify: needs: build runs-on: ubuntu-latest if: always() steps: - name: Notify Slack uses: slackapi/slack-github-action@v1 with: payload: | { "text": "${{ needs.build.result == 'success' && '✅' || '❌' }} Build ${{ needs.build.result }}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "*${{ github.repository }}* - ${{ github.ref_name }}\nCommit: ${{ github.sha }}\nBy: ${{ github.actor }}" } } ] } env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK ``` ## Pipeline Checklist ### Essential Components - [ ] Lint and format checks - [ ] Type checking (TypeScript) - [ ] Unit and integration tests - [ ] Build verification - [ ] Security scanning ### Deployment - [ ] Preview deployments for PRs - [ ] Production deployment on main - [ ] Environment-specific configs - [ ] Rollback capability ### Performance - [ ] Effective caching strategy - [ ] Parallel job execution - [ ] Conditional job execution - [ ] Build artifact reuse ### Security - [ ] Secrets properly managed - [ ] Dependency auditing - [ ] Code scanning enabled - [ ] Protected environments ## When to Use This Skill Invoke this skill when: - Setting up CI/CD for a new project - Optimizing slow pipelines - Adding deployment automation - Implementing release management - Setting up security scanning - Configuring environment-based deployments - Creating preview environments