GitHub Actions — CI/CD

Deploy TeachRepo automatically on every push to main, run type checks and E2E tests on pull requests, and apply database migrations safely.

Complete deploy workflow

Save as .github/workflows/deploy.yml in your fork root:

name: CI / Deploy

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
  WORKING_DIR: startup-91-lean-launch-plan-repo-to-course-git-native-paywall/apps/web

jobs:
  # ── 1. Type-check ─────────────────────────────────────────────
  typecheck:
    name: Type-check
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ${{ env.WORKING_DIR }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
          cache-dependency-path: ${{ env.WORKING_DIR }}/package-lock.json
      - run: npm ci
      - run: npm run type-check

  # ── 2. Deploy to Vercel ────────────────────────────────────────
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    needs: typecheck
    defaults:
      run:
        working-directory: ${{ env.WORKING_DIR }}
    outputs:
      deployment-url: ${{ steps.deploy.outputs.url }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
          cache-dependency-path: ${{ env.WORKING_DIR }}/package-lock.json
      - run: npm ci
      - run: npm install -g vercel@latest
      - name: Pull Vercel environment
        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
      - name: Build
        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
      - name: Deploy to production
        id: deploy
        if: github.ref == 'refs/heads/main'
        run: |
          URL=$(vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }})
          echo "url=$URL" >> $GITHUB_OUTPUT
          echo "Deployed: $URL"
      - name: Deploy preview (PR)
        if: github.event_name == 'pull_request'
        run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}

  # ── 3. E2E tests ─────────────────────────────────────────────
  e2e:
    name: E2E tests
    runs-on: ubuntu-latest
    needs: deploy
    if: always() && needs.deploy.result == 'success'
    defaults:
      run:
        working-directory: ${{ env.WORKING_DIR }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
          cache-dependency-path: ${{ env.WORKING_DIR }}/package-lock.json
      - run: npm ci
      - name: Install Playwright browsers
        run: npx playwright install chromium --with-deps
      - name: Run E2E tests
        env:
          BASE_URL: ${{ needs.deploy.outputs.deployment-url }}
          TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
          TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
        run: npx playwright test --reporter=dot
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report
          path: ${{ env.WORKING_DIR }}/playwright-report/
          retention-days: 7

Required GitHub secrets

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

SecretRequired?Where to get it
VERCEL_TOKENYesVercel → Account Settings → Tokens
VERCEL_ORG_IDYesVercel dashboard URL or .vercel/project.json
VERCEL_PROJECT_IDYes.vercel/project.json after vercel link
TEST_USER_EMAILFor E2EYour test account email
TEST_USER_PASSWORDFor E2EYour test account password
SLACK_WEBHOOK_URLOptionalSlack → Incoming Webhooks app
ℹ️Vercel env vars (Supabase keys, Stripe keys, etc.) are pulled automatically by vercel pull in the workflow — you do not need to duplicate them as GitHub secrets.

Database migrations

Schema changes are plain SQL files in supabase/migrations/. Apply them from CI using the Supabase CLI:

# Add to your deploy job, before the Vercel build step:

- name: Apply DB migrations
  env:
    SUPABASE_PROJECT_ID: ${{ secrets.SUPABASE_PROJECT_ID }}
    SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
  run: |
    # Install Supabase CLI
    npm install -g supabase

    # Link to project (non-interactive)
    supabase link --project-ref $SUPABASE_PROJECT_ID

    # Push any pending migrations
    supabase db push --linked

Add SUPABASE_PROJECT_ID (the short ref, e.g. zkwyfjrg) and SUPABASE_ACCESS_TOKEN (from supabase.com/account/tokens) as GitHub secrets.

⚠️Always commit migration files before merging a PR that requires them. The migration apply step runs before the build so the app has the schema it expects on deploy.

Auto-publish courses on push

Add this workflow to your course repository (not the app repo) to auto-publish whenever you push to main:

# .github/workflows/publish-course.yml
name: Publish course to TeachRepo

on:
  push:
    branches: [main]
  workflow_dispatch:
    inputs:
      label:
        description: Version label (e.g. v1.2)
        required: false

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0   # needed for version history

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install TeachRepo CLI
        run: npm install -g @teachrepo/cli

      - name: Push course
        env:
          TEACHREPO_TOKEN: ${{ secrets.TEACHREPO_TOKEN }}
          TEACHREPO_URL: ${{ secrets.TEACHREPO_URL }}
        run: |
          teachrepo push \
            --url "$TEACHREPO_URL" \
            --token "$TEACHREPO_TOKEN" \
            ${{ github.event.inputs.label && format('--label {0}', github.event.inputs.label) || '' }}

Get your TEACHREPO_TOKEN from /dashboard/settings → API tokens.

Slack deploy notifications (optional)

# Add as a final step in the deploy job:

- name: Notify Slack on deploy
  if: github.ref == 'refs/heads/main' && success()
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
  run: |
    curl -s -X POST "$SLACK_WEBHOOK_URL" \
      -H 'Content-Type: application/json' \
      -d '{
        "text": "✅ TeachRepo deployed to production",
        "attachments": [{
          "color": "good",
          "fields": [
            {"title": "Commit", "value": "${{ github.sha }}", "short": true},
            {"title": "Author", "value": "${{ github.actor }}", "short": true},
            {"title": "URL", "value": "${{ needs.deploy.outputs.deployment-url }}", "short": false}
          ]
        }]
      }'
← Self-Hosting GuideCLI Reference →Pricing & Billing →