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 derive from TURBOPANEL_DEV_ROOT (defaults: $HOME/{turbopaneld,turbopanel,ui,website}); a per-repo TURBOPANEL_<DIR>_REPO variable is an optional override that is only persisted when you set it explicitly (~/.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.7).

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). Both TURBOPANEL_INSTANCE_RUNTIME=deno and =workers sit behind the same Caddy proxy. Every hostname is https://<host>:8443. Trust the Platform CA for a name that presents that leaf.

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/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.

Every table and non-obvious column has a one-sentence description applied to the live database as COMMENT ON metadata; see Database for how to read them with psql.

Port allocation

ServiceDefault portHost forwardNotes
Hosting Caddy HTTP800.0.0.0Hosted sites, and the control-plane Let's Encrypt window
Hosting Caddy HTTPS4430.0.0.0Hosting only (hosted sites)
Caddy HTTPS84430.0.0.0Always. Control plane (Platform CA catch-all)
Website198200.0.0.0Next.js (optional)
Wrangler (Workers mode)18787—Internal only
Expo web80810.0.0.0Metro; Caddy also proxies this in the guest
Postgres5432—TCP in Workers mode; Unix socket in Deno
Mailpit web8025127.0.0.1Unauthenticated
Mailpit SMTP1025—Dev 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 uses Caddy on :8443 (HTTPS). Every hostname is https://<host>:8443. Trust the Platform CA. Port 80 answers only while Let's Encrypt is issuing or renewing, and while hosted sites are deployed. Port 443 is hosting. 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/app.ts).
  • Expo / React Native — Cross-platform UI (ui/).
  • Next.js + Fumadocs — Marketing and documentation (website/).
  • Drizzle + Postgres — Schema in turbopanel/src/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/features/commands/ / turbopaneld/src/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