> ## Documentation Index
> Fetch the complete documentation index at: https://docs.slipway.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# PR previews

> Give every pull request a running copy of your project on its own URLs.

Open a pull request against a branch your project tracks and slipway deploys a preview: a
temporary copy of the project built at the PR's commit, on its own URLs. Pushing more
commits updates it in place. Closing or merging the PR destroys it.

Previews live in the project's **Previews** tab, one row per open PR with its status, who
opened it, the URL, and actions to redeploy, extend or destroy it.

Previews require a plan that includes them. See [Plans](/plans).

## Turning them on

**Console**

Open the project's **Settings → Previews** and choose a generation mode.

**slipway.yaml**

```yaml slipway.yaml theme={"system"}
version: 1

previews:
  generation: automatic

services:
  - name: web
    runtime: docker
    ports:
      - port: 3000
        public: true
```

A committed spec wins over the console setting from that deploy on.

| Generation | Behaviour |
| - | - |
| `automatic` | Every qualifying PR gets a preview of the whole stack. |
| `manual` | Only PRs that opt in, with the `slipway-preview` label or `[slipway preview]` in the title. |
| `off` | The default. No project-level previews, and each service's own [service preview](/previews/service-previews) setting applies instead. |

A preview spawns when a PR opens, or when a draft flips to ready, against a repo and branch
one of your projects tracks. Each new commit redeploys the changed service. The PR gets a
status comment with the links, and the commit gets a check that turns green when the
preview is healthy.

## Skipping a preview

Add `[skip preview]` to the PR title, or the `slipway-preview-skip` label. This works in
every mode.

Skipping only prevents creation. It never destroys a preview that already exists. A PR that
was skipped, or opened while the mode is `manual`, gets its preview later if you add the
trigger label or title tag.

Bot-authored PRs from Dependabot and Renovate never spawn previews, so dependency bumps do
not burn compute.

## Two tiers

```mermaid theme={"system"}
flowchart LR
  proj{{"Project preview<br/>generation"}}
  full["Full-stack preview<br/>every service, isolated"]
  svc{{"Each service's own<br/>preview setting"}}
  single["Single-service preview<br/>wired to live siblings"]
  none["No preview"]

  proj e1@-->|"automatic or manual"| full
  proj e2@-->|"off"| svc
  svc e3@-->|"automatic or manual"| single
  svc e4@-->|"off"| none

  e1@{animation: slow}
  e2@{animation: slow}
  e3@{animation: slow}
  e4@{animation: slow}

  classDef choice fill:#0D857714,stroke:#0D8577,stroke-width:1.5px
  classDef result fill:#2DD4BF22,stroke:#0D8577,stroke-width:2px
  class proj,svc choice
  class full,single,none result
```

The project-level setting wins. Per-service settings are consulted only while it is `off`.

## What a preview contains

A project preview is a full copy by default: every enabled service in the project,
databases included, deployed together into an isolated namespace.

Disks seed from the default snapshot when one is set, so a preview's database starts with
real data rather than empty. See [Disks](/data/disks).

Where a full copy is too heavy, use [service previews](/previews/service-previews) instead
— or keep project previews on and choose per service what they replicate.

## Choosing what replicates

**Settings → Previews → Replication** lists every service with a per-service choice:

| Choice | Behaviour |
| - | - |
| Replicate in preview | The default. The preview runs its own copy of the service. |
| Use live service | The preview does not run the service. It reaches the **production** one instead, and `${service.X.*}` references resolve to production URLs and credentials. |

Pointing previews at a live service saves the compute and startup time of replicating
something PRs rarely change — a heavyweight database, a shared search index, a slow-to-warm
model server.

Three things to know:

* **The PR's own services always deploy.** A preview exists to run the PR's code, so
  services from the repo the PR targets replicate regardless of this setting.
* **A live database is the production database.** Code running in the preview reads and
  writes real data. Prefer replication (with a seeded snapshot) for anything a PR might
  migrate or mutate destructively, and use preview values to point elsewhere when needed.
* **The choice applies at spawn.** Changing it affects previews created from then on;
  running previews keep the shape they were born with.

Cron jobs and static sites always replicate — they have no in-cluster address a preview
could point at instead.

In a preview with live services, its services list shows a **Live from production** group,
and each of those rows links to the production service.

## URLs

Every public service in a preview gets its own hashed hostname, shaped like
`myproject-web-3f2a1b9c.<your-zone>`. The hash is stable for the life of the PR, so the
link in the PR comment keeps working across pushes.

Preview responses carry `X-Robots-Tag: noindex`, so crawlers never index them.

## Environment

Preview containers get extra variables so an app can tell it is running in a preview, which
is useful for disabling email, pointing at sandbox APIs or tagging telemetry.

| Variable | Value |
| - | - |
| `SLIPWAY_PREVIEW` | `true` in a preview |
| `SLIPWAY_PR_NUMBER` | The pull request number |
| `IS_PULL_REQUEST` | `true` in a preview, `false` in every other deploy |

`IS_PULL_REQUEST` is always set, so code can branch on it without guarding against it being
absent.

## Preview values

Previews inherit the project's variables and secrets. For anything that must not point at
production, set a preview value in **Settings → Previews**. Previews resolve it instead of
the production value, and production is untouched.

A service can also carry preview-only `env`, `cpu` and `memory` in the spec:

```yaml slipway.yaml theme={"system"}
services:
  - name: api
    runtime: docker
    cpu: 2
    memory: 4GB
    previews:
      cpu: "500m"
      memory: 512MB
      env:
        STRIPE_KEY: ${secret.STRIPE_TEST_KEY}
```

The same per-service env can be set from the console — each service's **Previews** tab has
a Preview environment editor — without committing anything. When both set the same key, the
console value wins. A preview value set in the console likewise wins over one the file
resolves.

## Lifecycle

| Stage | Behaviour |
| - | - |
| Sleep | Previews sleep when idle, on by default with a configurable window and a 5-minute minimum, and wake on the next request. A preview sleeps as a whole instance, every service together. |
| Expiry | Optionally, previews expire after a number of days without a new commit. Each push resets the clock. |
| Close | When the PR closes or merges, the preview is archived and its comment updated. Reopening the PR brings it back. |

You can destroy a preview by hand from the Previews tab. Pushes do not resurrect it, but
recreating it from the tab or reopening the PR does.

## Access

Previews respect the project's access settings under **Settings → Previews → Access**: a
shared link token, per-preview tokens, or sign-in restricted to your GitHub or GitLab org
members, with per-service exceptions for endpoints that must stay open.

## Billing

Previews bill like everything else, per second and only while awake. Each preview
provisions its own disks, billed as storage while it exists.

Preview pods count against your plan's running-pod ceiling. If a PR cannot get a preview
because of a cap or an empty balance, org admins get a notification explaining why.

Downgrading a plan never destroys previews that are already running. It stops new ones
spawning.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.