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:

Use the hosted SaaS if you want a zero-ops setup where Previewops manages billing, plan enforcements, and infrastructure on your behalf.


Requirements


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

  1. Go to GitHub → Settings → Developer Settings → GitHub Apps → New GitHub App.
  2. Use infra/github-app-manifest.json as a reference for required permissions and events.
  3. Set the Webhook URL to the webhook_url Terraform output.
  4. Download the private key .pem file 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:

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