How to Set Up Preview Deployments for Pull Requests

Preview deployments automatically build and host a temporary, isolated version of your application for every pull request — letting reviewers see and test actual changes in a live environment before merging, without waiting for a full staging deployment.

Why Preview Deployments Are Valuable

  • Reviewers can interact with actual running changes, not just read code diffs
  • Catches visual/UX issues that code review alone misses
  • Stakeholders (design, product) can review without needing to run the code locally

Basic Architecture

Pull request opened → CI/CD builds the PR's branch → Deploys to a unique subdomain/path → Comment posted on the PR with the preview link

Prerequisites

  • A VPS with enough resources to host multiple preview environments simultaneously (or a cleanup strategy limiting active previews)
  • Nginx configured for dynamic subdomain routing

Step 1 — Set Up Wildcard DNS

Type: A
Name: *.preview
Value: YOUR_SERVER_IP

This routes any subdomain under *.preview.yourdomain.com to your VPS.

Step 2 — Configure Nginx for Dynamic Preview Routing

server {
    listen 80;
    server_name ~^pr-(?<pr_number>\d+)\.preview\.yourdomain\.com$;

    root /var/www/previews/pr-$pr_number;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

This single server block dynamically serves content based on the PR number extracted from the subdomain.

Step 3 — Create the CI/CD Workflow

name: Preview Deployment

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm install
      - run: npm run build

      - name: Deploy preview
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          ssh-keyscan -H ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts
          rsync -avz --delete -e "ssh -i ~/.ssh/deploy_key" \
            ./dist/ ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}:/var/www/previews/pr-${{ github.event.pull_request.number }}/

      - name: Comment preview link
        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: https://pr-${context.issue.number}.preview.yourdomain.com`
            })

Step 4 — Clean Up Previews When PRs Close

on:
  pull_request:
    types: [closed]

jobs:
  cleanup:
    runs-on: ubuntu-latest
    steps:
      - name: Remove preview
        run: |
          ssh -i ~/.ssh/deploy_key user@server \
            "rm -rf /var/www/previews/pr-${{ github.event.pull_request.number }}"

Restricting Access to Previews (Recommended)

location / {
    auth_basic "Preview - Authorized Only";
    auth_basic_user_file /etc/nginx/.htpasswd-previews;
}

Preview environments shouldn't be publicly discoverable/indexed, especially for private projects or those handling any sensitive content.

Adding HTTPS for Wildcard Previews

Use a wildcard certificate covering *.preview.yourdomain.com — see How to Install a Wildcard SSL Certificate with Certbot DNS Challenge.

Preventing Resource Exhaustion from Too Many Active Previews

Implement a maximum active preview count or automatic expiration (e.g. delete previews inactive for 7+ days) to prevent unbounded disk usage as PR volume grows.

Common Errors

Preview subdomain doesn't resolve — verify the wildcard DNS record was correctly configured and has propagated.

Preview shows old content — confirm the synchronize event trigger is included, so pushes to the PR branch (not just the initial open) trigger a fresh deployment.

Best Practices

  • Always restrict preview access, even for open-source projects, unless intentionally public
  • Clean up previews automatically when PRs close
  • Set a reasonable limit on total active previews to prevent resource exhaustion

Continue Reading

Browse more articles in Static Site Hosting & Frontend Deployment.

  • preview deployments, pull request preview, wildcard subdomain, ci/cd preview environment
  • 0 משתמשים שמצאו מאמר זה מועיל
?האם התשובה שקיבלתם הייתה מועילה

מאמרים קשורים

How to Host a Static Website on a VPS with Nginx

Static websites — plain HTML, CSS, and JavaScript with no server-side processing —...

How to Deploy a Next.js Application on a VPS

Next.js supports several deployment modes — fully static export, server-side rendering with...

How to Deploy a Static Site Built with Astro, Hugo, or Jekyll

Static site generators (Astro, Hugo, Jekyll) produce plain HTML/CSS/JS at build time —...

How to Optimize Images for Web Performance

Images are typically the largest contributor to page weight and load time. This guide covers...

How to Set Up a Jamstack Site with a Headless CMS Backend

The Jamstack architecture (JavaScript, APIs, Markup) combines a pre-built static frontend with a...