{"protocol":"clank-doc/1","frameworkVersion":"0.9.4","slug":"preview-environments","title":"Preview environments","description":"Clank previews are temporary deployments attached to a production project. Each preview is a real isolated project with its own URL, SQLite database, migration history, releases, secrets, jobs, logs, metrics, backups, and project scoped tok","group":{"id":"deploy","title":"Deploy and operate"},"url":"https://docs.clank.run/docs/preview-environments","source":"docs/preview-environments.md","headings":["Deploy a preview","Remove a preview","Control plane UI and API","Isolation and limits","Self hosting policy","CI naming"],"tableOfContents":[{"id":"deploy-a-preview","title":"Deploy a preview","level":2},{"id":"remove-a-preview","title":"Remove a preview","level":2},{"id":"control-plane-ui-and-api","title":"Control-plane UI and API","level":2},{"id":"isolation-and-limits","title":"Isolation and limits","level":2},{"id":"self-hosting-policy","title":"Self-hosting policy","level":2},{"id":"ci-naming","title":"CI naming","level":2}],"markdown":"# Preview environments\n\nClank previews are temporary deployments attached to a production project. Each preview is a real\nisolated project with its own URL, SQLite database, migration history, releases, secrets, jobs,\nlogs, metrics, backups, and project-scoped tokens.\n\nThe important default is absence: creating a preview does **not** copy production data or secrets.\nAn application must populate test data explicitly. This prevents a routine branch deploy from\nturning into an unreviewed production-data export.\n\n## Deploy a preview\n\nLink the directory to its production project once, then name the preview:\n\n```sh\nclank preview deploy feature-auth\nclank preview deploy pull-482 --ttl=48\nclank preview list\n```\n\n`--ttl` is an integer number of hours. The hosted defaults are seven days with a maximum of 30\ndays. Deploying the same normalized name again reuses its isolated environment, publishes a new\natomic release, and refreshes its expiration. It never changes `.clank/project.json`, so the\ndirectory remains linked to production.\n\nFor agents and CI:\n\n```sh\nclank preview deploy pull-482 --ttl=24 --json\n```\n\nThe `clank-preview-result/1` document includes the preview ID, normalized name, parent project,\nexpiration, release digest, URL, and build/upload timing.\n\n## Remove a preview\n\nExpiration removes the runtime and all platform-managed resources. Remove it sooner with:\n\n```sh\nclank preview remove feature-auth \\\n  --confirm=\"delete-preview feature-auth\" \\\n  --acknowledge-data-loss\n```\n\nThe acknowledgement is deliberate because preview databases can contain useful test data. A\nproduction project cannot be deleted while it still owns previews; remove or let them expire\nfirst.\n\n## Control-plane UI and API\n\nThe project's **Previews** page shows the active environment, URL, runtime status, current release,\nand expiration. Authorized members can remove a preview there. Creation remains a CLI-first\noperation so the exact local build and artifact digest are preserved.\n\nThe authenticated API is:\n\n| Method | Endpoint | Purpose |\n| --- | --- | --- |\n| `GET` | `/api/projects/:parentId/previews` | List unexpired previews and the effective TTL/isolation policy |\n| `POST` | `/api/projects/:parentId/previews` | Create or refresh `{ \"name\": string, \"ttlHours\"?: integer }` |\n| `DELETE` | `/api/projects/:parentId/previews/:previewId` | Permanently remove a preview after exact confirmation |\n\nCreating, refreshing, and deleting previews requires deploy-capable workspace access and an\naccount session or account CLI token. A project-scoped token cannot manage previews because it\nwould not automatically gain authority over the newly isolated child project. Deploying a release\nto an existing preview uses the ordinary release API and authorization rules.\n\n## Isolation and limits\n\n- Preview and production database paths live under different project IDs.\n- Production secrets are not inherited. Set preview secrets explicitly if the app needs them.\n- Migrations run against only the preview database.\n- Realtime connections, MCP/OAuth endpoints, durable jobs, and logs belong to the preview URL.\n- Preview projects are hidden from the top-level production project list and appear under their\n  parent.\n- Every preview counts toward both account and workspace project limits. This prevents previews\n  from bypassing compute, port, storage, and retention limits.\n- Nested previews are rejected.\n- Names are unique within one parent project. The public hostname also carries a random project-ID\n  suffix, so deleting and recreating a name does not accidentally address an older environment.\n\n## Self-hosting policy\n\nProgrammatic control planes can tune preview retention:\n\n```ts\nconst platform = await openPlatform({\n  // ...\n  previews: {\n    defaultTtlMs: 3 * 24 * 60 * 60_000,\n    maxTtlMs: 14 * 24 * 60 * 60_000,\n    cleanupIntervalMs: 60_000,\n  },\n});\n```\n\nCleanup runs at startup before application recovery and then in bounded background batches.\nExpired previews are not restarted. Deletion uses the same path containment, encrypted-backup\nremoval, remote-artifact cleanup, token revocation, process shutdown, and audit path as an explicit\nproject deletion. Set `cleanupIntervalMs: false` only when an external operator owns expiration.\n\n## CI naming\n\nUse stable, low-cardinality names such as `pull-482` or `branch-auth-refresh`. Always run the remove\ncommand when a pull request closes; TTL cleanup is the safety net for interrupted workflows. Do not\nput secrets, email addresses, commit messages, or untrusted free-form text in a preview name.\n"}