{"protocol":"clank-doc/1","frameworkVersion":"0.9.4","slug":"runner-fleet","title":"Deployment runner fleet","description":"Clank can expose a closed, authenticated coordinator for deployment runners. The control plane administrator can enroll named nodes, inspect heartbeat and capacity, drain them before maintenance, reactivate them, or revoke their credentials","group":{"id":"deploy","title":"Deploy and operate"},"url":"https://docs.clank.run/docs/runner-fleet","source":"docs/runner-fleet.md","headings":["Enable the coordinator","Enroll one node","Operate and upgrade nodes","Legacy shared enrollment","Cost and topology guidance"],"tableOfContents":[{"id":"enable-the-coordinator","title":"Enable the coordinator","level":2},{"id":"enroll-one-node","title":"Enroll one node","level":2},{"id":"operate-and-upgrade-nodes","title":"Operate and upgrade nodes","level":2},{"id":"legacy-shared-enrollment","title":"Legacy shared enrollment","level":2},{"id":"cost-and-topology-guidance","title":"Cost and topology guidance","level":2}],"markdown":"# Deployment runner fleet\n\nClank can expose a closed, authenticated coordinator for deployment runners. The control-plane\nadministrator can enroll named nodes, inspect heartbeat and capacity, drain them before\nmaintenance, reactivate them, or revoke their credentials from `/admin`.\n\nThis is a fleet-control foundation, not automatic remote application placement. In the current\nrelease, ordinary hosted projects still run under the built-in supervisor on the control-plane\nhost. Enrollment alone does not grant project secrets, SQLite databases, ingress traffic, backups,\nor rollback data. A separate exact-lease runtime-capsule protocol can carry application placement\ninputs to a trusted provider, but the built-in platform keeps it unselected until the complete\ndata, backup/restore, rollback, and ingress lifecycle is implemented. See\n[Remote runtime placement](runtime-placement.md).\n\n## Enable the coordinator\n\nThe default remains closed and costs nothing:\n\n```sh\nCLANK_RUNNER_COORDINATOR=0\n```\n\nSet the explicit switch on a self-hosted installation that is ready to operate runner nodes:\n\n```sh\nCLANK_RUNNER_COORDINATOR=1\n```\n\nThis opens only the versioned `/api/runner/v1` coordinator routes. It does not create another\nservice, VM, volume, database, bucket, or Railway resource. It also retains an additional exact\ncompressed upload for each new release so an authorized operation can fetch it later. That copy is\nincluded in project release quotas.\n\nAfter restarting, sign in with an address in `CLANK_PLATFORM_ADMIN_EMAILS` and open `/admin`.\nThe **Deployment runner fleet** panel reports whether the coordinator is enabled. Platform fleet\nadministration is browser-session-only, CSRF-protected, unavailable during support impersonation,\nand never accepts CLI, project, application, or MCP bearer tokens.\n\n## Enroll one node\n\nIn the fleet panel:\n\n1. Enter the exact node ID and region the runner will advertise.\n2. Choose a 15-minute, 1-hour, or 24-hour lifetime.\n3. Create the enrollment and copy the `clnke_...` value immediately.\n\nThe plaintext value is returned once. Clank stores only its SHA-256 digest. It is bound to the\nexact node ID and region, expires automatically, is transactionally reserved during registration,\nand can be consumed once. A malformed registration rolls the reservation back; concurrent\nredemption has one winner; a completed token cannot be replayed. Creation, consumption, revocation,\ndraining, activation, and node revocation are audited without recording either the enrollment or\nnode credential.\n\nConfigure the target host:\n\n```sh\nexport CLANK_CONTROL_URL=https://clank.example.com\nexport CLANK_RUNNER_NODE_ID=runner-us-central-01\nexport CLANK_RUNNER_REGION=us-central\nexport CLANK_RUNNER_REGISTRATION_TOKEN='clnke_value-copied-once'\nexport CLANK_RUNNER_CREDENTIALS=/var/lib/clank-runner/credentials.json\n\nexport CLANK_PROVIDER_URL=https://runtime.internal.example\nexport CLANK_PROVIDER_TOKEN='separate-provider-bridge-secret'\n\nclank-runner\n```\n\nBefore starting the long-running process, validate the environment without consuming a fresh\nenrollment:\n\n```sh\nclank-runner --check\nclank-runner --check --json\n```\n\nWhen a saved node credential exists, the check authenticates it with the coordinator. On a first\nstart it validates the local configuration and reports that enrollment is ready without redeeming\nthe one-time token. The provider bridge has no generic health call, so the check validates its URL\nand token shape but does not issue a reconciliation. Neither secret is printed.\n\n`clank-runner` exchanges the enrollment for a separate `clnka_...` node credential and writes it\natomically to an owner-only `0600` file. Remove `CLANK_RUNNER_REGISTRATION_TOKEN` from the\nlong-running service after the first successful start. Normal restarts use the saved node\ncredential. Keep the credentials path on a private persistent disk and use one file per runner\nprocess.\n\nThe provider bridge token is a different secret with a different authority. Never reuse the\nplatform master key, browser session, CLI token, project token, application secret, enrollment\ntoken, or node credential.\n\n## Operate and upgrade nodes\n\nThe fleet panel shows:\n\n- active, draining, and offline nodes;\n- advertised region, capacity, and labels;\n- last heartbeat and current credential lease;\n- assigned placement count; and\n- queued, leased, retrying, and failed coordination operations.\n\nTo upgrade a runner:\n\n1. Select **Drain**. The node stops receiving new placement or claims but keeps its existing\n   placement record.\n2. Let current leased work finish or expire.\n3. Stop and upgrade the process.\n4. Start it with the same credentials file.\n5. Select **Reactivate** if the process did not reactivate itself.\n\n**Revoke** rotates the stored credential digest immediately, marks the node offline, and\nreassigns eligible desired placement. The old runner can no longer authenticate, renew, complete,\nor observe work. A leased operation stays fenced until its lease expires, then becomes retryable on\nthe replacement node with a higher fence. To reuse a revoked node ID, create and consume a new\none-time enrollment.\n\nDraining is graceful maintenance; revocation is credential invalidation. Do not use revocation as\nthe normal shutdown path.\n\n## Legacy shared enrollment\n\n`CLANK_RUNNER_REGISTRATION_TOKEN` on the control plane remains supported for compatibility and\nalso enables the coordinator when `CLANK_RUNNER_COORDINATOR` is unset. It is a shared,\nnon-expiring credential that can rotate any named node and therefore has more authority and more\noperational risk than an administrator-created one-time enrollment.\n\nPrefer managed enrollment for new installations. If legacy automation still needs the shared\nsecret, store it in an operator secret manager, rate-limit the enrollment path at the edge, and\nrotate it after provisioning-system exposure. Never place it in repository files, command history,\nURLs, logs, application configuration, or the runner credential file.\n\n## Cost and topology guidance\n\nKeep the coordinator disabled for a cheap one-service deployment. Enabling it does not make the\ncurrent process runner safe for mutually untrusted public users and does not improve isolation by\nitself. Provision an additional runner host and optional object storage only when the placement\nphase and provider adapter need them.\n\nOn the current Railway single-service profile, leave `CLANK_RUNNER_COORDINATOR` unset and do not add\na service or bucket. A future runner can be another small service on a private network; an\nS3-compatible artifact store is optional and should be added only when artifacts must survive\noutside the control-plane volume.\n\nFor the underlying authentication, leases, artifacts, fencing, and agent loop, see\n[Durable distributed deployment](distributed-deployment.md). For the infrastructure mutation\nboundary and HTTP bridge, see [Deployment provider adapters](provider-adapters.md). For the\ngeneration-bound private traffic hop, see [Provider runtime\ningress](provider-runtime-ingress.md).\n"}