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 mount | GitHub repo | Role |
|---|---|---|---|
…/dev/ | ~/dev/ | turbopanel/dev | Vagrantfile, ./console, Ink UI |
…/turbopaneld/ | ~/turbopaneld/ | turbopanel/turbopaneld | Remote daemon + Ansible |
…/turbopanel/ | ~/turbopanel/ | turbopanel/turbopanel | Hono API (Workers + Deno) |
…/ui/ | ~/ui/ | turbopanel/ui | Expo web / native UI |
…/website/ | ~/website/ | turbopanel/website | Marketing + Fumadocs (this site) |
…/.github/ | ~/.github/ | turbopanel/.github | Community 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:
cd path/to/turbopanel/dev
vagrant up
vagrant ssh
dev/consoleDev 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.
| Concern | Development | Production (managed / FHS) |
|---|---|---|
| Source repos | $HOME/{turbopaneld,turbopanel,ui,website} (Vagrant mounts) | No source checkout (compiled release) |
| Daemon entrypoint | deno run main.ts from ~/turbopaneld | Native /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 unit | turbopaneld.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 area | What it drives |
|---|---|
| Services | Unit health, Deno vs Workers runtime switch |
| Developer | Fleet, database, shell, Converge / re-converge, cell trace |
| Bootstrap | Transient provisioning overlay (daemon install / converge) |
Service graph (Deno mode, default)
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.
- Generate + apply migrations: edit
turbopanel/src/lib/db/schema.ts, runpnpm drizzle-kit generate --name <summary>, review SQL undermigrations/, thenTURBOPANEL_DATABASE_URL=… pnpm migrate(or./scripts/bootstrap-dev-db.sh). - Push (code → DB, Deno dev iteration only): after a migrated baseline exists,
./scripts/sync.shfrom the guestdevcheckout can apply schema edits via drizzle-kit push for quick local iteration — not a fresh-DB bootstrap. - Pull (DB → code): change tables in Drizzle Studio, then
./scripts/introspect.shfrom the guestdevcheckout.
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
| Service | Default port | Host forward | Notes |
|---|---|---|---|
| Hosting Caddy HTTP | 80 | 0.0.0.0 | Tenant site ingress (not the control plane) |
| Hosting Caddy HTTPS | 443 | 0.0.0.0 | Tenant site ingress (not the control plane) |
| Caddy HTTPS | 8443 | 0.0.0.0 | Primary browser entrypoint (TLS) |
| Caddy HTTP (dev) | 8880 | 0.0.0.0 | Plaintext mirror of :8443 |
| Website | 19820 | 0.0.0.0 | Next.js (optional) |
| Wrangler (Workers mode) | 18787 | — | Internal only |
| Expo web | 8081 | 0.0.0.0 | Metro; Caddy also proxies this in the guest |
| Postgres | 5432 | — | TCP in Workers mode; Unix socket in Deno |
| Mailpit web | 8025 | 127.0.0.1 | Unauthenticated |
| Mailpit SMTP | 1025 | — | Dev email SMTP |
| Drizzle Studio | 4983 | 127.0.0.1 | Unauthenticated |
| Redis Insight | 5540 | 127.0.0.1 | Unauthenticated |
| DuckDB UI | 4213 | 127.0.0.1 | On-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 (
commandtable); 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. Seeturbopanel/AGENTS.md(Daemon Cell + Command Pipeline) andturbopanel/src/lib/commands//turbopaneld/src/instance/commands/.
These support both Cloudflare Workers (cloud-hosted) and Deno + Caddy (self-hosted) deployment models described in Introduction.
Last updated on
Architecture
TurboPanel multi-repo layout, dev environment orchestration, and instance API design
The Compose contract
TurboPanel is a Compose implementation with exactly one extension namespace. Compose says what the workload wants, x-turbopanel says only what Compose cannot express, and the compiler decides how it runs.