Workflow Examples

Basic Compose workflow

Unless stated otherwise, examples below use the default lightsail provider.

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:
          deployment_target: compose
          app_path: .
          admins: "@collaborators/push"
          instance_type: micro
          ttl: 1h
        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 only handles dangling deployment cleanup. For v6, create and update flows are driven by labeled pull request events, not direct branch pushes. See Migrating from v5 to v6 if you previously used always_on.

Hetzner Compose workflow

Generate an SSH CA key once:

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

Store the private key contents as HETZNER_CA_KEY.

- uses: pullpreview/action@v6
  with:
    provider: hetzner
    deployment_target: compose
    region: nbg1
    image: ubuntu-24.04
    instance_type: cpx21
    ttl: 1h
  env:
    HCLOUD_TOKEN: ${{ secrets.HCLOUD_TOKEN }}
    HETZNER_CA_KEY: ${{ secrets.HETZNER_CA_KEY }}

Compose with automatic HTTPS

- uses: pullpreview/action@v6
  with:
    deployment_target: compose
    proxy_tls: web:8080
    dns: rev1.click
  env:
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Compose with private registry images

- uses: pullpreview/action@v6
  with:
    deployment_target: compose
    registries: docker://${{ secrets.GHCR_PAT }}@ghcr.io
  env:
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Compose with multiple Compose files

- uses: pullpreview/action@v6
  with:
    deployment_target: compose
    compose_files: docker-compose.yml,docker-compose.pullpreview.yml
    compose_options: --build,--remove-orphans
  env:
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Helm with a local chart on Hetzner

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

jobs:
  deploy_helm:
    runs-on: ubuntu-slim
    if: github.event.label.name == 'pullpreview' || contains(github.event.pull_request.labels.*.name, 'pullpreview')
    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
          chart_set: image.tag=${{ github.sha }},baseUrl={{ pullpreview_url }}
          proxy_tls: "{{ release_name }}-web:80"
          dns: rev2.click
        env:
          HCLOUD_TOKEN: ${{ secrets.HCLOUD_TOKEN }}
          HETZNER_CA_KEY: ${{ secrets.HETZNER_CA_KEY }}

Helm with a repo chart on Lightsail

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

jobs:
  deploy_lightsail_helm:
    runs-on: ubuntu-slim
    if: github.event.label.name == 'pullpreview-helm' || contains(github.event.pull_request.labels.*.name, 'pullpreview-helm')
    timeout-minutes: 45
    steps:
      - uses: actions/checkout@v6
      - uses: pullpreview/action@v6
        with:
          label: pullpreview-helm
          provider: lightsail
          region: us-east-1
          deployment_target: helm
          chart: wordpress
          chart_repository: https://charts.bitnami.com/bitnami
          chart_set: service.type=ClusterIP
          proxy_tls: "{{ release_name }}-wordpress:80"
          instance_type: medium
          dns: rev3.click
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

See also: