Getting started

Set up PullPreview in your repository in about ten minutes.

This guide walks you through adding PullPreview to an existing GitHub repository. By the end, opening a pull request and adding the pullpreview label will spin up a live preview environment automatically.

Prerequisites

Before you start:

  • A Dockerized app. Your application must be launchable via docker compose up. You need at least one docker-compose.yml in your repository.
  • A cloud account. Choose one:
    • AWS Lightsail — create an IAM user with Lightsail full access. You will need AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and your preferred AWS_REGION.
    • Hetzner Cloud — create a project and generate an API token. You will need HCLOUD_TOKEN and HETZNER_CA_KEY. The latter is a PEM-encoded SSH CA private key PullPreview uses to sign short-lived SSH access certificates — it is always required for Hetzner (not just for TLS). Generate it once with ssh-keygen -t rsa -b 3072 -m PEM -N "" -f hetzner_ca_key and store the contents as the secret.
  • A GitHub repository with Actions enabled.

Step 1 — Add GitHub secrets

Go to your repository → Settings → Secrets and variables → Actions → New repository secret.

For AWS Lightsail, add:

Secret nameValue
AWS_ACCESS_KEY_IDYour IAM access key ID
AWS_SECRET_ACCESS_KEYYour IAM secret access key

For Hetzner, add:

Secret nameValue
HCLOUD_TOKENYour Hetzner Cloud API token
HETZNER_CA_KEYYour Hetzner SSH CA private key (PEM)

The AWS_REGION value (e.g. us-east-1) is set directly in the workflow file, not as a secret.

For AWS, the example below uses static access keys for simplicity. For production we recommend short-lived credentials via GitHub OIDC and a least-privilege (lightsail:*) IAM policy — see Recommended AWS setup.

Step 2 — Create the workflow file

Create .github/workflows/pullpreview.yml in your repository. The minimal Lightsail workflow:

name: PullPreview
on:
schedule:
- cron: "30 */4 * * *"
pull_request:
types: [labeled, unlabeled, synchronize, reopened, opened, closed]
permissions:
contents: read
pull-requests: write
jobs:
deploy:
if: github.event_name == 'schedule' || github.event.label.name == 'pullpreview' || contains(github.event.pull_request.labels.*.name, 'pullpreview')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v5
- uses: pullpreview/action@v6
with:
admins: "@collaborators/push"
provider: lightsail
app_path: .
instance_type: nano
default_port: 80
compose_files: docker-compose.yml
proxy_tls: web:80
ttl: 24h
env:
AWS_ACCESS_KEY_ID: "${{ secrets.AWS_ACCESS_KEY_ID }}"
AWS_SECRET_ACCESS_KEY: "${{ secrets.AWS_SECRET_ACCESS_KEY }}"
AWS_REGION: "us-east-1"

The if: guard lets the job run in exactly the cases PullPreview needs: on schedule (cleanup), when the pullpreview label is added or removed (github.event.label.name), and on any PR event while the label is present (contains(...)). That covers creation, redeploys, and teardown — removing the label or closing/merging the PR runs the job and destroys the instance. The schedule trigger is only for periodic cleanup of closed and TTL-expired previews; it does not keep instances alive.

PullPreview v6 is PR-driven — a direct push to a branch no longer deploys it. To keep a long-lived branch preview, see Migrating from v5 to v6.

See Workflow examples for the complete Hetzner workflow and additional patterns.

Step 3 — Create the label

In your repository, go to Issues → Labels → New label. Create a label named pullpreview (matching is case-insensitive, and you can choose a different name via the label input). The color and description are up to you.

Step 4 — Open a pull request and add the label

  1. Push a branch and open a pull request.
  2. Add the pullpreview label to the PR.
  3. The workflow triggers. After a minute or two, the action posts a comment on the PR (and keeps it updated) with the preview URL. A job summary with the URL and SSH details is also written to the workflow run.

The environment redeploys on every push to the PR. Remove the pullpreview label (or close/merge the PR) to destroy the instance.

Next steps