{"protocol":"clank-doc/1","frameworkVersion":"0.8.0","slug":"data-plane","title":"Managed ingress and external data","description":"The data plane module separates public routing and provider managed SQL from application processes.","group":{"id":"full-stack","title":"Full stack"},"url":"https://docs.clank.run/docs/data-plane","source":"docs/data-plane.md","headings":["Managed ingress","Custom domains","External PostgreSQL over HTTPS","Provisioning"],"tableOfContents":[{"id":"managed-ingress","title":"Managed ingress","level":2},{"id":"custom-domains","title":"Custom domains","level":2},{"id":"external-postgresql-over-https","title":"External PostgreSQL over HTTPS","level":2},{"id":"provisioning","title":"Provisioning","level":2}],"markdown":"# Managed ingress and external data\n\nThe data-plane module separates public routing and provider-managed SQL from application processes.\n\n## Managed ingress\n\n`createManagedIngress` routes exact hostnames to fixed upstream origins. Route configuration is trusted control-plane data; request paths never select an arbitrary upstream.\n\nThe proxy:\n\n- matches only active, uniquely assigned hosts;\n- allows loopback upstreams by default and requires an explicit allowlist for remote upstream hosts;\n- strips fixed hop-by-hop headers, `Connection`-nominated headers, and private server headers;\n- sets controlled forwarding and project headers;\n- bounds request bodies before forwarding;\n- applies timeouts and safe-method retries for network failure or transient upstream 5xx responses;\n- streams response bodies, including SSE;\n- tracks per-route circuit failures; and\n- exposes active-route health checks.\n- records bounded per-project ingress metrics; and\n- constructs every target from the trusted upstream origin before assigning the request path, so a scheme-relative path cannot replace the upstream host.\n\nThe Clank platform can enable ingress directly:\n\n```ts\nawait openPlatform({\n  publicUrl: \"https://console.clank.example\",\n  dataDirectory: \"/srv/clank\",\n  ingress: {\n    baseDomain: \"apps.clank.example\",\n  },\n});\n```\n\nProjects are then available at `https://<slug>.apps.clank.example`. TLS should terminate at the edge proxy or load balancer in front of the Clank control/data-plane process.\n\n## Custom domains\n\n```sh\nclank domain add tasks.customer.example\n# publish the displayed TXT record\nclank domain verify <domain-id>\nclank domain list\n```\n\nCustom hosts do not enter the ingress route set until the exact `_clank.<hostname>` TXT challenge is present **and** the hostname resolves to the configured CNAME target or edge A/AAAA address. Challenges are project bound, expiring, and unique by hostname. Pending and verified hostnames cannot be claimed by another project.\n\nOwnership, routing, and certificate status are separate. Clank provides a constant-time, token-protected permission endpoint for Caddy On-Demand TLS; Caddy or another edge remains responsible for ACME, certificate/key storage, and renewal. See [Deployment dashboard, quotas, and domains](platform-dashboard.md) for the DNS lifecycle and production Caddy configuration.\n\nThe deployment platform refreshes routing in bounded background batches. Expiring SQLite claims prevent duplicate work across control-plane processes, per-domain deadlines prevent a slow resolver from stopping the pass, and manual checks fence out stale automatic results. Ownership verification stays explicit.\n\n## External PostgreSQL over HTTPS\n\n`createHttpPostgresDriver` is the zero-package external SQL boundary:\n\n```ts\nconst postgres = createHttpPostgresDriver({\n  url: process.env.DATABASE_HTTP_URL!,\n  token: process.env.DATABASE_HTTP_TOKEN!,\n});\n\nconst result = await postgres.query({\n  text: \"SELECT id, title FROM tasks WHERE owner_id = $1\",\n  parameters: [user.id],\n});\n```\n\nThe driver sends structured statements and JSON parameters to an HTTPS database gateway. It never interpolates parameters, rejects redirects, bounds statement counts and response bytes, applies a timeout, and validates every result envelope.\n\n`applyExternalMigrations` creates the same immutable `clank_migrations` ledger and sends all pending migration and ledger statements in one provider transaction. Edited or missing applied migrations stop deployment.\n\nThe framework's built-in auth, live document database, and revision journal remain SQLite-first. External PostgreSQL is an explicit service driver for workloads that require horizontal SQL. An application must choose which records are authoritative; it must not dual-write SQLite and PostgreSQL without an outbox or another explicit consistency protocol.\n\n## Provisioning\n\n`createHttpDatabaseProvisioner` defines the control-plane boundary for creating and destroying project databases:\n\n- provisioning requires project, region, engine, and idempotency key;\n- provider credentials stay in platform secrets;\n- returned connection material is validated before storage;\n- destruction requires `destroy <database-id>`; and\n- application code receives only its own binding.\n\nOpen-source operators can implement this HTTP contract against their preferred PostgreSQL provider without changing framework application code.\n"}