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://localhost:8443 |
| API health | https://localhost:8443/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/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 HTTP, and Let's Encrypt issuance or renewal | 0.0.0.0 (LAN) |
| 443 | Hosting HTTPS (hosted sites) | 0.0.0.0 (LAN) |
| 8443 | Control plane HTTPS (always) | 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:8443. - 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. The control plane is alwayshttps://<host>:8443. Port 80 answers only while Let's Encrypt is issuing or renewing (and while hosted sites are deployed). Port 443 is hosting. Ports80/443/8443/8081/8088/19820listen on all host interfaces. Trust the Platform CA. LAN names can usecurl -kfor a one-off health check. - 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. - Hosted 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 | Repair install, Converge / re-converge development environment, Optional services…, Reset development environment, Reset dev database, Run tests…, cell trace, Purge completely |
In the Developer area: ↑↓ picks an action and Enter runs it (destructive actions ask for confirmation first).
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. Developer → Reset dev database wipes the dev schema and re-applies the migrations; Drizzle Studio is on the Services list.
Formatting
Each repo formats independently — run the formatter configured in that repo before committing.
Workflow diagram
Related
Last updated on