Local development
This guide covers contributor local development. For supported OSes and required tooling, see Prerequisites. For layout and service dependencies, see Development architecture. For common failures, see Dev console troubleshooting.
TurboPanel is not a monorepo. Clone the five sibling repos into $HOME (~/dev, ~/daemon, ~/instance, ~/ui, ~/website). The dev repository is the entry point — it installs the dev console and orchestrates the stack via Ansible.
Fresh-clone → working dev
- Clone the five repos into
$HOME(~/dev,~/daemon,~/instance,~/ui,~/website). - From
~/dev, run./console→ prereqs, pinned Node,pnpm install, TUI launch (exportsTURBOPANEL_MODE=development,TURBOPANEL_DEV_ROOT,TURBOPANEL_<DIR>_REPO). - Start dev stack → daemon bootstraps as the dev user, writes
/etc/turbopanel/daemon.env, runs thedev/orchestrationoverlay: runtimes into/opt/turbopanel/vendor, systemd units + Docker (postgres/redis/rabbitmq/mailpit) as the dev user, mutable data under FHS trees dev-user-owned. Notp/tpctrl/tpcacheaccounts created. - Open
https://localhost:8443(or devhttp://localhost:8880); edit source in place under$HOME.
Quick start
1. Bootstrap the dev console
One-liner (fresh machine):
curl -fsSL dev.turbopanel.sh | shThe one-liner clones or updates ~/dev (or ${TURBOPANEL_DEV_ROOT}/dev) and launches ./console automatically.
Or, from an existing checkout:
cd ~/dev
sh scripts/develop.sh # update dev repo only
./console2. Start the dev stack
From ./console, choose Start dev stack in the Actions area. The console:
- Ensures the daemon repo exists at
~/daemon(orTURBOPANEL_DAEMON_REPO) - Writes developer identity (
TURBOPANEL_DEV_USER/UID/GID) into/etc/turbopanel/daemon.env - Runs
scripts/bootstrap-orchestration.ts(uv, Python, Ansible under/opt/turbopanel/vendor/) andinstall-daemon-systemd.sh - Lets the daemon install instance, UI, Caddy, and Postgres via Ansible (dev overlay in
dev/orchestration/)
3. Open
| Service | URL |
|---|---|
| App (Caddy → UI + API, HTTPS) | https://localhost:8443 |
| App (Caddy → UI + API, dev plaintext) | http://localhost:8880 — co-located dev only; requires TURBOPANEL_DEV_HTTP_CONTROL_PLANE=1 |
| API health (HTTPS) | https://localhost:8443/api/health |
| API health (dev plaintext) | http://localhost:8880/api/health |
| Website (docs + API reference) | http://localhost:19820 (when running separately) |
| Mailpit | http://localhost:8025 (when configured) |
Trust the platform CA at instance/certs/ca.crt in your browser to avoid TLS warnings on :8443. The :8880 entrypoint is a dev-only plaintext mirror of :8443 — gated by TURBOPANEL_DEV_HTTP_CONTROL_PLANE=1 (set automatically on co-located dev) — and works the same for TURBOPANEL_INSTANCE_RUNTIME=deno and =workers because both runtimes sit behind the same Caddy proxy.
Smoke test:
curl -k https://localhost:8443/api/health
curl -k https://localhost:8443/api/client/v1/status
# Co-located dev plaintext mirror (no -k needed):
curl http://localhost:8880/api/health
curl http://localhost:8880/api/client/v1/statusDev console areas
Use ← / → to switch areas in ./console:
| Area | Contents |
|---|---|
| Status | Runtime, source checkouts, dev stack units |
| Instance | Runtime mode (Deno/Workers), instance unit status, switch action |
| Developer | Fleet, services, shell, database, … (when instance.sock is present) |
| Actions | Install daemon, start stack, follow logs, build mode, refresh, quit |
In the Developer area: ↑↓ picks a section, Enter opens it, Esc returns to the section list.
Instance runtime modes
TURBOPANEL_INSTANCE_RUNTIME in /etc/turbopanel/daemon.env selects how the instance runs locally. Switch via the Instance area in the console.
| Mode | Behaviour |
|---|---|
| deno (default) | turbopanel-instance systemd unit + Unix socket at /run/turbopanel/instance.sock |
| workers | Stops the systemd unit, exposes Postgres on TCP (127.0.0.1:5432), expects pnpm dev in ~/instance manually |
Workers mode has no Redis. Set CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE in instance/.dev.vars.
Repository layout
Source repos live under $HOME (override with TURBOPANEL_DEV_ROOT and TURBOPANEL_<DIR>_REPO):
~/
├── dev/ # turbopanel/dev — console entrypoint
├── daemon/ # turbopanel/turbopaneld
├── instance/ # Hono API (Workers + Deno)
├── ui/ # Expo frontend
└── website/ # marketing + docs (optional local run)
/opt/turbopanel/
└── vendor/
├── deno/2.9.4/ # pinned Deno runtime (orchestration-managed)
├── node/ # pinned Node runtime (console-managed)
├── uv/ # shared orchestration runtime (daemon bootstrap)
├── python/ # managed Python for Ansible venv
└── ansible/ # ansible-playbook venv + Galaxy content
/etc/turbopanel/ # config (dev-user-owned)
/var/lib/turbopanel/ # state (dev-user-owned)
/var/log/turbopanel/ # service logs (dev-user-owned)
/run/turbopanel/ # runtime sockets (dev-user-owned)Common tasks
Database schema
TurboPanel ships versioned SQL migrations in instance/migrations/. Co-located dev converge and Workers deploy apply them via pnpm migrate. For quick Deno-only iteration without committing migration files, use drizzle-kit push from the dev checkout:
./scripts/sync.sh # push instance schema.ts → Postgres (interactive)
./scripts/sync.sh --force # skip destructive-change prompts (dev only)
./scripts/introspect.sh # pull live DB → instance schema.tsFor errors and resets, see Database troubleshooting. The dev console Database section can reset the dev instance or start Drizzle Studio.
Drizzle Studio
In the developer console Database section, start Studio via the API. The instance spawns drizzle-kit studio on 4983; open https://local.drizzle.studio?host=localhost&port=4983 in your browser.
Formatting
Each repo formats independently — run the formatter configured in that repo before committing.
Workflow diagram
Port reference
| Service | Port | Notes |
|---|---|---|
| Caddy (HTTPS app) | 8443 | Primary browser entrypoint (TLS) |
| Caddy (HTTP dev mirror) | 8880 | Plaintext mirror of :8443; requires TURBOPANEL_DEV_HTTP_CONTROL_PLANE=1; co-located dev only |
| Website | 19820 | Next.js (when run locally) |
| Wrangler (Workers mode) | 18787 | Not browser-facing |
| Expo web | 8081 | Proxied by Caddy in dev |
| Postgres | 5432 | Docker, 127.0.0.1:5432 (Workers mode) or Unix socket (Deno) |
| Mailpit web | 8025 | Dev email UI |
| Mailpit SMTP | 1025 | Dev email SMTP |
| Drizzle Studio | 4983 | HTTP API; UI at local.drizzle.studio |
Related
Last updated on