TurboPanel Docs
Architecture

Development architecture

This page describes how the TurboPanel multi-repo Vagrant environment is orchestrated. Before you start, see Prerequisites. For day-to-day commands, see Local development and Dev console troubleshooting.

Repository model

TurboPanel is six sibling repositories under one parent directory on the host. The dev repo is the entry point — it does not contain platform source; Vagrant mounts the siblings into the guest and ./console drives them.

Host path (example)Guest mountGitHub repoRole
…/dev/~/dev/turbopanel/devVagrantfile, ./console, Ink UI
…/turbopaneld/~/turbopaneld/turbopanel/turbopaneldRemote daemon + Ansible
…/turbopanel/~/turbopanel/turbopanel/turbopanelHono API (Workers + Deno)
…/ui/~/ui/turbopanel/uiExpo web / native UI
…/website/~/website/turbopanel/websiteMarketing + Fumadocs (this site)
…/.github/~/.github/turbopanel/.githubCommunity health files

Paths resolve via TURBOPANEL_DEV_ROOT and per-repo TURBOPANEL_<DIR>_REPO overrides (defaults: $HOME/{turbopaneld,turbopanel,ui,website}; ~/.github is mounted separately via the github-repo Ansible role, not a TURBOPANEL_*_REPO variable).

Canonical flow:

Terminal
cd path/to/turbopanel/dev
vagrant up
vagrant ssh
dev/console

Dev vs production path model

The console drives a dev layout where source repos live under $HOME (guest) and mutable data uses FHS paths owned by the dev user. Production uses the same FHS tree with dedicated service users. The daemon's src/paths/layout.ts is the single source of truth and picks the mode automatically (development when a resolvable daemon checkout is present, otherwise production); every path is env-overridable.

ConcernDevelopmentProduction (managed / FHS)
Source repos$HOME/{turbopaneld,turbopanel,ui,website} (Vagrant mounts)No source checkout (compiled release)
Daemon entrypointdeno run main.ts from ~/turbopaneldNative /opt/turbopanel/bin/turbopaneld, or turbopaneld.js via vendored Deno when the native binary cannot load
Orchestration assets<checkout>/orchestration + dev/orchestration/ overlay/opt/turbopanel/share/orchestration
Vendored runtimes/opt/turbopanel/vendor/opt/turbopanel/vendor
Config/etc/turbopanel/daemon.env (dev-user-owned)/etc/turbopanel/daemon.env
State / identity/var/lib/turbopanel (dev-user-owned)/var/lib/turbopanel
Logs/var/log/turbopanel (dev-user-owned)/var/log/turbopanel
Sockets / runtime/run/turbopanel (dev-user-owned)/run/turbopanel (2770 tp:tp)
systemd unitturbopaneld.service (dev user)turbopaneld.service (tp)

Both modes use the turbopaneld.service systemd unit. Production code never names the dev checkout root — a CI guard (deno task check:layout) and src/orchestration/paths.test.ts pin the two trees and the shared DENO_VERSION (2.9.6).

Dev console orchestration

The Ink console (./console, inside the guest) is the single operator surface:

Console areaWhat it drives
ServicesUnit health, Deno vs Workers runtime switch
DeveloperFleet, database, shell, Converge / re-converge, cell trace
BootstrapTransient provisioning overlay (daemon install / converge)

Service graph (Deno mode, default)

Diagram will load when scrolled into view

Caddy terminates TLS on 8443 and proxies /api/* and /ws/* to the instance Unix socket; everything else goes to Expo (dev) or static export (production build mode).

On co-located guests, Caddy also listens on 8880 as a plaintext mirror of every route on :8443. Both TURBOPANEL_INSTANCE_RUNTIME=deno and =workers sit behind the same Caddy proxy.

Instance runtime switch

TURBOPANEL_INSTANCE_RUNTIME in the daemon .env is deno (default) or workers. Switch via the Services area in the console. In Workers mode the same turbopanel-instance.service unit runs wrangler dev (scripts/workers-serve.sh in the instance checkout) with Postgres on TCP.

Database schema (development)

Schema changes are versioned in turbopanel/migrations/. Versioned pnpm migrate is the only fresh-database bootstrap path — co-located dev converge runs scripts/bootstrap-dev-db.sh (pnpm migrate), and Workers deploy runs the same command. Applied versions are tracked in public.migration. An unmigrated database is an operational failure (missing relations propagate); it must not be treated as install mode / needsInstall.

  1. Generate + apply migrations: edit turbopanel/src/lib/db/schema.ts, run pnpm drizzle-kit generate --name <summary>, review SQL under migrations/, then TURBOPANEL_DATABASE_URL=… pnpm migrate (or ./scripts/bootstrap-dev-db.sh).
  2. Push (code → DB, Deno dev iteration only): after a migrated baseline exists, ./scripts/sync.sh from the guest dev checkout can apply schema edits via drizzle-kit push for quick local iteration — not a fresh-DB bootstrap.
  3. Pull (DB → code): change tables in Drizzle Studio, then ./scripts/introspect.sh from the guest dev checkout.

Drizzle Studio bind host

Drizzle Studio must bind to loopback only (localhost / 127.0.0.1 / ::1). Vagrant forwards guest :4983 to host loopback only. Open https://local.drizzle.studio?host=localhost&port=4983.

Local-Console developer API auth

The Ink console authenticates to /api/developer/v1/* over the instance Unix socket with an HMAC Local-Console credential (shared root secret at /etc/turbopanel/instance/.instance_secrets). The signed payload covers method, full request target (path and query), and a SHA-256 content digest header so captured tokens cannot be replayed against altered requests. Cookie-authenticated browser writes to the developer surface are also same-origin gated (alongside client/admin/install).

See Database troubleshooting for resets and connection issues.

Port allocation

ServiceDefault portHost forwardNotes
Hosting Caddy HTTP800.0.0.0Tenant site ingress (not the control plane)
Hosting Caddy HTTPS4430.0.0.0Tenant site ingress (not the control plane)
Caddy HTTPS84430.0.0.0Primary browser entrypoint (TLS)
Caddy HTTP (dev)88800.0.0.0Plaintext mirror of :8443
Website198200.0.0.0Next.js (optional)
Wrangler (Workers mode)18787Internal only
Expo web80810.0.0.0Metro; Caddy also proxies this in the guest
Postgres5432TCP in Workers mode; Unix socket in Deno
Mailpit web8025127.0.0.1Unauthenticated
Mailpit SMTP1025Dev email SMTP
Drizzle Studio4983127.0.0.1Unauthenticated
Redis Insight5540127.0.0.1Unauthenticated
DuckDB UI4213127.0.0.1On-demand, development build only

Browser traffic from the host should use Caddy on :8443 (HTTPS) or http://localhost:8880. Prefer a LAN hostname when attaching remote test machines to the forwarded 0.0.0.0 ports.

Technology choices

  • Hono — Shared HTTP app for Workers and Deno (turbopanel/src/app.ts).
  • Expo / React Native — Cross-platform UI (ui/).
  • Next.js + Fumadocs — Marketing and documentation (website/).
  • Drizzle + Postgres — Schema in turbopanel/src/lib/db/schema.ts; Hyperdrive in Workers production.
  • Caddy — TLS termination and reverse proxy in dev and self-hosted production.
  • Ansible (daemon) — Installs and updates runtimes, instance, UI, and Caddy on managed hosts.
  • Ink — Terminal dev console in TurboPanel/dev.
  • Vagrant — Contributor guest (libvirt on Linux, UTM on macOS).
  • Command Pipeline — Typed command/job records in Postgres (command table); Cloudflare Queues (Workers) or RabbitMQ (Deno) for durable async transport; Daemon Cell (Durable Object per server on Workers, Redis on self-hosted) for live WS delivery and request correlation. Status reads stay Postgres-only — never fan UI polling into the cell. Production daemon commands are typed handlers only — never arbitrary shell strings. See turbopanel/AGENTS.md (Daemon Cell + Command Pipeline) and turbopanel/src/lib/commands/ / turbopaneld/src/instance/commands/.

These support both Cloudflare Workers (cloud-hosted) and Deno + Caddy (self-hosted) deployment models described in Introduction.

Edit on GitHub

Last updated on

On this page