Getting Started with Previewops

Previewops automatically deploys pull-request previews to your cloud account. Comment /deploy-previewops on any PR and the bot builds your Docker image, deploys it, and replies with the live URL — all within minutes.


How it works

  1. You (or a teammate) comment /deploy-previewops on a pull request.
  2. Previewops builds a Docker image from the PR branch (via Cloud Build, SSH, or your provider's native build, depending on configuration).
  3. The image is deployed to your chosen cloud provider (Cloud Run, Fly.io, AWS Lightsail, etc.).
  4. Previewops posts a comment on the PR with the preview URL.
  5. When the PR is merged or closed, Previewops deletes the preview automatically.
  6. Previews also expire automatically after their TTL (default: 24 hours).

Prerequisites

  1. GitHub App installed on your organisation or repository. Install Previewops →

  2. Cloud credentials stored (required for Free and Premium BYOC plans). See credentials.md.

    Free BYOC: only public repositories are supported. Private repo support requires Premium BYOC.

  3. A Dockerfile in your repository root (or configured via .previewops.yaml). If your Dockerfile is not in the repo root, you will need to set the path in .previewops.yaml before your first deploy.

    Previewops builds and runs your container as-is. A few requirements:

    • The container must listen on a port (Previewops detects it automatically, or you can pin it via port: in .previewops.yaml).
    • The container must start quickly — Previewops polls for readiness after deploy; a slow cold start will delay the preview URL appearing on the PR.
    • Run as a non-root user where possible. This is a security best practice that reduces the container's attack surface.

    A minimal multi-stage Dockerfile example looks like this:

    # ── Build stage ──────────────────────────────────────────────────────────
    FROM node:22-alpine AS builder
    
    WORKDIR /app
    COPY package*.json ./
    RUN npm ci
    COPY . .
    RUN npm run build
    
    # ── Runtime stage ────────────────────────────────────────────────────────
    FROM node:22-alpine AS runtime
    
    # Non-root user (recommended as a security best practice)
    RUN addgroup -S app && adduser -S app -G app
    
    WORKDIR /app
    COPY --from=builder /app/dist ./dist
    COPY --from=builder /app/node_modules ./node_modules
    COPY package.json ./
    
    USER app
    
    ENV NODE_ENV=production
    ENV PORT=8080
    EXPOSE 8080
    
    CMD ["node", "dist/index.js"]
    

    Adjust the build commands, copied paths, dependencies, and base image for your language or framework. For non-Node stacks (Python, Go, Ruby, etc.), the same multi-stage pattern applies: compile or install in a builder stage, then copy only the required artefacts into a slim runtime image.


Your first deploy

Step 1 — Install the GitHub App

Go to the Previewops GitHub App page and install it on your organisation or the specific repositories you want to enable.

After installation, sign in to the dashboard with GitHub. If you belong to multiple organisations that have Previewops installed, you'll be prompted to choose which org's dashboard to access.

Step 2 — Store your cloud credentials (BYOC plans)

If you are on the Free or Premium BYOC plan, you need to add your cloud provider credentials before your first deploy. Click Credentials in the left sidebar, or see credentials.md for a step-by-step walkthrough.

Pro, Enterprise, and Custom plan users can skip this step — Previewops handles the cloud account for you.

Step 3 — Open a pull request and deploy

Comment on any open PR in an enabled repository:

/deploy-previewops

Previewops will reply with a "Building Previewops environment…" comment on the PR, then update it with the live preview URL once the deploy completes (typically 2–5 minutes depending on build time).

Deploy status guide:

  • ✅ Live — Previewops confirmed the app is answering requests before showing this status. If the URL stops responding afterward, see the "Troubleshooting readiness and availability" section further down this page.
  • ❌ Failed — either the build or deployment failed, or the deployed service did not respond to a readiness check within the startup window. A preview resource may not exist after a build or deployment failure. After a readiness failure, the resource exists and remains recorded so Previewops can clean it up. Expand Show build logs in the PR comment for the specific error, or run /validate-previewops to check your credentials and configuration.
  • ⌛ Expired — the preview's TTL has passed but the periodic cleanup job hasn't reaped it yet. The URL may still work for a short while. Run /delete-previewops to remove it immediately, or wait for the next cleanup pass.

Privacy note for AI debugging links: The optional Ask Claude, Ask ChatGPT, and Open in Cursor links send a generated prompt to the selected third-party service. The prompt contains the repository name, PR number, provider, branch name, and a sanitized excerpt of the deployment error. Review that information and your organisation's data-handling policy before opening one of these links. Do not use them when the remaining error text contains credentials, customer data, or other sensitive information.

Step 4 — Configure per-repo settings (required if Dockerfile is not in root)

Add a .previewops.yaml file to the root of your repository to customise TTL, concurrency, the Dockerfile path, and more. See configuration.md for all available options.

Troubleshooting readiness and availability

A deploy shows ✅ Live only after Previewops receives an HTTP response from the application. Diagnose a preview that never becomes Live differently from one that was Live and later became unavailable.

The preview never becomes Live

Common causes of an initial readiness failure include:

  1. The app is listening on 127.0.0.1 or localhost instead of 0.0.0.0. The provider's ingress layer connects through the container's network interface. A loopback-only binding refuses that connection even when curl localhost:PORT works inside the container. Most frameworks default to 0.0.0.0, but an explicit binding such as app.listen(port, '127.0.0.1') will prevent external readiness checks from succeeding.
  2. The app ignores the injected PORT environment variable. Previewops sets PORT to the configured service port, and the provider routes traffic to that port. Hardcoding a different port makes the application unreachable.
  3. The start command blocks on a migration or another dependency. For example, npx prisma db push && node server.js never starts the server if the migration cannot reach the database. Run migrations as a separate, visible step when possible so failures surface independently instead of preventing the application from starting.

The preview was Live but later stopped responding

Because the application already passed its readiness check, its host and port configuration were valid at deployment time. Check the runtime and provider logs for an application crash, an out-of-memory termination, or an unavailable dependency such as a database or external API. Confirm that the preview has not expired, inspect the provider's service status, and redeploy after correcting the underlying issue.


Next steps