preview-environments-builder

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Preview Environments Builder

预览环境构建工具

Deploy isolated preview environments for every pull request.
为每个Pull Request部署独立的预览环境。

Vercel Preview Deployment

Vercel预览部署

yaml
undefined
yaml
undefined

.github/workflows/preview.yml

.github/workflows/preview.yml

name: Preview Deployment
on: pull_request: types: [opened, synchronize, reopened]
jobs: deploy-preview: runs-on: ubuntu-latest environment: name: preview-${{ github.event.pull_request.number }} url: ${{ steps.deploy.outputs.url }}
steps:
  - uses: actions/checkout@v4

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

  - run: npm ci
  - run: npm run build

  - name: Deploy to Vercel
    id: deploy
    uses: amondnet/vercel-action@v25
    with:
      vercel-token: ${{ secrets.VERCEL_TOKEN }}
      vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
      vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
      scope: ${{ secrets.VERCEL_ORG_ID }}
      alias-domains: pr-${{ github.event.pull_request.number }}.myapp.dev

  - name: Comment PR
    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!\n\n🔗 **URL:** ${{ steps.deploy.outputs.url }}\n\nCommit: ${context.sha.substring(0, 7)}`
        })
undefined
name: Preview Deployment
on: pull_request: types: [opened, synchronize, reopened]
jobs: deploy-preview: runs-on: ubuntu-latest environment: name: preview-${{ github.event.pull_request.number }} url: ${{ steps.deploy.outputs.url }}
steps:
  - uses: actions/checkout@v4

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

  - run: npm ci
  - run: npm run build

  - name: Deploy to Vercel
    id: deploy
    uses: amondnet/vercel-action@v25
    with:
      vercel-token: ${{ secrets.VERCEL_TOKEN }}
      vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
      vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
      scope: ${{ secrets.VERCEL_ORG_ID }}
      alias-domains: pr-${{ github.event.pull_request.number }}.myapp.dev

  - name: Comment PR
    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!\n\n🔗 **URL:** ${{ steps.deploy.outputs.url }}\n\nCommit: ${context.sha.substring(0, 7)}`
        })
undefined

Docker-based Preview

基于Docker的预览部署

yaml
preview:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4

    - name: Build Docker image
      run: |
        docker build -t myapp:pr-${{ github.event.pull_request.number }} .

    - name: Deploy to Kubernetes
      run: |
        kubectl create namespace pr-${{ github.event.pull_request.number }} || true
        kubectl apply -f k8s/preview.yml \
          --namespace pr-${{ github.event.pull_request.number }}
        kubectl set image deployment/myapp \
          myapp=myapp:pr-${{ github.event.pull_request.number }} \
          --namespace pr-${{ github.event.pull_request.number }}

    - name: Get preview URL
      id: url
      run: |
        URL=$(kubectl get ingress myapp \
          --namespace pr-${{ github.event.pull_request.number }} \
          -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
        echo "url=https://pr-${{ github.event.pull_request.number }}.preview.myapp.com" >> $GITHUB_OUTPUT

    - name: Comment PR
      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.url.outputs.url }}`
          })
yaml
preview:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4

    - name: Build Docker image
      run: |
        docker build -t myapp:pr-${{ github.event.pull_request.number }} .

    - name: Deploy to Kubernetes
      run: |
        kubectl create namespace pr-${{ github.event.pull_request.number }} || true
        kubectl apply -f k8s/preview.yml \
          --namespace pr-${{ github.event.pull_request.number }}
        kubectl set image deployment/myapp \
          myapp=myapp:pr-${{ github.event.pull_request.number }} \
          --namespace pr-${{ github.event.pull_request.number }}

    - name: Get preview URL
      id: url
      run: |
        URL=$(kubectl get ingress myapp \
          --namespace pr-${{ github.event.pull_request.number }} \
          -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
        echo "url=https://pr-${{ github.event.pull_request.number }}.preview.myapp.com" >> $GITHUB_OUTPUT

    - name: Comment PR
      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.url.outputs.url }}`
          })

Cleanup on PR Close

PR关闭时清理

yaml
undefined
yaml
undefined

.github/workflows/cleanup-preview.yml

.github/workflows/cleanup-preview.yml

name: Cleanup Preview
on: pull_request: types: [closed]
jobs: cleanup: runs-on: ubuntu-latest steps: - name: Delete Vercel deployment uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const deployments = await github.rest.repos.listDeployments({ owner: context.repo.owner, repo: context.repo.repo, environment:
preview-${context.issue.number}
});
        for (const deployment of deployments.data) {
          await github.rest.repos.createDeploymentStatus({
            owner: context.repo.owner,
            repo: context.repo.repo,
            deployment_id: deployment.id,
            state: 'inactive'
          });

          await github.rest.repos.deleteDeployment({
            owner: context.repo.owner,
            repo: context.repo.repo,
            deployment_id: deployment.id
          });
        }

  - name: Cleanup Kubernetes namespace
    run: |
      kubectl delete namespace pr-${{ github.event.pull_request.number }} --ignore-not-found=true
undefined
name: Cleanup Preview
on: pull_request: types: [closed]
jobs: cleanup: runs-on: ubuntu-latest steps: - name: Delete Vercel deployment uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const deployments = await github.rest.repos.listDeployments({ owner: context.repo.owner, repo: context.repo.repo, environment:
preview-${context.issue.number}
});
        for (const deployment of deployments.data) {
          await github.rest.repos.createDeploymentStatus({
            owner: context.repo.owner,
            repo: context.repo.repo,
            deployment_id: deployment.id,
            state: 'inactive'
          });

          await github.rest.repos.deleteDeployment({
            owner: context.repo.owner,
            repo: context.repo.repo,
            deployment_id: deployment.id
          });
        }

  - name: Cleanup Kubernetes namespace
    run: |
      kubectl delete namespace pr-${{ github.event.pull_request.number }} --ignore-not-found=true
undefined

Environment Naming

环境命名规范

yaml
undefined
yaml
undefined

Consistent naming pattern

Consistent naming pattern

environment: name: preview-pr-${{ github.event.pull_request.number }} url: https://pr-${{ github.event.pull_request.number }}.preview.myapp.com
undefined
environment: name: preview-pr-${{ github.event.pull_request.number }} url: https://pr-${{ github.event.pull_request.number }}.preview.myapp.com
undefined

Database Seeding

数据库初始化

yaml
- name: Setup preview database
  run: |
    # Create database
    psql -c "CREATE DATABASE preview_pr_${{ github.event.pull_request.number }};"

    # Seed with test data
    npm run db:seed -- --database=preview_pr_${{ github.event.pull_request.number }}
  env:
    DATABASE_URL: ${{ secrets.PREVIEW_DB_URL }}
yaml
- name: Setup preview database
  run: |
    # Create database
    psql -c "CREATE DATABASE preview_pr_${{ github.event.pull_request.number }};"

    # Seed with test data
    npm run db:seed -- --database=preview_pr_${{ github.event.pull_request.number }}
  env:
    DATABASE_URL: ${{ secrets.PREVIEW_DB_URL }}

Best Practices

最佳实践

  1. Unique URLs: pr-{number}.preview.domain.com
  2. Auto cleanup: Delete on PR close
  3. Comment on PR: Link to preview
  4. Environment protection: Require approval
  5. Resource limits: Prevent abuse
  6. TTL: Auto-delete after 7 days
  7. Secrets management: Use preview-specific secrets
  1. 唯一URL:pr-{number}.preview.domain.com
  2. 自动清理:PR关闭时删除资源
  3. PR评论:添加预览链接
  4. 环境保护:需要审批
  5. 资源限制:防止滥用
  6. 存活时间(TTL):7天后自动删除
  7. 密钥管理:使用预览专用密钥

Output Checklist

输出检查清单

  • Preview deployment workflow
  • Unique URL generation
  • PR comment with link
  • Cleanup workflow on close
  • Environment naming strategy
  • Database seeding (if needed)
  • Resource limits configured
  • 预览部署工作流
  • 唯一URL生成
  • 带链接的PR评论
  • 关闭时的清理工作流
  • 环境命名策略
  • 数据库初始化(如有需要)
  • 资源限制配置