Skip to main content
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 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.
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.
If you do script it, fetch the Frame first, take its spec.ci object, change preview_deploys, and PATCH the complete object back:
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):
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 are available in the preview, with the Frame-scoped value winning if a name is set at both levels.
  • 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.

CI/CD

Tune how Nubo builds on every push

Deployments

See builds and their status per Frame