Local development
This guide covers contributor local development. For host tooling, see Prerequisites. For layout and service dependencies, see Development architecture. For common failures, see Dev console troubleshooting.
TurboPanel is not a monorepo. Clone (or fork) six sibling repositories under one parent directory, then run the stack inside a Vagrant guest. Edit on the host with VS Code / Cursor; Node, Deno, Docker, and systemd run in the VM.
System requirements
We recommend at least an Intel Core i7-4790K (or equivalent) with 16 GB of RAM. Some workloads need more memory, so 24 GB+ is recommended, and 32 GB is ideal.
| Requirement | Recommendation |
|---|---|
| CPU | Intel Core i7-4790K or equivalent |
| RAM | 16 GB minimum · 24 GB+ recommended · 32 GB ideal |
| Software | Vagrant plus a provider (libvirt on Linux, UTM on macOS) |
Install Vagrant and the provider before vagrant up. Full host tooling, Git/SSH, and provider install notes are on Prerequisites. Additional Vagrant providers for macOS and Linux are welcome — open a PR against TurboPanel/dev.
Repository layout
Use your own forks and feature branches so you can open PRs against trunk:
turbopanel/
├── dev/ # Vagrantfile + Ink console — https://github.com/TurboPanel/dev
├── turbopaneld/ # host daemon + Ansible — https://github.com/TurboPanel/turbopaneld
├── turbopanel/ # control plane — https://github.com/TurboPanel/turbopanel
├── ui/ # Expo product console — https://github.com/TurboPanel/ui
├── website/ # marketing + docs — https://github.com/TurboPanel/website
└── .github/ # community health — https://github.com/TurboPanel/.githubInside the guest these mount at ~/dev, ~/turbopaneld, ~/turbopanel, ~/ui, ~/website, and ~/.github.
Quick start
1. Install Vagrant and a provider (see Prerequisites).
2. Boot the guest from the dev checkout:
cd path/to/turbopanel/dev
vagrant up
vagrant sshPlain vagrant up selects libvirt on Linux and UTM on macOS. First boot downloads the box and may reboot once when a newer kernel is pending.
3. Start the console inside the guest:
dev/consoleThat ensures pinned Node, runs pnpm install, and launches the Ink developer console. On a fresh guest the console bootstraps the daemon and converges the stack (optional-services picker after bootstrap). On later launches it sits idle until Developer → Converge / re-converge.
4. Open the control plane from the host
| Service | URL |
|---|---|
| App (Caddy → UI + API, HTTPS) | https://localhost:8443 |
| App (Caddy → UI + API, plaintext) | http://localhost:8880 |
| API health (HTTPS) | https://localhost:8443/api/health |
| API health (plaintext) | http://localhost:8880/api/health |
| Website (docs + API reference) | http://localhost:19820 (when enabled) |
| Mailpit | http://localhost:8025 (when enabled; host loopback only) |
Trust the platform CA bundle at /var/lib/turbopanel/tls/ca-bundle.pem (after converge, or fetch it from GET /api/daemon/v1/instance/ca) in your browser to avoid TLS warnings on :8443.
Smoke test from the host:
curl -k https://localhost:8443/api/health
curl http://localhost:8880/api/healthPorts forwarded to the host
Vagrant forwards these guest ports. Use them from the IDE, browsers, or remote test machines.
| Port | Service | Host bind |
|---|---|---|
| 80 | Hosting Caddy HTTP (tenant sites) | 0.0.0.0 (LAN) |
| 443 | Hosting Caddy HTTPS (tenant sites) | 0.0.0.0 (LAN) |
| 8443 | Control plane (Caddy HTTPS) | 0.0.0.0 (LAN) |
| 8880 | Control plane (Caddy plaintext HTTP) | 0.0.0.0 (LAN) |
| 8081 | Expo / Metro (native + direct; Caddy also proxies this) | 0.0.0.0 (LAN) |
| 8088 | Optional extra forward (guest must listen) | 0.0.0.0 (LAN) |
| 19820 | Website (Next.js) | 0.0.0.0 (LAN) |
| 4983 | Drizzle Studio (unauthenticated) | 127.0.0.1 only |
| 8025 | Mailpit web UI (unauthenticated) | 127.0.0.1 only |
| 5540 | Redis Insight (unauthenticated) | 127.0.0.1 only |
| 4213 | DuckDB UI (on-demand, dev build only) | 127.0.0.1 only |
- Local browsing / VS Code / Cursor:
https://localhost:8443orhttp://localhost:8880. - Remote test machines / extra daemons: give your development host a hostname (for example
https://dev.lan:8443or your LAN IP) so clients are not stuck onlocalhost. Ports80/443/8443/8880/8081/8088/19820listen on all host interfaces. - Studio / Mailpit / Redis Insight / DuckDB UI stay loopback-only on purpose — those UIs are unauthenticated. Open Drizzle Studio at
https://local.drizzle.studio?host=localhost&port=4983. - Tenant sites:
http://localhostandhttps://localhost(ports 80 / 443) reach hosting Caddy after a deploy. On Linux those host binds are privileged — the libvirt tunnel supervisor uses passwordlesssudo -n, or you can lowernet.ipv4.ip_unprivileged_port_start.
Dev console areas
Use ← / → to switch areas in ./console (ActiveArea = "developer" | "services" | "bootstrap" — bootstrap is the transient provisioning overlay, not a persistent tab):
| Area | Contents |
|---|---|
| Services | Service list/detail, restart, runtime switch (Deno/Workers) |
| Developer | Fleet, database, shell, Converge / re-converge, cell trace |
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 Services area in the console.
| Mode | Behaviour |
|---|---|
| deno (default) | turbopanel-instance systemd unit runs Deno + Unix socket at /run/turbopanel/instance.sock |
| workers | Same systemd unit runs wrangler dev (scripts/workers-serve.sh) with Postgres on TCP (127.0.0.1:5432) |
Workers mode has no Redis. Converge generates the runtime env for both modes (including the local Hyperdrive connection string) under /etc/turbopanel/instance/ — no manual .dev.vars editing is needed.
Guest filesystem (after converge)
~/dev/ ~/turbopaneld/ ~/turbopanel/ ~/ui/ ~/website/ ~/.github/ # mounted checkouts
/opt/turbopanel/vendor/ # pinned Node, Deno, Caddy, uv, Ansible, …
/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 turbopanel/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 guest 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.
Formatting
Each repo formats independently — run the formatter configured in that repo before committing.
Workflow diagram
Related
Last updated on