Getting Started

1. Choose a deployment target

Upgrading from v5 and relying on always_on? See Migrating from v5 to v6 before using the workflow examples below.

PullPreview supports two deployment targets:

  • compose: deploy your app with Docker Compose on the preview instance
  • helm: bootstrap k3s on the preview instance and deploy a Helm chart

Choose compose if your repo already runs with docker compose up.
Choose helm if your app is already packaged as a Helm chart.

See Deployment Targets for the full comparison.

2. Prepare your app

For compose

  • Keep your app under app_path (default .).
  • PullPreview looks for docker-compose.yml by default.
  • If your Compose files live elsewhere, set app_path and/or compose_files.

For helm

  • Set deployment_target: helm.
  • Set chart.
  • Set proxy_tls because Helm previews are always exposed through the PullPreview-managed Caddy gateway.
  • If you use Helm values files, keep them under app_path and pass them via chart_values.

3. Create the trigger label

Create a repository label named pullpreview, or use another label and set the label input.

4. Configure cloud credentials

Lightsail (default)

Add repository secrets:

  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY
  • optional AWS_REGION (defaults to us-east-1)

For least-privilege setup, see Recommended AWS Configuration.

Hetzner

Add repository secrets:

  • HCLOUD_TOKEN
  • HETZNER_CA_KEY

HETZNER_CA_KEY is a private SSH CA key used by PullPreview for per-run SSH certificate authentication.

Generate one once and store only the private key in GitHub Secrets:

ssh-keygen -t rsa -b 3072 -m PEM -N "" -f hetzner_ca_key

Optional Hetzner action inputs:

  • provider: hetzner
  • region: nbg1 (default)
  • image: ubuntu-24.04 (default)
  • instance_type: cpx21 (default)

5. Add a workflow

Minimal compose workflow

name: PullPreview
on:
  schedule:
    - cron: "30 */4 * * *"
  pull_request:
    types: [labeled, unlabeled, synchronize, closed, reopened, opened]

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-slim
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v6
      - uses: pullpreview/action@v6
        with:
          app_path: .
          deployment_target: compose
          admins: "@collaborators/push"
          # optional HTTPS
          # proxy_tls: web:80
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_REGION: us-east-1

schedule is optional and is only used for cleanup of dangling deployments. v6 preview deploys themselves are driven by pull request events.

Minimal helm workflow

name: PullPreview Helm
on:
  pull_request:
    types: [labeled, unlabeled, synchronize, closed, reopened, opened]

permissions:
  contents: read
  pull-requests: write

jobs:
  deploy:
    if: github.event.label.name == 'pullpreview' || contains(github.event.pull_request.labels.*.name, 'pullpreview')
    runs-on: ubuntu-slim
    timeout-minutes: 45
    steps:
      - uses: actions/checkout@v6
      - uses: pullpreview/action@v6
        with:
          provider: hetzner
          deployment_target: helm
          chart: ./charts/my-app
          chart_values: charts/my-app/values-preview.yaml
          proxy_tls: "{{ release_name }}-web:80"
        env:
          HCLOUD_TOKEN: ${{ secrets.HCLOUD_TOKEN }}
          HETZNER_CA_KEY: ${{ secrets.HETZNER_CA_KEY }}

6. Deploy

Open a pull request, add the trigger label, and let the workflow run.

On success you get:

  • a live preview URL
  • PR status comments updated by PullPreview
  • a GitHub job summary with preview and SSH details
  • action outputs: live, url, host, and username

Next: