TurboPanel Docs
Deployment

Control Plane Configuration Reference

What a self-hosted control plane (the Deno control plane, run by the turbopanel-instance unit from /opt/turbopanel/bin/turbopanel) reads from its environment at boot, in one place. On a managed install the daemon's instance-launch role writes these into the unit and /etc/turbopanel/instance/runtime.env; you only set them by hand on a bespoke host. TurboPanel High Availability (Cloudflare Workers) reads the subset marked Workers too from Wrangler vars and secrets.

Three behaviours matter more than any single value:

  • Two variables throw at boot when missing: TURBOPANEL_DATABASE_URL and TURBOPANEL_SECRET / TURBOPANEL_SECRETS. Everything else has a default.
  • Redis is required but not checked up front: the daemon cell, the query cache and the auth rate limiters open TURBOPANEL_REDIS_SOCKET lazily; a missing socket surfaces as failures on the first request that needs it, not as a refusal to start.
  • The email queue degrades silently: with TURBOPANEL_AMQP_URL unset the control plane probes the dev broker, and with no broker reachable it logs one line and uses a no-op queue — sign-ups, invitations and verifications then produce no email at all.

Required

VariableRead byBehaviour
TURBOPANEL_DATABASE_URLboot, migrate verbPostgres URL. Socket form on managed installs: postgresql://<user>:<pass>@/<db>?host=/var/run/turbopanel/postgres. Throws TURBOPANEL_DATABASE_URL is required when unset. Managed installs reach Postgres over that Unix socket path; TCP Postgres is a source-mode development option.
TURBOPANEL_SECRET or TURBOPANEL_SECRETSbootThe root secret keyring (session signing, OTP and TOTP verifiers, data-encryption key derivation). TURBOPANEL_SECRETS is the versioned form 2:<new secret>,1:<old secret> written by instance-launch to /etc/turbopanel/instance/.instance_secrets. The first entry is the current key: it signs and encrypts, and the later entries only verify and decrypt. List the highest version first. An out-of-order list is used as written, with a warning in the log, so putting the old key first keeps signing with it. TURBOPANEL_SECRET is a single value. Throws when neither is set. Losing this keyring loses every encrypted row — see Security.

Runtime services

VariableDefaultBehaviour
TURBOPANEL_REDIS_SOCKET/run/turbopanel/redis.sockRedis over a Unix socket: daemon cell (presence, outbox, request correlation), query cache, auth and daemon rate limiters. Opened lazily; not validated at boot.
TURBOPANEL_AMQP_URLprobe amqp://guest:guest@localhost:19828, else no-opRabbitMQ for the email queue (the control plane's in-process email consumer drains it). Empty string means "no queue" explicitly; unset probes the dev broker URL and falls back to a no-op queue with one log line. Managed installs set it from the rabbitmq role.
TURBOPANEL_UPDATE_CHANNELreleaseThe channel this control plane resolves daemon updates on (trunk, canary, rc, release; edge reserved). Every queued update carries it. An unknown value is a boot error, like the daemon's. Managed installs write the channel they installed from.
TURBOPANEL_AUTO_FAILOVERonWhether this control plane may fence and promote a managed database's failover replica on its own when a primary is reported dead. on or off (also true/false, 1/0), read when each report arrives, so no restart is needed after a change. Any other value counts as off. Unset is on, except when TURBOPANEL_ENVIRONMENT is staging or live. Off records a blocked recovery with auto_failover_disabled and queues nothing; manual switchover and disaster recovery are unaffected. See Automatic failover. Workers too.
TURBOPANEL_FIREWALL_APPLY_SERVERSemptyUp to three server UUIDs, separated by commas, that may be sent firewall rules to load (not just a preview) when an owner or manager also sets their firewall mode to Managed. Empty, or any *, all or malformed entry, means no server. Ignored when TURBOPANEL_ENVIRONMENT is live. See Turn enforcement on for one server.
TURBOPANEL_INSTANCE_SERVICEturbopanel-instanceThe systemd unit the developer surface restarts after Upgrade System. Set it only when the unit has a non-standard name.

Paths (FHS layout)

VariableDefaultPurpose
TURBOPANEL_STATE_DIR/var/lib/turbopanelDurable state: the platform CA (tls/), metrics, execution logs, and in compiled mode the server leaf certificate (tls/certs/).
TURBOPANEL_CONFIG_DIR/etc/turbopanelProtected config (instance/runtime.env, the secret keyring, public URLs).
TURBOPANEL_RUN_DIR/run/turbopanelRuntime sockets. TURBOPANEL_SOCKET_DIR is the older name for the same value; TURBOPANEL_SOCKET overrides the control plane listen socket path itself (<run dir>/instance.sock).
TURBOPANEL_LOG_DIR/var/log/turbopanelFile logs.
TURBOPANEL_METRICS_DIR<state>/metricsThe embedded DuckDB metrics store.
TURBOPANEL_EXECUTION_LOG_DIR<state>/execution-logsCommand and deploy logs when TURBOPANEL_EXECUTION_LOG_DRIVER is file (the default).
LD_LIBRARY_PATH (unit)/opt/turbopanel/libWhere the compiled instance finds libduckdb.so. The turbopanel-instance unit sets it; there is no TURBOPANEL_* variable for this path.
TURBOPANEL_UI_ROOT/opt/turbopanel/share/uiThe static web export Caddy serves (TURBOPANEL_UI_MODE=static); co-located development points it at a checkout.

Public identity and TLS

VariableDefaultBehaviour
TURBOPANEL_PUBLIC_URLSderivedComma-separated projection of the Platform CA hostnames. Admin → Access → Hostnames is the editor; this setting stays in sync so certificate SANs, webhook reachability, and the install command keep reading it. Unset, the control plane derives an origin from the host's interfaces. Every stored name is served at https://<host>:8443.
TURBOPANEL_BASE_URLderived from the requestThe origin used in outbound links (verification and invitation emails, offline-sweep notices). Unset, it is resolved per request; behind a Unix socket the control plane guards against the literal null origin.
TURBOPANEL_TLS_PUBLICunset1 / true when any hostname is Let's Encrypt, or when the operator marks TLS public. GET /api/daemon/v1/instance/ca then 404s for a name that presents a Let's Encrypt or uploaded leaf. An unlisted name on :8443 still receives the Platform CA bundle. Install commands omit --insecure-tls for a publicly trusted leaf.
TURBOPANEL_INSTANCE_ACME__CONTACT_EMAILunsetContact email for this control plane's Let's Encrypt account. Env wins over Admin → Access → Certificates. Independent of every organization's Let's Encrypt opt-in.
TURBOPANEL_INSTANCE_ACME__TOS_ACCEPTEDfalseTerms acceptance for that account. Same env-wins rule.
TURBOPANEL_INSTANCE_ACME__DIRECTORY_URLLet's Encrypt production directoryACME directory URL. Same env-wins rule.
TURBOPANEL_INSTANCE_ACME__USE_STAGINGfalsetrue selects the Let's Encrypt staging directory. Same env-wins rule.
TURBOPANEL_TLS_CA, TURBOPANEL_TLS_CA_KEY, TURBOPANEL_TLS_CA_BUNDLE<state>/tls/ca.crt, ca.key, ca-bundle.pemThe Platform CA the generate-self-signed-cert verb maintains and daemons trust.
TURBOPANEL_TLS_CERTS_DIR<state>/tls/certs (compiled) / <checkout>/certs (source)Where the Platform CA server leaf is written. Let's Encrypt and uploaded leaves for individual hostnames are files beside it.
CADDY_PORT8443Development only. The port the development Caddy unit (and the docs site's dev unit) listens on and links to. The control plane does not read it: install origins always use :8443, and a managed Caddyfile binds :8443 whatever this is set to.
CADDY_TLS_CERT, CADDY_TLS_KEY<certs dir>/self-signed.crt, .keyLeaf paths the development Caddyfile reads. A managed Caddyfile bakes platform-ca.* plus per-hostname files at render time. The Caddy unit sets these from TURBOPANEL_TLS_CERTS_DIR.
TURBOPANEL_REVISIONstamped at buildThe exact source commit /api/health reports for AGPL Corresponding Source. A release build carries its commit already; source-mode installs set it from git rev-parse HEAD.

The four TURBOPANEL_INSTANCE_ACME__* keys are this control plane's settings. Environment wins over Admin → Access → Certificates, the same way other Admin settings work, and an env-sourced key is read-only there. They have no effect on an organization's Allow Let's Encrypt certificates, and that organization toggle has no effect on them. Hostname rows (source, uploaded certificate, last issuance error) live in the database, edited from Admin → Access → Hostnames, not in these variables.

Sign-up and providers

VariableDefaultBehaviour
TURBOPANEL_IS_SIGNUP_ENABLEDunsetForces sign-up open (1 / true) or closed (0 / false), overriding the admin setting. Rarely set on a self-hosted host; the install wizard creates the first account either way. Workers too.
TURBOPANEL_AUTH_PROVIDERS__GITHUB_CLIENT_ID, …__GITHUB_CLIENT_SECRET, …__GOOGLE_CLIENT_ID, …__GOOGLE_CLIENT_SECRETunsetSign in with GitHub / Google. Env wins over the SYSTEM_AUTH_PROVIDERS setting row. Workers too (as secrets).

Email

Every SYSTEM_EMAIL setting has an environment form TURBOPANEL_SYSTEM_EMAIL__<KEY> that wins over the value stored in the admin settings: PROVIDER (smtp default, mailgun, mailpit-api on Workers only, mailpit-smtp on Deno only), FROM (noreply@turbopanel.local), SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, MAILPIT_API_URL (Workers mailpit-api — full HTTP base URL), MAILPIT_SMTP_PORT (Deno mailpit-smtp — co-located Mailpit SMTP, default 1025), MAILGUN_API_KEY, MAILGUN_DOMAIN, MAILGUN_REGION, RATE_LIMIT_PER_MINUTE, RATE_LIMIT_BURST, QUEUE_PREFETCH. Secrets stored in the database are tpsecret envelopes sealed with the root keyring. On self-hosted Deno the email consumer runs inside the control plane process and reads the same variables; on Workers the mailpit-api and mailgun providers send directly from the request path.

Metrics and logs

VariableDefaultBehaviour
TURBOPANEL_SERVER_METRICS_RETENTION_DAYS90How long the DuckDB metrics store keeps samples. Workers too (Analytics Engine).
TURBOPANEL_SERVER_METRICS_DUCKDB_THREADS, TURBOPANEL_SERVER_METRICS_DUCKDB_MEMORY_LIMIT2, 128 (MiB)Resource caps for the embedded store.
TURBOPANEL_EXECUTION_LOG_DRIVERfilefile (the default) or s3; any other value falls back to files. With s3: TURBOPANEL_EXECUTION_LOG_S3_BUCKET, _ENDPOINT, _REGION, _ACCESS_KEY_ID, _SECRET_ACCESS_KEY, _FORCE_PATH_STYLE.
TURBOPANEL_EXECUTION_LOG_RETENTION_DAYS90Retention for command and deploy logs. Workers too.
TURBOPANEL_DAEMON_WS_INBOUND_LIMIT, TURBOPANEL_DAEMON_WS_INBOUND_WINDOW_MSbuilt-inPer-daemon inbound WebSocket rate limit. Workers too.
TURBOPANEL_ARGON2ID_MEMORY_KIB, TURBOPANEL_ARGON2ID_TIME_COSTbuilt-inPassword-hash work factor; the hasher is verified available at boot.
TURBOPANEL_UI_CORS_ORIGINSunsetExtra browser origins (a docs site, a Metro dev server) allowed read-only cross-origin API access. Workers too.
TURBOPANEL_DAEMON_DEBUGunset1 / true turns on debug logging.
TURBOPANEL_UPGRADE_STEP_RETENTION_DAYS14How long a finished successful upgrade step (done or skipped) is kept. Blank, non-integer, or out of range (not 1–3650) falls back to 14. Failed steps (failed, rolled_back, needs_attention) stay 90 days. Terminal upgrade runs stay 365 days, and the newest 50 are kept past that. High Availability too.
TURBOPANEL_UPDATE_HEALTH_TIMEOUT_SECONDS600How long this host's daemon waits for the new control plane to pass GET /api/health after an update restarts it, before it restores the previous build. Whole seconds, 30 to 3600; anything else keeps 600. Set it in /etc/turbopanel/daemon.env and restart turbopaneld.
TURBOPANEL_UPGRADE_VERIFY_TIMEOUT_MINUTES20How long a control-plane update step may stay Restarting or Verifying with no word from the daemon before it becomes needs_attention with verify_timeout. Whole minutes, 5 to 180; anything else keeps 20. Keep it longer than TURBOPANEL_UPDATE_HEALTH_TIMEOUT_SECONDS on the host.

Development only

Read only when the developer surface is on (TURBOPANEL_DEV_SURFACE=1, written solely by the managed turbopanel-instance unit for co-located Deno source-mode development; TURBOPANEL_UI_MODE only selects Expo vs. static serving), never on a managed install: TURBOPANEL_DEV_USER, TURBOPANEL_DEV_HOST_AUTH, TURBOPANEL_TRUNK_BRANCH, TURBOPANEL_DAEMON_REPO / TURBOPANEL_UI_REPO (optional checkout overrides; defaults derive from TURBOPANEL_DEV_ROOT), TURBOPANEL_UI_MODE=dev, TURBOPANEL_DRIZZLE_STUDIO_HOST / _PORT, TURBOPANEL_NODE, TURBOPANEL_DENO, TURBOPANEL_RUNTIMES_DIR, TURBOPANEL_USER, CADDY_PORT. See Local development.

Workers only

HYPERDRIVE / HYPERDRIVE_CACHED (Hyperdrive bindings), TURBOPANEL_COMMAND_QUEUE (Cloudflare Queue binding), TURBOPANEL_TLS_CA_PEM_B64 (the platform CA served to daemons), TURBOPANEL_ANALYTICS_ENGINE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, TURBOPANEL_PROJECT_ID, and the Stripe billing secrets. These are configured in wrangler.jsonc and the Cloudflare dashboard; the self-hosted binary never reads them.

Stripe webhook signing secret (High Availability). A Stripe Dashboard webhook endpoint has its own whsec_… signing secret, distinct from the one the Stripe CLI prints. Set it with wrangler secret put TURBOPANEL_STRIPE_WEBHOOK_SIGNING_SECRET (a secret, never a vars entry); until it is set, /webhook/stripe answers 503 stripe_webhook_not_configured. A self-hosted control plane has no billing surface and no Stripe webhook, so there is nothing to set there.

The install-time verbs

The compiled instance binary carries three verbs the installer runs before the unit exists, each reading the variables above: turbopanel migrate (TURBOPANEL_DATABASE_URL), generate-secret (none), and generate-self-signed-cert (TURBOPANEL_STATE_DIR, TURBOPANEL_TLS_*, TURBOPANEL_PUBLIC_URLS, TURBOPANEL_TLS_EXTRA_SANS). None of them loads the metrics store, so they run without libduckdb.so on the library path.

Edit on GitHub

Last updated on

On this page