Skip to content
Subnomic
← Back to blog
Sites Oct 27, 2026 · 3 min read

Every deploy has an address, live or not

Deploy without --prod and nothing changes for visitors. You get an address that serves exactly the files that would go live.

subnomic sites deploy without --prod uploads a build, seals it and stops there. The site’s own address goes on serving what it served before, and the command prints a second address that serves the new build:

$ subnomic sites deploy
✓ Hashed 326 files (4.2 MB)
  312 already stored, 14 to upload
✓ Uploaded 14 files
✓ Deployed as a preview
  add --prod to make it live
https://blog--01a182d666547b3c.subnomic.app/

Only the last line goes to stdout, so url=$(subnomic sites deploy) captures the address and nothing else.

A preview is a deployment, not a copy

Every deploy creates a deployment: the build’s files under a prefix of their own, unchanged once sealed. What the site serves is a pointer naming one of them. A preview is any sealed deployment the pointer does not name, so what you open at its address is byte for byte what visitors would get after promotion. Nothing is rebuilt to show it, and nothing is rebuilt to make it live.

That includes its rules. _redirects and _headers are read from the deployment being served, so a preview answers with the rules it shipped with, not the ones that are live.

The address

A preview address is the site’s address, two hyphens, and the first sixteen hex characters of the deployment’s id. Site addresses may not contain two hyphens in a row, which keeps the split unambiguous: blog is the live site, and blog--01a182d666547b3c is one build of it, whatever is live.

Only a sealed deployment answers there. A build that is still uploading, was cancelled halfway or failed to seal is not served at its address, so a half-finished upload cannot be opened by anyone.

A preview of a static build is served with X-Robots-Tag: noindex, set after your _headers file is applied, so no rule in it can switch that off. A copy of every build in a search index would compete with the site itself.

Who can open it

Anyone who has the address. A preview address is not access control: share it the way you would share the live site, and do not put anything in a preview build that you would not publish.

If a build should not be seen, protect the site. In the console, a site’s Settings tab has Who can see this site, and besides Anyone it offers Anyone with the password, remembered for 12 hours once entered, and Workspace members only. Protection belongs to the site, not to a build, so every preview address is behind the same gate; the unlock is remembered per address, so each preview asks once. A protected static page is sent with Cache-Control: private, no-store, whatever _headers asks for.

Making it live

Promotion moves the pointer and copies nothing. On a site’s Deployments tab, Make live promotes the build you pick, and the one it replaces stays there to go back to. From a pipeline, subnomic sites deploy --prod uploads and promotes in one command; any file an earlier deployment of the site already holds is linked rather than uploaded, so it sends only what changed.

Either way, a promotion names the deployment it expects to replace. If something else went live in between, a rollback during an incident for example, the promotion is refused with 409 and sites.deployments.error.changed_underneath instead of quietly undoing it.

How many are kept

Previews are deployments, so they share the site’s history. A static site keeps its live deployment, everything created in the last two hours, and the ten newest sealed deployments older than that. The rest are retired and their files deleted. A preview of a long-merged branch therefore stops answering after a while, and a busy branch shortens how far back production can roll.

An upload that never finished is given up after six hours, and its partial files go with it.

A preview per pull request

In CI the two commands split by event: a pull request deploys a preview, and a push to main deploys with --prod.

if [ "$GITHUB_EVENT_NAME" = "push" ]; then
  url=$(subnomic sites deploy --prod)
else
  url=$(subnomic sites deploy)
fi
echo "url=$url" >> "$GITHUB_OUTPUT"

Every push to the branch makes another preview with an address of its own. The key in SUBNOMIC_TOKEN needs sites.deploy to publish a preview, and sites.read as well to go live, because promotion reads the site first. Neither scope lets the pipeline delete the site it publishes to.

The whole workflow, with the preview address commented on the pull request and a rollback button, is in Deploy from GitHub Actions. Domains, redirects and protection are in the Sites docs, and why a rollback is only a pointer is in Rollback should move a pointer.