Self-Hosting Guide
Previewops can be self-hosted — run your own instance without using the hosted SaaS service. Self-hosted mode gives you unlimited deploys, no plan limits, and full control over where preview containers run.
The SaaS control plane (billing dashboard, GitHub OAuth, Stripe) is entirely optional. Leaving DATABASE_URL unset disables it completely.
When to self-host
Choose self-hosted if you:
- Want unlimited deploys with no monthly caps.
- Prefer to keep all infrastructure within your own GCP (or other cloud) project.
- Have compliance requirements that prevent using a third-party SaaS.
- Already have a GitHub App registered under a different name.
Use the hosted SaaS if you want a zero-ops setup where Previewops manages billing, plan enforcements, and infrastructure on your behalf.
Requirements
- A GCP project — used for Cloud Build (image builds) and Cloud Run (default preview host). Cloud Build runs in GCP for the
cloud-runprovider; providers that build on their own infrastructure (Fly, Hetzner, DigitalOcean, Lightsail, Railway, Render) do not require GCP for the build step. - A GitHub App installed on your organisation or repositories.
- A container host for the Previewops process itself (Cloud Run is recommended, but any container platform works).
Minimal setup (Cloud Run provider)
1. Create GCP infrastructure
cd infra/terraform
terraform init
terraform apply \
-var="gcp_project_id=YOUR_PROJECT_ID" \
-var="github_app_id=YOUR_APP_ID"
This provisions Cloud Run, Artifact Registry, Cloud Scheduler, IAM bindings, and Secret Manager secrets.
2. Create and configure a GitHub App
- Go to GitHub → Settings → Developer Settings → GitHub Apps → New GitHub App.
- Use
infra/github-app-manifest.jsonas a reference for required permissions and events. - Set the Webhook URL to the
webhook_urlTerraform output. - Download the private key
.pemfile and note the App ID.
Required permissions: Contents (Read), Issues (Write), Pull requests (Read + Write), Metadata (Read).
Required webhook events: issue_comment, pull_request, push, installation.
3. Store secrets
export GCP_PROJECT_ID=your-project-id
chmod +x infra/scripts/setup-github-app.sh
./infra/scripts/setup-github-app.sh
4. Deploy
chmod +x infra/scripts/deploy.sh
./infra/scripts/deploy.sh
Or push to main — the Cloud Build pipeline (cloudbuild.yaml) handles it automatically.
5. Install the App
Go to your GitHub App settings → Install App and install it on the repositories you want to enable.
6. Test
Open a PR and comment /deploy-previewops. Previewops should reply with a "building…" comment and update it with the preview URL.
Running Previewops on a non-GCP host
If you want to host the Previewops process outside GCP (e.g., your own server or a different cloud), you still need GCP for the image build step. Run the container with the minimum required env vars:
docker run -p 8080:8080 \
-e GITHUB_APP_ID=123456 \
-e GITHUB_WEBHOOK_SECRET=... \
-e GITHUB_APP_PRIVATE_KEY_PATH=/secrets/key.pem \
-e GCP_PROJECT_ID=my-project \
-v /path/to/key.pem:/secrets/key.pem:ro \
ghcr.io/your-org/previewops:latest
Using a non-Cloud Run provider
Set provider and providerConfig in the repository's .previewops.yaml. The image build step still runs in Cloud Build. See the provider guides for per-provider setup instructions.
Example — Hetzner Cloud:
provider: hetzner
providerConfig:
sshKeyName: my-hetzner-key
serverType: cx22
location: nbg1
Disabling the SaaS control plane
Set no DATABASE_URL and no Stripe/OAuth env vars. The app will:
- Serve webhooks and deploy previews with no plan limits.
- Apply no Free BYOC restrictions (private repo enforcement and inactivity culling are both disabled).
- Use provider-based
listAllPreviews()for TTL cleanup instead of the database. - Return 404 for all
/control/*and/pricingroutes.
This is equivalent to the original Previewops before the SaaS layer was added.
TTL cleanup
If you are not using Terraform (or Cloud Scheduler is unavailable), run cleanup manually:
npm run cleanup:run
# or using the CLI:
node dist/cli.js cleanup
Set up a cron job on your host to run this hourly.
GitHub App slug
If you registered your GitHub App under a name other than previewops, set the GITHUB_APP_SLUG env var to the URL-safe slug shown on your App's GitHub settings page. This is used to build install-redirect URLs for unauthenticated users.
GITHUB_APP_SLUG=my-custom-app-name