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

# Preview deployments

> A live preview URL for every pull request

Every pull request gets its own running copy of your app at a unique URL. Share it with your team, click around before you merge, and know that what reviewers see is exactly what will ship.

## How it works

1. A teammate opens a pull request against your tracked branch.
2. Nubo builds the PR's branch and runs it alongside your production Frame.
3. The preview gets its own Nubo-assigned subdomain, distinct from the production Frame's URL.
4. Push a new commit to the PR? Nubo rebuilds the preview.
5. Merge or close the PR? Nubo tears the preview down.

Production is never affected. Previews run in the same Space and use the same build configuration. Both [Project-scoped and Frame-scoped environment variables](/environment-variables) from the production Frame carry over into the preview automatically. If the same name is set at both levels, the Frame-scoped value wins, just like it does in production.

## Enable or disable previews

Previews are enabled per Frame. Open the Frame's **CI/CD** tab and turn on "Preview deployments" for any Frame where you want a preview per pull request. Turn it back off to stop building previews for that Frame.

<Warning>
  The API route `PATCH .../ci` **replaces the whole CI spec**, it does not merge. Any field you leave out of the body resets to its default: auto-deploy switches off, custom build and install commands clear, and skip tags reset. Sending just `{ "preview_deploys": true }` would quietly wipe the rest of your CI settings. The **CI/CD** toggle above changes only this one field, so prefer it for a single flip.
</Warning>

If you do script it, fetch the Frame first, take its `spec.ci` object, change `preview_deploys`, and PATCH the complete object back:

```bash theme={null}
curl https://shuttle.withnubo.com/v2/projects/<project_id>/spaces/<space_id>/frames/<frame_id> \
  -H "Authorization: Bearer <your-token>"
```

Then send the full spec (these are the field defaults, replace each value with what your Frame returned so nothing else changes):

```bash theme={null}
curl -X PATCH https://shuttle.withnubo.com/v2/projects/<project_id>/spaces/<space_id>/frames/<frame_id>/ci \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "auto_deploy_primary": true,
    "skip_tags": ["[skip ci]", "[ci skip]"],
    "ignored_paths": [],
    "root_directory": "/",
    "build_command": null,
    "install_command": null,
    "preview_deploys": true,
    "preview_source": "pull_requests",
    "preview_allowed_branches": [],
    "preview_ttl_hours": 168,
    "preview_retention_count": 2,
    "preview_env_overrides": {}
  }'
```

## Finding the preview URL

Check the **Deployments** tab on the Frame; previews show up there labeled with the PR number. Nubo also writes back to the pull request itself: it posts a comment containing the **Preview URL**, plus a commit status and a GitHub deployment. These update on every build and are removed when the PR closes.

## What's different from a production deploy

A preview is a separate runtime with its own URL, but otherwise behaves like any other Frame:

* Build steps, language detection, and runtime behavior are identical.
* Both Project-scoped and Frame-scoped [environment variables](/environment-variables) are available in the preview, with the Frame-scoped value winning if a name is set at both levels.
* [Build logs](/build-logs) are captured the same way.
* Custom domains are **not** attached to previews - previews use their Nubo-assigned URL only.

## Cleanup

Previews are torn down automatically when the PR is closed (merged or not). Two other things clean them up even while the PR is still open:

* **Inactivity TTL.** A preview that goes untouched for `preview_ttl_hours` (default 7 days) is reaped. Configure it on the CI spec.
* **Retention cap.** Only the newest `preview_retention_count` previews per Frame are kept (default 2); older ones are reaped first. Configure it on the CI spec too.

There is no manual delete button for a running preview. To force one down, close the pull request (you can reopen it to rebuild), or let the TTL and retention rules take care of it.

## Related

<Card title="CI/CD" icon="rotate" href="/ci-cd">
  Tune how Nubo builds on every push
</Card>

<Card title="Deployments" icon="rocket" href="/deployments">
  See builds and their status per Frame
</Card>
