CI/CD Preview Deploys

Give every pull request a live preview URL — without provisioning infrastructure.

How it works

In your CI pipeline, install NullBore, start your app, open a tunnel, and post the URL as a PR comment. Reviewers click the link and see a live version of the branch.

GitHub Actions example

name: Preview Deploy
on:
  pull_request:
    types: [opened, synchronize]

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

      - name: Install NullBore
        run: curl -fsSL nullbore.com/install.sh | sh

      - name: Start app
        run: |
          npm ci
          npm start &
          sleep 5  # wait for server to boot

      - name: Open tunnel
        env:
          NULLBORE_API_KEY: ${{ secrets.NULLBORE_API_KEY }}
        run: |
          export PATH="$PATH:$HOME/.local/bin"
          nullbore open --port 3000 --name pr-${{ github.event.number }} --ttl 2h
          echo "PREVIEW_URL=https://pr-${{ github.event.number }}.yourname.nullbore.com" >> $GITHUB_ENV

      - name: Comment on PR
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: `🔗 Preview: ${process.env.PREVIEW_URL}\n\n_Expires in 2 hours._`
            })

Named tunnels (like pr-123) require a Dev plan ($7/mo) or higher. On the free tier, use random slugs and capture the URL from nullbore open output instead.

Why this over alternatives

  • No infrastructure to manage — no Vercel/Netlify lock-in, no extra cloud resources
  • Works with any stack — if it runs on localhost, NullBore can expose it
  • Time-limited — preview URLs auto-expire, no orphaned deployments
  • Self-hostable — run your own NullBore server for fully internal previews

Tips

  • Use pr-$NUMBER as the tunnel name for stable, predictable URLs.
  • Set TTL to match your review cycle (2-4 hours is typical).
  • Add a cleanup step that runs nullbore close on PR merge/close.