Control Plane Deployment
The TurboPanel instance is the control plane API. It runs as Deno on self-hosted hosts (Unix socket + Caddy) or on Cloudflare Workers for TurboPanel High Availability. The Expo UI is served by Caddy (static export or dev proxy) or Workers assets.
Overview
Purpose
- Centralized API for organizations, servers, auth, and daemon orchestration
- Web UI (Expo web) for operators
- WebSocket hub for remote daemons (
/ws/daemon/v1) - OpenAPI + Scalar at
/api/openapi.jsonand/api/reference
Relationship to daemon
On the control plane host, a co-located daemon installs and updates the instance, Caddy, UI, and Postgres via Ansible. Additional servers run a standalone daemon that connects back over WSS. See Daemon setup.
Co-located daemon
The control plane server includes a co-located daemon that dials the local instance (socket or Caddy). Remote nodes use the CDN installer — not a separate control-plane container image.
Architecture (self-hosted)
Deployment models
| Feature | Self-hosted (Deno) | TurboPanel High Availability |
|---|---|---|
| Runtime | Deno on Unix socket | Cloudflare Workers |
| TLS | Caddy (platform CA) | Cloudflare-managed TLS |
| Database | Local Postgres (socket) | Postgres via Hyperdrive |
| UI | Caddy → Expo dev or static export (/opt/turbopanel/share/ui; dev override ../ui/dist) | Workers assets / separate hosting |
| Daemon on CP host | Co-located (socket mode) | Connects to Workers URL |
| Pricing | Free, unlimited servers | Pay per server |
Installation
Self-hosted (systemd + Ansible)
Current production path
There is no public one-line install script for self-hosted control planes yet. Use the dev console on a Debian/Ubuntu VM.
On Debian/Ubuntu:
curl -fsSL dev.turbopanel.sh | shChoose Start dev stack in the console. This enables co-located dev-instance mode and lets the daemon run instance-dev-install via Ansible. To run the console again later: cd ~/dev && ./console. Services:
| Unit | User | Role |
|---|---|---|
turbopaneld.service | current dev user | Ansible orchestration (daemon from ~/daemon in dev) |
turbopanel-instance.service | current dev user | Deno API on Unix socket |
turbopanel-caddy.service | current dev user | TLS + reverse proxy :8443; dev-only plaintext mirror :8880 when enabled |
turbopanel-ui.service | current dev user | Expo web dev (:8081, dev only) |
A hardened production install uses dedicated service users (tp, tpctrl, tpcaddy) — see Daemon setup.
Logs: journalctl -u turbopanel-instance -u turbopanel-caddy -u turbopanel-ui -u turbopaneld -f
TurboPanel High Availability
From the instance repo:
export TURBOPANEL_DATABASE_URL="postgresql://user:pass@host:5432/dbname"
# or, for CI / dashboard deploy workflows:
# export DATABASE_URL="postgresql://user:pass@host:5432/dbname"
export CLOUDFLARE_ENV=live # or testing — must match a wrangler.jsonc env name
pnpm install
pnpm deploypnpm deploy runs pending migrations (via TURBOPANEL_DATABASE_URL or DATABASE_URL) then wrangler deploy --env $CLOUDFLARE_ENV --minify. The migration step uses Node only (drizzle-kit migrate + post-migration resource-registry repair) — Deno is not required on the deploy host. Configure Hyperdrive, secrets (TURBOPANEL_SECRET / TURBOPANEL_SECRETS), and bindings in wrangler.jsonc before deploy.
Daemon Cell on Workers
TurboPanel High Availability coordination uses a hibernation-safe Durable Object per server
(getByName(serverId)) for live WS presence, outbox delivery, and request
correlation — same /api/daemon/v1/* and /ws/daemon/v1 paths as self-hosted.
Self-hosted uses the Redis cell backend instead. UI status reads come from Postgres
only; the cell is not a polling API. See Daemon cell architecture
and instance/AGENTS.md (Daemon Cell) for cost/hibernation rules agents must not regress.
Contributor local dev
curl -fsSL dev.turbopanel.sh | shUse Start dev stack in the console — see Local development. To run again later: cd ~/dev && ./console.
Configuration
Key environment variables (instance)
| Variable | Purpose |
|---|---|
TURBOPANEL_SECRET / TURBOPANEL_SECRETS | Session signing (required in production) |
TURBOPANEL_DATABASE_URL | Full Postgres connection URL for self-hosted Deno boot (instance-launch) and tooling |
DATABASE_URL | Tooling-only fallback for pnpm migrate / drizzle-kit when TURBOPANEL_DATABASE_URL is unset (common in CI and dashboard deploy) |
CLOUDFLARE_ENV | Wrangler env name for Workers deploy (e.g. live, testing) — required by pnpm deploy |
TURBOPANEL_SOCKET / TURBOPANEL_SOCKET_DIR | Unix socket path overrides |
TURBOPANEL_UI_MODE | dev (Expo proxy) or static (exported UI) |
TURBOPANEL_UI_ROOT | Static UI export root. Production (FHS) default /opt/turbopanel/share/ui (where the daemon ui-build role publishes the export); co-located dev overrides it to a checkout-relative path such as ../ui/dist |
CADDY_PORT | HTTPS listen port (default 8443) |
CADDY_HTTP_PORT | Dev-only plaintext HTTP listen port (default 8880) |
TURBOPANEL_DEV_HTTP_CONTROL_PLANE | Must be "1" to serve the dev plaintext entrypoint on CADDY_HTTP_PORT; injected automatically on co-located dev hosts only |
TURBOPANEL_IS_SIGNUP_ENABLED | Workers dev sign-up override |
Managed hosts inject vars via Ansible instance-launch. Workers dev uses .dev.vars in the instance checkout.
API entrypoints
| Path | Description |
|---|---|
/api/health | Unversioned health probe |
/api/client/v1/* | End-user REST API + auth |
/api/install/v1/* | Self-hosted install wizard (Deno only) |
/api/developer/v1/* | Developer console (dev tooling) |
/api/daemon/v1/* | Daemon REST (version, instance/ca) |
/ws/daemon/v1 | Daemon WebSocket |
/api/openapi.json | OpenAPI 3.1 spec |
/ | Web UI (via Caddy) |
HTTPS entrypoint (self-hosted): https://<host>:8443
Dev-only plaintext HTTP entrypoint
Co-located development also exposes a plaintext mirror at http://<host>:8880
(CADDY_HTTP_PORT, default 8880). It is gated by
TURBOPANEL_DEV_HTTP_CONTROL_PLANE=1 (set automatically by Ansible when
turbopanel_dev_user is configured). Requests are rejected with 403 when
the flag is off. This entrypoint is not enabled on managed or production
installs — it exists to bypass self-signed TLS friction when troubleshooting
daemon WebSocket connections or attaching a daemon without CA setup. Both
TURBOPANEL_INSTANCE_RUNTIME=deno and =workers use the same Caddy
proxy, so :8880 mirrors every route on :8443 for either runtime.
Communication patterns
- Browser → Caddy → instance: REST and cookies on
/api/client/v1/* - Daemon → instance: WSS
/ws/daemon/v1(through Caddy or direct Unix socket when co-located) - Dev sync / tunnel: Operator pushes daemon builds or tunnel tokens via developer API + daemon WS messages
Related documentation
- Daemon setup — Remote node installer
- Security — TLS, auth, and hardening
- Deployment troubleshooting — Runtime issues
- Instance API architecture — Code layout
Last updated on