Deployment

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

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

FeatureSelf-hosted (Deno)TurboPanel High Availability
RuntimeDeno on Unix socketCloudflare Workers
TLSCaddy (platform CA)Cloudflare-managed TLS
DatabaseLocal Postgres (socket)Postgres via Hyperdrive
UICaddy → Expo dev or static export (/opt/turbopanel/share/ui; dev override ../ui/dist)Workers assets / separate hosting
Daemon on CP hostCo-located (socket mode)Connects to Workers URL
PricingFree, unlimited serversPay 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 | sh

Choose 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:

UnitUserRole
turbopaneld.servicecurrent dev userAnsible orchestration (daemon from ~/daemon in dev)
turbopanel-instance.servicecurrent dev userDeno API on Unix socket
turbopanel-caddy.servicecurrent dev userTLS + reverse proxy :8443; dev-only plaintext mirror :8880 when enabled
turbopanel-ui.servicecurrent dev userExpo 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 deploy

pnpm 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 | sh

Use Start dev stack in the console — see Local development. To run again later: cd ~/dev && ./console.

Configuration

Key environment variables (instance)

VariablePurpose
TURBOPANEL_SECRET / TURBOPANEL_SECRETSSession signing (required in production)
TURBOPANEL_DATABASE_URLFull Postgres connection URL for self-hosted Deno boot (instance-launch) and tooling
DATABASE_URLTooling-only fallback for pnpm migrate / drizzle-kit when TURBOPANEL_DATABASE_URL is unset (common in CI and dashboard deploy)
CLOUDFLARE_ENVWrangler env name for Workers deploy (e.g. live, testing) — required by pnpm deploy
TURBOPANEL_SOCKET / TURBOPANEL_SOCKET_DIRUnix socket path overrides
TURBOPANEL_UI_MODEdev (Expo proxy) or static (exported UI)
TURBOPANEL_UI_ROOTStatic 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_PORTHTTPS listen port (default 8443)
CADDY_HTTP_PORTDev-only plaintext HTTP listen port (default 8880)
TURBOPANEL_DEV_HTTP_CONTROL_PLANEMust be "1" to serve the dev plaintext entrypoint on CADDY_HTTP_PORT; injected automatically on co-located dev hosts only
TURBOPANEL_IS_SIGNUP_ENABLEDWorkers dev sign-up override

Managed hosts inject vars via Ansible instance-launch. Workers dev uses .dev.vars in the instance checkout.

API entrypoints

PathDescription
/api/healthUnversioned 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/v1Daemon WebSocket
/api/openapi.jsonOpenAPI 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

Edit on GitHub

Last updated on

On this page