404 Not Found

404 Not Found


nginx

CI/CD with GitHub Actions

CI/CD is like "autopilot" for development teams—every commit automatically goes through a quality gate, and only code that passes is deployed to the production environment.

1. What You'll Learn


2. A True Story of a Technical Manager

(1) Pain Point: Manual deployment—still staring at the terminal at 3 a.m.

Bob is the Technical Lead of the TaskFlow team, which consists of five members who maintain a SaaS platform serving over 10,000 users. The release process every Friday goes like this:

"Alice merges the PR → Notifies Bob → Bob runs tests locally → Manually clicks vercel deploy --prod after the tests pass → Stares at the terminal for 10 minutes → Confirms the deployment was successful → Sends a message to the team"

Last week, Bob accidentally deployed an outdated version because he forgot to pull the latest code before npm run build. Five minutes later, users were unable to log in, and it took another 15 minutes to roll back the changes. Customer service received over 100 complaints.

Pain Points Impact
Manual Deployment High rate of human error (30% of releases have issues)
No automated access control testing Flawed code can still make it to production
Environment Mismatch Runs locally, but throws an error in production
No PR Preview You can't see the results before merging

(2) GitHub Actions Solution

Bob designed three automated assembly lines:

YAML
# Every time PR → Automatic lint + test + build
# Merge into main → Automated Build + Deploy to Vercel
# Every day at dawn → Automated Build Docker Image

(3) Revenue

Dimension Manual Deployment CI/CD Automation
Time to publish 30 minutes 8 minutes
Human error 30% < 1%
PR Review Efficiency Code Only Code + Preview URL
Deployment Frequency Once a week 5 times a day

3. GitHub Actions Basics

Actions is GitHub's built-in CI/CD platform that uses YAML files to define workflows.

100%
graph LR
    A[git push] --> B[GitHub Actions]
    B --> C[Events Trigger]
    C --> D[Jobs Parallel/Serial]
    D --> E[Steps Steps]
    E --> F[Actions Market Components]
    
    style B fill:#cce5ff
    style D fill:#d4edda
Concept Description Example
Workflow One YAML file = One automation process .github/workflows/test.yml
Event Event that triggers the workflow push, pull_request, schedule
Job A set of steps (which may be parallel or dependent) linttestdeploy
Step A single command or Action npm run lint, actions/checkout
Runner Execution Environment ubuntu-latest, windows-latest

(1) Workflow File Structure

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

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

env:
  NODE_VERSION: '20'
  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
      - run: npm ci
      - run: npm run lint

  test:
    needs: [lint]
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: ['18', '20', '22']
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test
      - run: npm run test:coverage
      - uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

  build:
    needs: [test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: .next/

4. test.yml: Parallel Matrix Testing

100%
graph TB
    A[Git Push / PR] --> B[lint]
    B --> C[test 18.x]
    B --> D[test 20.x]
    B --> E[test 22.x]
    C --> F[build]
    D --> F
    E --> F
    F --> G[Upload Build Artifacts]
    
    style A fill:#cce5ff
    style C fill:#d4edda
    style D fill:#d4edda
    style E fill:#d4edda

(1) Complete test.yml

YAML
# .github/workflows/test.yml
name: Test Suite

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

jobs:
  lint:
    name: Lint Check
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          
      - name: Install dependencies
        run: npm ci
          
      - name: TypeScript type check
        run: npx tsc --noEmit
        
      - name: ESLint check
        run: npm run lint
        
      - name: Prettier check
        run: npx prettier --check "src/**/*.{ts,tsx}"

  unit-and-integration:
    name: Unit & Integration Tests
    needs: [lint]
    runs-on: ubuntu-latest
    timeout-minutes: 15
    
    strategy:
      matrix:
        node-version: ['18', '20', '22']
      fail-fast: false
    
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: testdb
        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: ${{ matrix.node-version }}
          cache: 'npm'
          
      - name: Install dependencies
        run: npm ci
          
      - name: Generate Prisma client
        run: npx prisma generate
          
      - name: Run database migrations
        run: npx prisma migrate deploy
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb
          
      - name: Run unit tests
        run: npm run test
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb
          
      - name: Run integration tests
        run: npm run test:integration
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb
          
      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          flags: unittests
          name: codecov-node-${{ matrix.node-version }}

  e2e:
    name: E2E Tests
    needs: [lint]
    runs-on: ubuntu-latest
    timeout-minutes: 20
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          
      - name: Install dependencies
        run: npm ci
          
      - name: Install Playwright browsers
        run: npx playwright install --with-deps chromium
        
      - name: Run E2E tests
        run: npx playwright test --project=chromium
        env:
          TEST_DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
          
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 7

  build:
    name: Build Check
    needs: [unit-and-integration]
    runs-on: ubuntu-latest
    timeout-minutes: 10
    
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run build
      
      - uses: actions/upload-artifact@v4
        with:
          name: next-build
          path: .next/
          retention-days: 1

▶ Example: Matrix Parallel Test Results

TEXT
# GitHub Actions Console Output
Job: unit-and-integration (node-version: 18.x)  ✓ 3m 12s
Job: unit-and-integration (node-version: 20.x)  ✓ 2m 58s
Job: unit-and-integration (node-version: 22.x)  ✓ 3m 05s

Summary:
  ✓ lint                        (1 job)  0m 45s
  ✓ unit-and-integration        (3 jobs) 3m 12s
  ✓ e2e                         (1 job)  4m 30s
  ✓ build                       (1 job)  1m 20s

5. deploy.yml: Automatic deployment to Vercel

Deployment Environment Trigger Conditions Target
Preview Create/Update PR Deploy to Preview Branch
Production Merged into main Production environment update
Staging Release tag Pre-release validation

(1) Vercel Deploy Configuration

YAML
# .github/workflows/deploy.yml
name: Deploy to Vercel

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

env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

jobs:
  deploy-preview:
    name: Deploy Preview
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    timeout-minutes: 15
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Install Vercel CLI
        run: npm install --global vercel@latest
      
      - name: Pull Vercel Environment
        run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
        
      - name: Build Project Artifacts
        run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
        
      - name: Deploy to Vercel Preview
        run: |
          vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} > deployment-url.txt
          echo "PREVIEW_URL=$(cat deployment-url.txt)" >> $GITHUB_ENV
      
      - name: Comment Preview URL on PR
        uses: thollander/actions-comment-pull-request@v2
        with:
          message: |
            🚀 Preview Deployment Ready
            
            | Environment | URL |
            |:-----------|:----|
            | Preview | ${{ env.PREVIEW_URL }} |
            | Branch | ${{ github.head_ref }} |
            
            _This comment is automatically updated on each push._

  deploy-production:
    name: Deploy Production
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    needs: [deploy-preview]
    runs-on: ubuntu-latest
    timeout-minutes: 15
    environment: production
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Install Vercel CLI
        run: npm install --global vercel@latest
      
      - name: Pull Vercel Environment
        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
        
      - name: Build Project Artifacts
        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
        
      - name: Deploy to Vercel Production
        run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
      
      - name: Notify deployment success
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "✅ Production deployment completed: ${{ github.repository }}@${{ github.sha }}"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

6. Docker Image Build (Multi-stage, 358 MB)

For self-hosted deployments, you need to automatically build Docker images in CI.

(1) Multi-stage Dockerfile

DOCKERFILE
# Dockerfile — Next.js 16 standalone Build an image
# Phase 1: Dependency Installation(Using a Cache Layer)
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production && \
    npm cache clean --force

# Phase 2: Build(Generate .next Products)
FROM node:20-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build

# Phase 3: Run(Minimize Mirror ~358MB)
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

RUN addgroup --system --gid 1001 nodejs && \
    adduser --system --uid 1001 nextjs

COPY --from=build /app/public ./public
COPY --from=build --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=build --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

CMD ["node", "server.js"]

(2) Docker Build Action

YAML
# .github/workflows/docker.yml
name: Docker Build & Push

on:
  push:
    tags: ['v*']
  schedule:
    - cron: '0 2 * * 0'  # Early Sunday morning 2 AM

jobs:
  docker:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,format=short
            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
      
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

▶ Example: Docker image build output

TEXT
# Build Log
#1 [deps 1/1] RUN npm ci --only=production
#1 DONE 15.2s

#2 [build 1/4] COPY --from=deps /app/node_modules ./node_modules
#2 DONE 0.1s

#3 [build 2/4] COPY . .
#3 DONE 0.3s

#4 [build 3/4] RUN npm run build
#4 DONE 28.5s

#5 [runner 1/6] COPY --from=build /app/public ./public
#5 DONE 0.1s

#6 Exporting layers
#6 DONE 3.2s

# Analysis of Image Size
ghcr.io/myorg/taskflow:latest   358 MB

7. Environment Variables and Secrets Management

100%
graph TB
    A[GitHub Secrets] --> B[Actions Runtime]
    B --> C[Vercel Token]
    B --> D[Database URL]
    B --> E[API Keys]
    B --> F[Slack Webhook]
    C --> G[Deploy to Vercel]
    D --> H[Run Test]
    E --> I[Injection During Construction]
    F --> J[Deployment Notice]
    
    style A fill:#cce5ff
    style B fill:#d4edda
Environment Variable Name Purpose Source
VERCEL_TOKEN Vercel API Authentication GitHub Secrets
VERCEL_ORG_ID Vercel Team ID Vercel Dashboard
VERCEL_PROJECT_ID Vercel Project ID Vercel Dashboard
DATABASE_URL Database Connection GitHub Secrets
CODECOV_TOKEN Upload Coverage Codecov Website
SLACK_WEBHOOK_URL Deployment Notice Slack App Configuration

(1) Configure GitHub Secrets

BASH
# In GitHub Repository → Settings → Secrets and variables → Actions → Add

gh secret set VERCEL_TOKEN --body "your-vercel-token"
gh secret set DATABASE_URL --body "postgresql://user:pass@host:5432/db"
gh secret set SLACK_WEBHOOK_URL --body "https://hooks.slack.com/services/..."

(2) Environmental Isolation Strategy

YAML
# Variable Overrides in Different Environments
jobs:
  test:
    env:
      DATABASE_URL: postgresql://test:test@localhost:5432/testdb
      NODE_ENV: test
      
  deploy-preview:
    environment: preview
    env:
      DATABASE_URL: ${{ secrets.PREVIEW_DATABASE_URL }}
      NEXT_PUBLIC_API_URL: ${{ vars.PREVIEW_API_URL }}
      
  deploy-production:
    environment: production
    env:
      DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
      NEXT_PUBLIC_API_URL: https://api.taskflow.io

8. PR Preview Deployment Workflow

PR Preview allows developers to preview the effects of their changes before merging.

100%
graph TB
    A[Developer Creation PR] --> B[Actions Trigger deploy-preview]
    B --> C[Vercel Create Preview Deployment]
    C --> D[Generate preview URL]
    D --> E[Bot Comments PR Add a link]
    E --> F[The reviewer is Preview Upload for verification]
    F -->|Approval| G[Merge into main]
    G --> H[Actions Trigger deploy-production]
    
    style A fill:#cce5ff
    style F fill:#fff3cd
    style H fill:#d4edda

▶ Example: Complete PR Preview + Cleanup Workflow

Output:

TEXT
Save the above YAML configuration to the specified file path. The settings will take effect on the next server restart.
YAML
# .github/workflows/preview.yml
name: PR Preview

on:
  pull_request:
    types: [opened, synchronize, closed]

jobs:
  preview:
    if: github.event.action != 'closed'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to Vercel Preview
        id: deploy
        run: |
          npx vercel --token=${{ secrets.VERCEL_TOKEN }} \
            --scope=${{ secrets.VERCEL_ORG_ID }} \
            --confirm > preview-url.txt
          echo "url=$(cat preview-url.txt)" >> $GITHUB_OUTPUT
      
      - name: Comment URL
        uses: actions/github-script@v7
        with:
          script: |
            const url = '${{ steps.deploy.outputs.url }}'
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `🚀 Preview deployed\n\n${url}\n\n_Commit: ${context.sha}_`
            })

  cleanup:
    if: github.event.action == 'closed'
    runs-on: ubuntu-latest
    steps:
      - name: Remove Vercel Preview
        run: |
          npx vercel remove \
            taskflow-git-${GITHUB_HEAD_REF//\//-} \
            --token=${{ secrets.VERCEL_TOKEN }} \
            --yes --scope=${{ secrets.VERCEL_ORG_ID }} || true

Output:

TEXT
Sections: name, on, jobs.

9. Complete Example: Full TaskFlow CI/CD Pipeline

YAML
# .github/workflows/full-pipeline.yml
# ============================================
# TaskFlow Complete CI/CD Assembly Line
# ============================================
name: TaskFlow Full Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  release:
    types: [published]

env:
  NODE_VERSION: '20'
  PNPM_VERSION: '9'

jobs:
  # === Phase 1: Quality Access Control ===
  quality-gate:
    name: Quality Gate
    runs-on: ubuntu-latest
    timeout-minutes: 10
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: pnpm/action-setup@v4
        with:
          version: ${{ env.PNPM_VERSION }}
      
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'pnpm'
      
      - run: pnpm install --frozen-lockfile
      
      - name: TypeScript check
        run: pnpm typecheck
        
      - name: ESLint + Prettier
        run: pnpm lint && pnpm format:check
        
      - name: Unit tests with coverage
        run: pnpm test:coverage
        
      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          fail_ci_if_error: false

  # === Phase 2: Integration Testing ===
  integration:
    name: Integration Tests
    needs: [quality-gate]
    runs-on: ubuntu-latest
    timeout-minutes: 20
    
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_USER: taskflow
          POSTGRES_PASSWORD: taskflow
          POSTGRES_DB: taskflow_test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with:
          version: ${{ env.PNPM_VERSION }}
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'pnpm'
      
      - run: pnpm install --frozen-lockfile
      - run: npx prisma generate
      - run: npx prisma migrate deploy
        env:
          DATABASE_URL: postgresql://taskflow:taskflow@localhost:5432/taskflow_test
      
      - name: Integration tests
        run: pnpm test:integration
        env:
          DATABASE_URL: postgresql://taskflow:taskflow@localhost:5432/taskflow_test
      
      - name: E2E tests
        run: npx playwright install --with-deps chromium && pnpm test:e2e
        env:
          DATABASE_URL: postgresql://taskflow:taskflow@localhost:5432/taskflow_test

  # === Phase 3: Build ===
  build:
    name: Build Application
    needs: [integration]
    runs-on: ubuntu-latest
    timeout-minutes: 15
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
    
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with:
          version: ${{ env.PNPM_VERSION }}
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'pnpm'
      
      - run: pnpm install --frozen-lockfile
      - run: pnpm build
      
      - name: Build Docker image
        if: github.event_name == 'push'
        uses: docker/build-push-action@v5
        with:
          context: .
          load: true
          tags: taskflow:ci-${{ github.sha }}
      
      - name: Save build outputs
        uses: actions/upload-artifact@v4
        with:
          name: build-artifacts
          path: |
            .next/
            public/
            package.json

  # === Phase 4: Deployment ===
  deploy:
    name: Deploy
    needs: [build]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    timeout-minutes: 15
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to Vercel
        run: |
          npx vercel deploy --prod \
            --token=${{ secrets.VERCEL_TOKEN }} \
            --scope=${{ secrets.VERCEL_ORG_ID }}
      
      - name: Notify Slack
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "✅ TaskFlow deployed to production\nCommit: ${{ github.sha }}\nBy: ${{ github.actor }}"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
      
      - name: Run Lighthouse CI
        run: npx lhci autorun
        env:
          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_TOKEN }}

❓ FAQ

Q Is the free quota for GitHub Actions sufficient?
A The GitHub Free plan provides 2,000 minutes per month of Actions runtime (unlimited for public repositories). A typical Next.js CI pipeline takes about 8–12 minutes per run, so 2,000 minutes can support approximately 200 runs per month. If you need more, GitHub Team offers 3,000 minutes per month, or you can set up and self-host your own runners.
Q Why is it necessary to perform matrix testing across multiple Node.js versions?
A Your users may be running your application on various Node versions (Vercel uses 18.x, while Docker may use 20.x or 22.x). Matrix testing ensures that your code works correctly on all supported Node versions. Next.js 16 officially supports Node 18.17 and later; testing the three most recent LTS versions is best practice.
Q Which is better, vercel-action or the Vercel CLI?
A vercel-action (amondnet/vercel-action) wraps common CLI operations, but updates may be delayed. We recommend using the Vercel CLI (npm install -g vercel) directly; using run: vercel deploy --prod --token=... in Actions offers greater flexibility and ensures you have the latest version.
Q How can I protect secrets from being leaked?
A (1) Use GitHub Encrypted Secrets instead of storing them in plain text in YAML; (2) Disable automatic secret injection for pull requests from forks (Settings → Actions → Fork pull request workflows); (3) Rotate secrets regularly; (4) Use actions/secrets-scanning to detect accidental commits.
Q What is the difference between PR Preview and Vercel's default Preview?
A Vercel automatically creates a Preview Deployment for each PR (integrated with Git), but you need to log in to the Vercel Dashboard to view the URL. By manually deploying via GitHub Actions and commenting the URL in the PR, team members can click to access it directly from the PR page, which provides a better experience.
Q How can I optimize a slow CI pipeline?
A (1) Use actions/cache to cache node_modules and .next/cache; (2) Run independent jobs in parallel; (3) Use fail-fast: false to prevent a single version failure from causing the entire pipeline to be canceled; (4) Use pnpm instead of npm (for faster installation); (5) Self-host a runner (if the repository is large).

📖 Summary


📝 Exercises

  1. Basic Problem (⭐): Create a .github/workflows/ci.yml that contains three serial jobs—Lint, Test, and Build—and trigger it every time push transitions to main.

  2. Advanced Exercise (⭐⭐): Set up a complete Vercel automated deployment pipeline for your project: (1) Configure VERCEL_TOKEN in GitHub Secrets; (2) Write deploy.yml to implement PR Preview and production deployment; (3) Verify that the Preview URL appears in the PR comments.

  3. Challenge (⭐⭐⭐): Implement a multi-environment CI/CD pipeline (dev → staging → production): (1) Each environment has its own database and secrets; (2) The dev branch is automatically deployed to the dev environment; (3) The main branch is automatically deployed to staging; (4) Deployment to production requires manual approval; (5) Notify a Slack channel via a webhook after a successful deployment.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏