- AWS Lightsail
- Default provider
- Hetzner
- Cloud alternative
- auto TLS
- proxy_tls + custom domains
Full-fidelity preview environments for GitHub
Git push, deployed
Once installed in your repository, this action is triggered whenever a change is made to
pull requests labeled with pullpreview.
When triggered, PullPreview:
- Checks out repository code.
- Provisions an instance with Docker + Docker Compose on your selected provider:
lightsail(default)hetzner
- Deploys updates continuously from pull requests.
- Reports preview URLs in the GitHub UI.
Features
- Works with your existing tooling: if your app can run with Docker Compose, it can run as a PullPreview environment.
- Starts and destroys environments from PR labels.
- Runs on your cloud account (AWS Lightsail by default, Hetzner as an alternative).
- Supports privacy-first review workflows: your code stays within GitHub and your own infrastructure.
- Keeps preview state across deploys through Docker volumes.
- Makes reviewer access straightforward:
- preview links in PR checks,
- SSH access via installed GitHub user keys.
- Auto HTTPS and custom domain support:
proxy_tlsinjects a Caddy sidecar and handles Let’s Encrypt,dnslets you userev*.clickbuilt-ins or your custom domain.
Workflow examples
Use the provider-specific tab below and copy the complete workflow for your deployment target.
name: PullPreviewon: schedule: - cron: "30 */4 * * *" push: branches: - main pull_request: types: [labeled, unlabeled, synchronize, reopened, opened, closed]
permissions: contents: read pull-requests: write
jobs: deploy: if: github.event_name == 'schedule' || github.event_name == 'push' || (github.event.action != 'closed' && github.event.action != 'unlabeled' && (github.event.label.name == 'pullpreview' || contains(github.event.pull_request.labels.*.name, 'pullpreview'))) runs-on: ubuntu-slim 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: 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"name: PullPreviewon: schedule: - cron: "30 */4 * * *" push: branches: - main pull_request: types: [labeled, unlabeled, synchronize, reopened, opened, closed]
permissions: contents: read pull-requests: write
jobs: deploy: if: github.event_name == 'schedule' || github.event_name == 'push' || (github.event.action != 'closed' && github.event.action != 'unlabeled' && (github.event.label.name == 'pullpreview' || contains(github.event.pull_request.labels.*.name, 'pullpreview'))) runs-on: ubuntu-slim timeout-minutes: 30 steps: - uses: actions/checkout@v5 - uses: pullpreview/action@v6 with: admins: "@collaborators/push" provider: hetzner app_path: . region: nbg1 image: ubuntu-24.04 instance_type: cpx21 default_port: 80 compose_files: docker-compose.yml proxy_tls: web:8080 ttl: 1h env: HCLOUD_TOKEN: "${{ secrets.HCLOUD_TOKEN }}" HETZNER_CA_KEY: "${{ secrets.HETZNER_CA_KEY }}"HTTPS and custom domains
proxy_tlsconfigures HTTPS termination and routes traffic to your selected service.dnscontrols the public domain prefix used for generated preview hostnames.
# auto TLS using built-in rate-limit-friendly domainwith: proxy_tls: web:80 dns: rev1.click# custom domainwith: dns: preview.example.comIf you use proxy_tls on the shared default domain (my.preview.run), Let’s Encrypt rate
limits can be reached with high preview churn. Use a domain from rev1.click to rev9.click
or your own custom domain for safer issuance capacity.
Testimonials
It saved us thousands in development costs, and the monthly cost of the Lightsail instances is immaterial compared to SaaS tools that want to license on a per user or per repository basis.
An incredibly valuable tool for enhancing my workflow and improving collaboration with my team. One of the standout features of this action is its ease of setup.
We were stuck trying out lots of different managed solutions with Vercel, Render etc, but they all had problems. Using docker-compose with self-hosting made it so much simpler 👍
We were struggling with product reviews, as we performed them too late on the developpement cycle and faced difficulties to isolate the issues. It lead to long threads, frictions and was energy and time consuming for both technical and product teams. Then came PullPreview: an easy way to review PRs for product teams and ease communication between developers and PM/Designers.
Useful for the entire team
- Product Owners: Interact with new features as they are built and catch issues earlier.
- Developers: Share work in progress with instant, reproducible review environments.
- Ops: Keep operations ownership in your own cloud account.
- CTOs: Avoid third-party SaaS lock-in; your code and data stay in GitHub and your own servers.
Installation & Usage
- Getting Started
- Action Inputs / Outputs
- Workflow Examples
- FAQ
-> Please see the wiki for the full documentation.
Pricing
The action is open source, but commercial use is licensed separately.
- Non-profit individual use can be free.
- In all other cases, you need a commercial license (300€/year, currently covering one organization).
Then, for each preview environment you pay your provider’s compute cost. In practice, that means you pay only for the underlying infrastructure resources you actually run, with no extra per-preview markup.
Why this approach
- Simple architecture: One VM per preview, no Kubernetes complexity.
- Full GitHub integration: status updates and checks keep feedback in the PR flow.
- Secure by default: SSH access is scoped and controlled via GitHub users.
- Cost efficient: choose one of two providers and right-size instances via
instance_type.
Who did this?
👋 I’m Cyril Rohr, a freelance CTO/DevOps from France.
If you’re also optimizing CI/CD spend, check out runs-on.com. Their GitHub Actions runners are advertised as up to 10x cheaper than GitHub-hosted runners in many common workloads.