# Self-hosting Clank Deploy
Clank Deploy is one Node control-plane process plus one supervised process or container per active project.
## Requirements
- Node 22.16+;
- persistent local storage;
- HTTPS proxy outside loopback;
- Docker for mutually untrusted deployers;
- external master key and off-host backups for production.
## Environment
| Variable | Default | Purpose |
| --- | --- | --- |
| `PORT` | `4200` | Control-plane port |
| `HOST` | `127.0.0.1` | Listener address |
| `CLANK_PLATFORM_URL` | loopback URL | Exact public console origin |
| `CLANK_PLATFORM_DATA` | `.clank-platform` | Persistent root |
| `CLANK_PLATFORM_MASTER_KEY` | generated file | Base64/base64url 32-byte key |
| `CLANK_SIGNUP` | `bootstrap` | `bootstrap`, `public`, or `disabled` |
| `CLANK_RUNNER` | `process` | `process` or `docker` |
| `CLANK_DOCKER_IMAGE` | Node image | Pin by digest in production |
| `CLANK_APP_MEMORY` | `512m` | Container memory |
| `CLANK_APP_CPUS` | `1` | Container CPUs |
| `CLANK_APP_PIDS` | `128` | Container PID limit |
| `CLANK_APP_PORT_START` | `4300` | Port-range start |
| `CLANK_APP_PORT_END` | `4999` | Port-range end |
| `CLANK_APP_URL_TEMPLATE` | loopback with `{port}` | Public app URL pattern |
| `CLANK_MAX_ARTIFACT_BYTES` | `104857600` | Artifact limit |
| `CLANK_INGRESS` | inferred from base domain | Enable managed exact-host ingress |
| `CLANK_INGRESS_BASE_DOMAIN` | none | Built-in `slug.` site namespace |
| `CLANK_CUSTOM_DOMAIN_TARGET` | base domain | CNAME target displayed to customers |
| `CLANK_CUSTOM_DOMAIN_ADDRESSES` | none | Comma-separated edge A/AAAA values accepted for apex routing |
| `CLANK_TLS_ASK_TOKEN` | none | Secret for the private Caddy certificate permission check |
| `CLANK_INGRESS_MAX_BODY_BYTES` | `26214400` | Per-request managed-ingress body limit |
| `CLANK_DOMAIN_RECHECK_INTERVAL_MS` | `300000` | Background custom-domain routing interval; `0` disables it |
| `CLANK_DOMAIN_RECHECK_BATCH_SIZE` | `25` | Maximum domains durably claimed by one routing pass |
| `CLANK_DOMAIN_RECHECK_TIMEOUT_MS` | `10000` | Per-domain DNS lookup deadline |
| `CLANK_BACKUP_INTERVAL_MS` | `86400000` | Verified encrypted-backup cadence; `0` disables automatic runs |
| `CLANK_BACKUP_BATCH_SIZE` | `5` | Maximum projects durably claimed by one backup pass |
| `CLANK_BACKUP_MAX_COUNT` | `30` | Maximum retained backups per project |
| `CLANK_BACKUP_MAX_AGE_MS` | `7776000000` | Maximum retained backup age |
| `CLANK_BACKUP_MAX_DATABASE_BYTES` | `10737418240` | Maximum source database size accepted by backup creation |
| `CLANK_MAX_ORGANIZATIONS_PER_ACCOUNT` | `5` | Transactionally enforced account organization limit |
| `CLANK_MAX_PROJECTS_PER_ACCOUNT` | `10` | Transactionally enforced account-wide site limit |
| `CLANK_MAX_PROJECTS_PER_ORGANIZATION` | `10` | Transactionally enforced site limit |
| `CLANK_MAX_DOMAINS_PER_PROJECT` | `5` | Transactionally enforced custom-domain limit |
| `CLANK_METRICS_RETENTION_DAYS` | `30` | Ingress metric retention, 1–365 days |
| `CLANK_MAX_RELEASES_PER_PROJECT` | `50` | Retained runtime-artifact count per site |
| `CLANK_MAX_RELEASE_STORAGE_BYTES_PER_PROJECT` | `21474836480` | Uncompressed release files plus pre-deploy snapshots retained per site |
| `CLANK_ALLOW_UNSAFE_MIGRATIONS` | `0` | Operator approval for unrestricted SQL |
| `ALLOWED_HOSTS` | loopback | Exact host allowlist |
| `TRUST_PROXY` | `0` | Trust forwarded client/protocol |
`bootstrap` permits one initial account and then closes ordinary registration. Its SQLite claim is shared by control-plane processes using the same data directory. `disabled` blocks ordinary registration immediately. In both modes, an owner/admin-issued invitation can still create only its bound email account through **Use invitation**; revoke outstanding invitations before disabling all intended onboarding.
## Production start
```sh
export CLANK_PLATFORM_URL=https://deploy.example.com
export CLANK_PLATFORM_DATA=/var/lib/clank
export CLANK_PLATFORM_MASTER_KEY="$(your-secret-manager read clank-master-key)"
export CLANK_RUNNER=docker
export CLANK_DOCKER_IMAGE=node@sha256:
export CLANK_APP_URL_TEMPLATE='https://{slug}.apps.example.com'
export CLANK_INGRESS=1
export CLANK_INGRESS_BASE_DOMAIN=apps.example.com
export CLANK_CUSTOM_DOMAIN_TARGET=edge.apps.example.com
export CLANK_CUSTOM_DOMAIN_ADDRESSES=192.0.2.10,2001:db8::10
export CLANK_TLS_ASK_TOKEN="$(your-secret-manager read clank-tls-ask-token)"
export HOST=127.0.0.1
export PORT=4200
export ALLOWED_HOSTS=deploy.example.com,127.0.0.1,localhost
export TRUST_PROXY=1
clank-platform
```
Proxy the console and application hosts to port 4200. Clank performs exact-host project routing; the edge performs public DNS, TLS, WAF/rate limiting, and DDoS controls. The recommended Caddy On-Demand TLS configuration and DNS records are in [Deployment dashboard, quotas, and domains](platform-dashboard.md).
Use `/livez` only to determine whether the process can answer HTTP. Use `/healthz` or `/readyz` for
control-host readiness. Hosted load balancers that send a different `Host` header should use
`/_clank/readyz`, which is evaluated before application-host ingress. These readiness endpoints
execute a control-database probe and return `503` when the platform cannot safely accept work.
`SIGINT` and `SIGTERM` stop new HTTP work, drain an active scheduled backup, close supervised
applications and platform storage, and fail the process if shutdown cannot finish within 30 seconds.
## Tailscale
```sh
CLANK_PLATFORM_URL=https://host.tailnet-name.ts.net:8447 \
HOST=127.0.0.1 PORT=4200 TRUST_PROXY=1 \
ALLOWED_HOSTS=host.tailnet-name.ts.net,localhost,127.0.0.1 \
clank-platform
tailscale serve --https=8447 http://127.0.0.1:4200
```
Expose app ports separately or place a wildcard-capable proxy in front.
## Railway
The checked-in production image, health/restart policy, persistent-volume topology, DNS setup, and
operator runbook are in [Railway production deployment](railway.md).
## Storage
```text
control.sqlite
master.key
projects//
data/app.sqlite
releases//
backups/.sqlite
recovery//
database.enc
manifest.json
```
Use a local filesystem with correct SQLite locking/rename semantics. The platform sets umask `0077`.
Clank automatically creates authenticated, AES-256-GCM encrypted recovery points under `recovery/`. Durable control-database claims prevent duplicate scheduled work when multiple control-plane processes share the store. Release count and byte ceilings separately bound retained extracted runtime files and pre-deploy rollback snapshots. Back up the control database, completed recovery directories, recoverable artifacts/source, and master key through separate paths. Pre-release snapshots remain rollback material, not part of the recovery retention policy. See [Backup and disaster recovery](recovery.md).
Permanent site deletion removes the complete matching `projects//` tree after symlink-safe validation, then removes its control rows and revokes project tokens. It does not reach copied/off-host backups, external databases, artifact mirrors, or Caddy certificate storage. Include those systems in tenant-retention and erasure runbooks, and preserve the control database if deletion audit history must remain available.
Audit rows live in `control.sqlite`, retain organization attribution after project removal, and upgrade in place from earlier schemas. The public API does not mutate them, but SQLite administrators can. Export them to a separate append-only audit system when control-plane operator tampering or longer retention is in scope.
Authentication and CLI device-start rate-limit windows also live in `control.sqlite`. Their client/account keys are HMAC pseudonyms under the platform master key, expire automatically, and remain effective when requests move between control-plane processes or the process restarts. The table is bounded, so keep the console behind production edge rate limiting and DDoS controls.
## Upgrades
1. Back up data and key.
2. Stop new deploys and the platform.
3. Install and verify the new Clank build.
4. Start the selected active supervisor/worker topology.
5. Verify browser login, CLI login, organization and scoped-token access, project status, ingress/domain state, app health, test deploy, backup verification, rollback, and a disposable-site deletion.
Durable distributed locks, authenticated nodes, desired generations, operations/fencing, wildcard base-domain routing, ownership and routing verification, Caddy certificate eligibility, ingress metrics, enforced account/organization/site/domain limits, organization RBAC, scheduled encrypted backups, and external database drivers are implemented. The included child-process supervisor remains single-leader and artifacts/backups are local by default; a hosted multi-region service still needs leader/remote-runner integration, external object storage, globally transactional control storage, shared metric storage, and a multi-region edge service.