Multiple environments per PR
Run several independent preview environments from a single pull request with deployment_variant — e.g. frontend + backend.
Sometimes a single pull request needs more than one preview environment — for example a backend API alongside its frontend SPA. PullPreview supports this through the deployment_variant input: define one job per environment, and each variant gets its own instance, hostname, preview URL, SSH access, and TTL handling.
How it works
Add a separate job for each environment you want, and give each one a unique deployment_variant value (4 characters max). Every variant runs as an independent job and is provisioned as its own instance, so the variants never share resources or interfere with each other.
A few things to keep in mind:
- Each variant can deploy a different repository — for instance an API backend in one variant and its SPA frontend in another.
- All variants share the same
label, so a single label on the PR drives the entire set of environments. - This works on both providers and both deployment targets (Docker Compose and Helm). See the configuration reference for the full list of inputs.
Example workflow
The workflow below defines two environments, env1 and env2. Both jobs use the same label, so labeling the PR once spins up both environments together.
name: pullpreview_multi_envon: schedule: - cron: "45 0 * * *" pull_request: types: [labeled, unlabeled, synchronize, closed, reopened, opened]
jobs: deploy_env1: runs-on: ubuntu-latest if: github.event_name == 'schedule' || github.event.label.name == 'pullpreview-multi-env' || contains(github.event.pull_request.labels.*.name, 'pullpreview-multi-env') timeout-minutes: 30 steps: - uses: actions/checkout@v6 - uses: pullpreview/action@v6 with: deployment_variant: env1 label: pullpreview-multi-env admins: crohr app_path: ./examples/wordpress instance_type: micro 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 }}"
deploy_env2: runs-on: ubuntu-latest if: github.event_name == 'schedule' || github.event.label.name == 'pullpreview-multi-env' || contains(github.event.pull_request.labels.*.name, 'pullpreview-multi-env') timeout-minutes: 30 steps: - uses: actions/checkout@v6 - uses: pullpreview/action@v6 with: deployment_variant: env2 label: pullpreview-multi-env admins: crohr app_path: ./examples/wordpress instance_type: micro 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 }}"deploy_env2 is identical to deploy_env1 apart from its deployment_variant. Point its app_path (and optionally a different checked-out repository) at the second service to deploy distinct applications.
The if: guard is the canonical v6 form: it runs on scheduled cleanup, on the labeling event itself, and on any subsequent PR event while the label is present. Do not add a && github.event.action != 'closed' exclusion — that would skip teardown when the PR is closed. The same guard belongs on every variant job. For more on this pattern, see workflow examples and the private registries guide.
v6 is PR-based
As with single-environment workflows, multi-environment previews in v6 are driven by pull requests. The schedule trigger exists only to clean up expired environments — it does not keep them running. Direct branch push events are not a replacement for the always_on mode that was removed in v5. If you are coming from v5, read migrating from v5 to v6 to understand the new model.