Deployment

Daemon Setup

The TurboPanel daemon (turbopaneld) runs on managed servers. It connects to the instance over HTTPS/WSS, runs Ansible to install runtimes and services, and manages Docker, tunnels, and orchestration locally.

Overview

Purpose

Relationship to control plane

The daemon is not a slim Docker-only executor in the new architecture — it is the constant on every TurboPanel-managed host and the only party that runs Ansible. On the control plane host it runs co-located (Unix socket or Caddy URL). On additional servers it dials the instance URL you provide at install time.

Architecture

Deployment models

FeatureTurboPanel High AvailabilitySelf-hosted instance
Instance URLhttps://your-worker.workers.devhttps://<host>:8443
Co-located daemonOptional (dev / socket mode)Yes on control plane
Remote daemonRequired per extra serverRequired per extra server
CommunicationWSS through instance URLWSS through Caddy or direct socket
Daemon coordinationWSS (Durable Objects)WSS (Redis cell)

Installation

Supported platforms

OSArchitectureNotes
Debian 12+ (Bookworm/Trixie)x86_64 (amd64)Recommended for managed servers
Debian 12+ (Bookworm/Trixie)aarch64 (arm64)Supported
Raspberry Pi OS 64-bitaarch64 (arm64)Supported on 64-bit images only

Not supported: 32-bit ARM (armv7l, armhf), 32-bit Raspberry Pi OS, or any CPU architecture other than aarch64 and x86_64. Unsupported hosts fail during Ansible provisioning with an explicit architecture error.

Remote managed server (official installer)

Obtain a license from your TurboPanel organization, then run the channel installer. Set TURBOPANEL_LICENSE to the base64url-encoded licenseId:licenseToken (the UI copy-paste command includes this).

TurboPanel High Availability (Cloudflare Workers — default host from the channel manifest):

curl -fsSL turbopanel.sh | TURBOPANEL_LICENSE=<base64url-encoded-license> sh

Self-hosted instance (pass TURBOPANEL_HOST with the full instance URL):

curl -fsSL turbopanel.sh | \
  TURBOPANEL_LICENSE=<base64url-encoded-license> \
  TURBOPANEL_HOST=https://<instance-host>:8443 \
  sh

Production and self-hosted instances always fetch the installer from turbopanel.sh. The instance host /run.sh path is development-only — it is served by the dev overlay Caddyfile (plaintext :8880), not by the production instance Caddyfile.

# Dev overlay only (plaintext HTTP control plane)
curl -fsSL http://<dev-host>:8880/run.sh | \
  TURBOPANEL_LICENSE=<base64url-encoded-license> \
  TURBOPANEL_HOST=http://<dev-host>:8880 \
  sh

The installer self-escalates with sudo and prompts for your password when needed — do not prefix the pipeline with sudo.

While provisioning runs, the terminal shows a rolling status view (spinner on TTY hosts) with neutral labels — orchestration instead of Ansible tool names, cache instead of Redis, queue instead of RabbitMQ. Vendor paths, role names, and env vars on disk are unchanged; full detail remains in /var/log/turbopanel/daemon.log after install. See the daemon repo AGENTS.md — Installer presentation layer.

For self-hosted HTTPS, the installer fetches the platform CA from GET /api/daemon/v1/instance/ca and configures TURBOPANEL_INSTANCE_CA. Re-run the same command any time to upgrade or reconcile a node.

Refresh an existing node

If the server is already installed, re-run the same run.sh installer. Build the license argument from state on disk (or copy a fresh install command from the UI):

LICENSE_B64=$(python3 -c "
import base64
id = open('/var/lib/turbopanel/license.id').read().strip()
tok = open('/var/lib/turbopanel/license.token').read().strip()
print(base64.urlsafe_b64encode(f'{id}:{tok}'.encode()).decode().rstrip('='))
")

curl -fsSL turbopanel.sh | \
  TURBOPANEL_LICENSE="$LICENSE_B64" \
  TURBOPANEL_UPDATE_CHANNEL=trunk \
  sh

See Refresh a stuck daemon for offline UI mismatches, missing license files, and self-hosted hosts.

Control plane host

On the machine that runs the instance, use the dev console (Start dev stack) or Ansible co-located install — not the remote node installer.

Install options

Variable / flagDescription
TURBOPANEL_LICENSE / --license <b64>Required. Base64url-encoded licenseId:licenseToken.
TURBOPANEL_UPDATE_CHANNEL / --channel <name>Release channel. Default: trunk. Channel names (trunk, canary, rc, release, …) are technical identifiers — not product branding. Written to .env as TURBOPANEL_UPDATE_CHANNEL.
TURBOPANEL_HOST / --host <URL>Optional for production (defaults to manifest defaultControlPlaneUrl). Required for self-hosted instances (https://…).
TURBOPANEL_INSECURE_TLS=1 / --insecure-tlsUse curl -k for bootstrap downloads only (dev/self-signed CDN or instance)
--tunnel-token <TOKEN>Cloudflare tunnel token for this node
--instance-ca <PATH>PEM platform CA (skips automatic CA fetch)
--no-startProvision without starting turbopaneld.service

What gets installed (FHS layout)

The installer bootstraps uv/Python/Ansible and runs daemon-install.yml, which lays out a clean Filesystem Hierarchy Standard tree — there is no source checkout on a managed server:

PurposePath
Native daemon binary/opt/turbopanel/bin/turbopaneld
Deno JS runtime (hosts where the native binary cannot load)/opt/turbopanel/bin/turbopaneld.js
Orchestration assets (Ansible)/opt/turbopanel/share/orchestration
Static UI export (control plane host)/opt/turbopanel/share/ui
Vendored runtimes (deno/uv/python/ansible/cloudflared)/opt/turbopanel/vendor
Config (daemon.env, instance-ca.pem)/etc/turbopanel
Persistent identity (license, server.id, keys, tunnels)/var/lib/turbopanel
Logs/var/log/turbopanel
Runtime (sockets, daemon.lock)/run/turbopanel

Co-located dev is different

A control-plane host provisioned via the dev console runs the daemon from a source checkout under $HOME (e.g. ~/daemon, via deno run main.ts). Config lives in /etc/turbopanel/daemon.env; mutable state, logs, and sockets under dev-user-owned FHS paths (/etc/turbopanel, /var/lib/turbopanel, /var/log/turbopanel, /run/turbopanel); vendored runtimes under /opt/turbopanel/vendor. No dedicated tp / tpctrl / tpcache service accounts are created — systemd units and Docker-backed services run as the current dev user. The FHS tree above (including tp:tp) applies to managed/production installs only.

Configuration

Runtime config: /etc/turbopanel/daemon.env (EnvironmentFile= on turbopaneld.service).

VariablePurpose
TURBOPANEL_INSTANCE_URLHTTPS base URL of instance (remote nodes)
TURBOPANEL_INSTANCE_CAPlatform CA PEM path for TLS (default /etc/turbopanel/instance-ca.pem)
TURBOPANEL_UPDATE_CHANNELRelease channel for manual updates and UI updates (trunk default)
TURBOPANEL_DEV_INSTANCE1 for co-located dev (Ansible installs instance/UI)

Co-located socket mode omits TURBOPANEL_INSTANCE_URL and dials unix:///run/turbopanel/instance.sock.

Communication

Common issues

IssueSolution
Connection failuresVerify --host / TURBOPANEL_INSTANCE_URL, firewall, TLS CA trust
TLS errorsRe-fetch CA from /api/daemon/v1/instance/ca or pass --instance-ca
Unsupported architectureTurboPanel requires 64-bit aarch64 or x86_64; 32-bit Raspberry Pi OS is not supported
Docker permission errorsEnsure tp user is in docker group (installer adds this when Docker is installed)

See Deployment troubleshooting for more.

Edit on GitHub

Last updated on

On this page