Bỏ qua

Phase 7: CI/CD & Deployment

Mục tiêu

Setup automated CI/CD pipeline với GitHub Actions cho multi-environment deployment.

Thời gian ước tính: 2 giờ


Environment Strategy

Environments

Environment Branch Supabase Project Cloudflare Worker
Development - Local (supabase start) wrangler dev
Staging staging taskflow-staging taskflow-staging
Production main taskflow-prod taskflow

Branch Strategy

main (production)
staging (testing)
feature/* (development)

Step 1: GitHub Repository Setup

1.1 Create GitHub Environments

  1. Go to Repository → Settings → Environments
  2. Create staging environment:
  3. No protection rules (auto-deploy)
  4. Create production environment:
  5. Required reviewers: 1+
  6. Deployment branches: main only

1.2 Add Secrets

For each environment, add:

# Supabase
SUPABASE_PROJECT_REF
SUPABASE_ACCESS_TOKEN
SUPABASE_DB_PASSWORD

# Cloudflare
CLOUDFLARE_API_TOKEN
CLOUDFLARE_ACCOUNT_ID

# App secrets
SUPABASE_SERVICE_ROLE_KEY
SENDGRID_API_KEY

1.3 Add Variables

# Staging
SUPABASE_URL=https://staging.supabase.co
SUPABASE_ANON_KEY=eyJ...

# Production
SUPABASE_URL=https://production.supabase.co
SUPABASE_ANON_KEY=eyJ...

Step 2: CI Workflow

# .github/workflows/ci.yml
name: CI

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

jobs:
  lint-and-type-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'

      - name: Install pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 8

      - name: Install dependencies
        run: pnpm install

      - name: Lint
        run: pnpm lint

      - name: Type check
        run: pnpm tsc --noEmit

  build:
    runs-on: ubuntu-latest
    needs: lint-and-type-check
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'

      - name: Install pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 8

      - name: Install dependencies
        run: pnpm install

      - name: Build
        run: pnpm build
        env:
          NEXT_PUBLIC_SUPABASE_URL: ${{ vars.SUPABASE_URL }}
          NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ vars.SUPABASE_ANON_KEY }}

      - name: Build for Cloudflare
        run: pnpm build:worker

  test:
    runs-on: ubuntu-latest
    needs: lint-and-type-check
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'

      - name: Install pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 8

      - name: Install dependencies
        run: pnpm install

      - name: Run tests
        run: pnpm test

Step 3: Staging Deployment

# .github/workflows/deploy-staging.yml
name: Deploy Staging

on:
  push:
    branches: [staging]

jobs:
  deploy-database:
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/checkout@v4

      - name: Setup Supabase CLI
        uses: supabase/setup-cli@v1
        with:
          version: latest

      - name: Link Supabase project
        run: supabase link --project-ref ${{ vars.SUPABASE_PROJECT_REF }}
        env:
          SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}

      - name: Run migrations
        run: supabase db push
        env:
          SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}

      - name: Verify migrations
        run: supabase migration list
        env:
          SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}

  deploy-app:
    runs-on: ubuntu-latest
    needs: deploy-database
    environment: staging
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'

      - name: Install pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 8

      - name: Install dependencies
        run: pnpm install

      - name: Build for Cloudflare
        run: pnpm build:worker
        env:
          NEXT_PUBLIC_SUPABASE_URL: ${{ vars.SUPABASE_URL }}
          NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ vars.SUPABASE_ANON_KEY }}

      - name: Deploy to Cloudflare (Staging)
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: deploy --env staging

      - name: Set Cloudflare secrets
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: secret put SUPABASE_SERVICE_KEY --env staging
          secrets: |
            SUPABASE_SERVICE_KEY=${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}

  deploy-workers:
    runs-on: ubuntu-latest
    needs: deploy-database
    environment: staging
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Deploy Queue Consumer
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          workingDirectory: workers/notification-consumer
          command: deploy --env staging

      - name: Deploy Cron Worker
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          workingDirectory: workers/cron
          command: deploy --env staging

Step 4: Production Deployment

# .github/workflows/deploy-production.yml
name: Deploy Production

on:
  push:
    branches: [main]

jobs:
  deploy-database:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Setup Supabase CLI
        uses: supabase/setup-cli@v1
        with:
          version: latest

      - name: Link Supabase project
        run: supabase link --project-ref ${{ vars.SUPABASE_PROJECT_REF }}
        env:
          SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}

      - name: Backup before migration
        run: |
          echo "Creating backup checkpoint..."
          # For Pro plans, this triggers a backup
          # supabase db dump -f backup-$(date +%Y%m%d%H%M%S).sql
        env:
          SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}

      - name: Run migrations
        run: supabase db push
        env:
          SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}

      - name: Verify migrations
        run: supabase migration list
        env:
          SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}

  deploy-app:
    runs-on: ubuntu-latest
    needs: deploy-database
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'

      - name: Install pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 8

      - name: Install dependencies
        run: pnpm install

      - name: Build for Cloudflare
        run: pnpm build:worker
        env:
          NEXT_PUBLIC_SUPABASE_URL: ${{ vars.SUPABASE_URL }}
          NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ vars.SUPABASE_ANON_KEY }}

      - name: Deploy to Cloudflare (Production)
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: deploy

  deploy-workers:
    runs-on: ubuntu-latest
    needs: deploy-database
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Deploy Queue Consumer
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          workingDirectory: workers/notification-consumer
          command: deploy

      - name: Deploy Cron Worker
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          workingDirectory: workers/cron
          command: deploy

  notify:
    runs-on: ubuntu-latest
    needs: [deploy-app, deploy-workers]
    if: always()
    steps:
      - name: Notify Slack on success
        if: ${{ needs.deploy-app.result == 'success' }}
        run: |
          curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} \
            -H 'Content-Type: application/json' \
            -d '{"text": "TaskFlow deployed to production successfully!"}'

      - name: Notify Slack on failure
        if: ${{ needs.deploy-app.result == 'failure' }}
        run: |
          curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} \
            -H 'Content-Type: application/json' \
            -d '{"text": "TaskFlow production deployment failed!"}'

Step 5: Wrangler Configuration

Main App (multi-environment)

# wrangler.toml
name = "taskflow"
main = ".open-next/worker.js"
compatibility_date = "2024-01-01"
compatibility_flags = ["nodejs_compat"]

[site]
bucket = ".open-next/assets"

# Staging environment
[env.staging]
name = "taskflow-staging"
vars = { APP_ENV = "staging" }

[[env.staging.queues.producers]]
queue = "taskflow-notifications-staging"
binding = "NOTIFICATION_QUEUE"

# Production (default)
[vars]
APP_ENV = "production"

[[queues.producers]]
queue = "taskflow-notifications"
binding = "NOTIFICATION_QUEUE"

Workers (multi-environment)

# workers/notification-consumer/wrangler.toml
name = "taskflow-notification-consumer"
main = "src/index.ts"

[env.staging]
name = "taskflow-notification-consumer-staging"

[[env.staging.queues.consumers]]
queue = "taskflow-notifications-staging"
max_batch_size = 10
max_retries = 3

[[queues.consumers]]
queue = "taskflow-notifications"
max_batch_size = 10
max_retries = 3

Step 6: Local Development

# Start Supabase locally
supabase start

# Run Next.js dev server
pnpm dev

# In another terminal, run worker locally
pnpm preview

Rollback Procedures

Database Rollback

-- Option 1: Create reverse migration
supabase migration new rollback_xxx

-- In the migration file, write reverse operations

-- Option 2: Point-in-time recovery (Pro plan)
-- Dashboard → Database → Backups → Restore

Cloudflare Rollback

# Rollback to previous version
wrangler rollback

# Or deploy specific version
wrangler deploy --version-id <version-id>

GitHub Actions Rollback

# Revert commit and push
git revert <commit-sha>
git push origin main

Verification Checklist

  • [ ] CI workflow passing (lint, type-check, build, test)
  • [ ] Staging deployment working
  • [ ] Production deployment with approval
  • [ ] Database migrations running in CI
  • [ ] Cloudflare Workers deploying
  • [ ] Secrets properly configured
  • [ ] Rollback procedure tested

Final Project Checklist

Features

  • [ ] User registration and login
  • [ ] Organization/workspace management
  • [ ] Project CRUD
  • [ ] Kanban board with drag-and-drop
  • [ ] Task management with comments
  • [ ] File attachments
  • [ ] Real-time updates (optional)
  • [ ] Email notifications
  • [ ] Activity logging

Technical

  • [ ] Database schema with RLS
  • [ ] Type-safe Supabase client
  • [ ] Queue processing (pgmq or Cloudflare)
  • [ ] Scheduled jobs (pg_cron + Cron Triggers)
  • [ ] CI/CD pipeline
  • [ ] Multi-environment deployment

Quality

  • [ ] Code passes linting
  • [ ] TypeScript strict mode
  • [ ] Error handling
  • [ ] Loading states
  • [ ] Responsive design

Congratulations!

Bạn đã hoàn thành TaskFlow project với đầy đủ các tính năng của Light Development Stack:

  • Next.js on Cloudflare Workers
  • Supabase PostgreSQL, Auth, Storage, Realtime
  • Cloudflare Workers, Queues, Cron, KV
  • CI/CD với GitHub Actions

Next Steps

  1. Thêm more features (notifications UI, search, etc.)
  2. Optimize performance (caching, pagination)
  3. Add monitoring and analytics
  4. Scale to production

Resources