Supbuddy docs

Run multiple Supabase projects at once on one Mac, each with its own custom local domain.

Getting started

1. Install

Download the latest .dmg from the download page. Drag Supbuddy.app into /Applications and launch it. Supbuddy is signed and notarized; macOS will not show a Gatekeeper warning. Apple Silicon (M1/M2/M3/M4) and Intel Macs are both supported. Windows and Linux are not supported in v2.

2. Trust the local Certificate Authority

On first launch, open the app and click Install Certificate when prompted. Supbuddy generates a local CA (Caddy's internal PKI) at ~/Library/Application Support/Supbuddy/caddy-data/caddy/pki/authorities/local/root.crt and installs it into your system Keychain via sudo security add-trusted-cert. macOS will ask for your password once. After this, every Supbuddy domain gets the green padlock automatically — no per-domain prompts, no browser warnings.

If you skip the prompt, you can re-trigger it any time from the Settings → Network tab.

3. Add your first project

Click Add project in the Configure tab and pick a project root folder (the one with package.json and/or supabase/config.toml). Supbuddy scans it and creates auto-mapped subdomains based on what it finds:

  • Supabase Kong → api.<project>.test
  • Supabase Studio → studio.<project>.test
  • Supabase Inbucket / Mailpit → mail.<project>.test
  • Each detected app (Next.js, Vite, etc.) → <app-name>.<project>.test

The default TLD is .test. You can change it project-wide in Settings → General → Default TLD.

4. Start the proxy

Toggle the project on. Supbuddy starts Caddy on port 8443 (HTTPS) and starts its built-in DNS server on port 5353. If you want real ports 80/443 instead of 8080/8443, enable port forwarding in Settings → Network — Supbuddy adds a pfctl redirect rule (asks for sudo once).

Core concepts

Four things to understand:

  • Project — a folder you registered. Holds detected apps (Next.js, Vite, etc.), detected services (Supabase stack, Docker Compose services), and a list of mappings.
  • Mapping — a domain → port pair (e.g. api.acme.test → 54321). Auto-generated mappings are tied to a detected service or app; you can also create manual ones.
  • Isolation mode — per-project. Either:
    • host (default): Supabase runs on your host Docker, sharing ports with the host. Only one host-mode Supabase project can run at a time (the standard supabase start constraint).
    • vm (isolated): Supabase runs inside a Docker-in-Docker container exclusive to this project. This is what lets you run multiple Supabase projects simultaneously — each gets its own Postgres, its own Studio, its own port universe inside its DinD.
  • Active vs inactive — Free tier keeps any 2 projects "active" (proxied + reachable) at once. Pro removes that cap. Inactive projects keep their state (DinD container exists, just stopped), so flipping them on is a few seconds.

Project cards (Configure tab)

Each registered project appears as a card in the Configure tab. Cards have a single-row header that's always visible and a tab-based body that expands on click.

Reading left to right:

  • Expand chevron + project name — click to expand/collapse the card.
  • Status indicator — a single colored dot next to the project name aggregating the realtime state of every subsystem (VM, Supabase services, Compose, scripts, AI sync, port conflicts, next.config warnings). Red = error, amber = warning, green = at least one service running, muted gray = idle, animated cyan spinner = transitioning. Hover for a tooltip that lists each subsystem's state.
  • Tech badges — e.g. TurboRepo, Supabase (shown when detected).
  • Warning pills — amber next.config: N origins missing and red port conflict: N (click to fix). Hidden when none.
  • Env mode chip — read-only Host or Isolated label. To switch isolation modes, open the Supabase tab and use the Environment section at the top.
  • Issues counter — red for errors, amber for warnings. Click to open the issues popover (see below). Hidden when there are no issues.
  • Enable toggle (right edge) — turn the project's proxy on/off without deleting it.

Edit, Rescan, Select folder, Export bundle, and Delete are not in the header — they live in the Settings tab.

Issues popover

Clicking the issues counter opens a popover listing all current errors and warnings. Each issue shows a severity icon, title, optional detail, and a → open {tab} link. Clicking the link jumps to the relevant tab and closes the popover.

Body tabs (when expanded)

The body renders a flat tab strip with 7 conditional tabs. Below ~480 px, the strip collapses to a dropdown selector.

Apps (default tab)

Per-app rows are domain-first: domain → :port (with hover-revealed copy/open URL buttons), then app name + tech badge, then a flex spacer pushes hover-revealed edit / delete / access (LAN / Tailscale state) actions and the per-mapping toggle to the right edge. A Map CTA appears on hover for unmapped apps. Manual mappings scoped to this project (not auto-generated) are listed below under their own subheader.

Supabase (shown when Supabase is detected)

Environment section (top): host/isolated switcher. When isolated, shows the DinD VM lifecycle controls (Start VM / Stop VM) and a More menu containing Adjust resources and Reset VM.

Action bar: Start, Stop, Restart buttons; a first-class Connect button (cyan, opens the connection panel for .env generation / merge); and a More menu with Config editor and Details.

Service rows (read-only): status dot, service name, URL. No inline actions — lifecycle is driven by the action bar.

Compose (shown when Compose services are detected)

Action bar: Start, Stop, Restart. Service rows are read-only (status dot, name, URL).

Other (shown when non-Supabase, non-Compose services are detected)

Read-only service rows: status dot, name, URL.

Scripts (shown when scripts are detected)

Bookmarked scripts appear in a Quick Access group at the top; remaining scripts appear under Other Scripts. Per-script row: status dot, name, uptime, bookmark star, Start/Stop/Restart buttons. A search input appears when there are more than 5 scripts.

AI Tools

Wraps the project-context-sync panel — sync mode selector (Auto / Manual / Off), detected targets list, advanced options, and recent activity.

Settings

Explicit action buttons: Edit (rename/reconfigure), Rescan (re-detect apps and services), Select folder (move the project root), Export bundle (download a zip of the project's Supbuddy state), Delete (destructive, moves project to Trash).


Multiple Supabase projects (the main use case)

The reason Supbuddy exists. Stock Supabase CLI binds to fixed ports (54321 Kong, 54322 Postgres, 54323 Studio, 54324 Inbucket). Two projects on the same machine collide; you must supabase stop one before supabase start-ing the other.

How to run them all at once

  1. For each project: Add project, then open the project card → Supabase tab → Environment section → switch to Isolated.
  2. Supbuddy creates a per-project DinD container named supbuddy-<project-id>, copies your supabase/ folder into it, and runs supabase start inside.
  3. Each project gets its own subdomain set under its project domain, e.g. api.acme.test, studio.acme.test, db.acme.test:<port> for Postgres.
  4. Postgres is exposed via Caddy's L4 (TCP) routing, so the host port is stable across restarts (one of the recurring pains with raw docker run -p).

Free vs Pro for parallel projects

  • Free: register unlimited projects, but only 2 can be "active" (proxied) at the same time. Toggle which 2.
  • Pro: all projects can be active simultaneously. No limit.

The DinD containers themselves persist regardless of tier — toggling a project off just stops its container, toggling on resumes it.

Custom domains & TLDs

Every mapping resolves through Supbuddy's built-in DNS server on port 5353. By default the TLD is .test (an IETF-reserved TLD safe for local use). You can change the default in Settings → General → Default TLD to local, dev, or anything else; existing mappings are migrated to the new TLD on save.

For host resolution, Supbuddy does not use /etc/hosts for wildcards — it runs an actual DNS resolver. macOS's default resolver only queries port 53; Supbuddy installs a per-project resolver file under /etc/resolver/<project-domain> (e.g. /etc/resolver/myapp.local) pointing at 127.0.0.1:5353. macOS picks the longest-suffix-matching file, so per-project entries route reliably without colliding with reserved namespaces like .local (which Bonjour/mDNS owns). You'll be prompted for sudo the first time this changes.

LAN sharing

When LAN sharing is enabled (Settings → Network), Supbuddy binds Caddy to 0.0.0.0 instead of 127.0.0.1 and runs an mDNS responder so other machines on your local network can reach your dev servers via <hostname>.local. Useful for testing on your phone or another laptop without setting up Tailscale.

.local TLD + LAN sharing: macOS reserves the .local namespace for Bonjour/mDNS (RFC 6762), and macOS's TCP stack short-circuits self-connections to your own LAN IP via the loopback path without consulting pf — so the obvious "redirect lo0 → my LAN IP" trick can't fix it. Supbuddy's mDNS responder works around this by ignoring queries that originate from this machine, letting the OS resolver fall through to /etc/resolver/<project-domain> (which routes to 127.0.0.1 where Caddy listens). Other LAN devices still get answered with the LAN IP and reach you normally. The net result: .local works correctly both on this machine and on other LAN devices, with no manual configuration. If you previously worked around this by switching to .test, you can switch back.

If studio.<project>.local (or similar) doesn't load: open the Configure tab — a red banner will tell you whether it's a DNS, port-forwarding, or mDNS-race issue, with the specific recovery action.

Tailscale

If you have Tailscale installed and a Tailscale API key configured in Settings, Supbuddy can push split-DNS routes to your tailnet so any device on your tailnet resolves your Supbuddy domains. Optional, off by default.

Monorepo support

Supbuddy auto-detects these monorepo layouts when scanning a project root:

  • Turborepo (presence of turbo.json)
  • pnpm workspaces (pnpm-workspace.yaml)
  • npm/yarn workspaces (workspaces field in root package.json)
  • Common folder layouts: apps/*, packages/*, services/*, sites/*

Each detected app gets its own subdomain. Supabase is searched for in the project root and these subdirectories: apps/*, packages/*, services/*, sites/*, db/, db/*, database/, database/*, packages/backend, packages/db, packages/database.

Detected app frameworks

Port detection looks for the framework dependency in package.json and combines that with: explicit -p/--port in the dev script, PORT= env in the dev script, or a config file read. If none of those resolve, the framework default is used:

Framework dependencyDefault port
next3000
vite5173
@remix-run/dev, @remix-run/serve3000
astro4321
nuxt, nuxt33000
@sveltejs/kit5173
@angular/core4200
@nestjs/core3000
express, fastify, koa, hono, @hono/node-server, elysia, polka, tinyhttpnone — must be explicit in dev script

Server Actions allowedOrigins audit

For Next.js apps, Supbuddy reads your next.config.{ts,mts,js,mjs,cjs} and extracts the hosts in experimental.serverActions.allowedOrigins. If a mapped subdomain is missing from that list, the project shows an amber warning with a paste-ready snippet — Server Action POSTs through Supbuddy mappings would 403 otherwise.

The dialog also has an Apply… button: click it to see a unified diff of the change Supbuddy will make to your next.config, then Confirm & write to apply it. Supbuddy handles the four common config shapes (existing allowedOrigins array, existing serverActions block without it, existing experimental block without serverActions, or no experimental at all). After write, Supbuddy rescans the project so the warning disappears immediately. Restart your dev server for the change to take effect — Next.js does not hot-reload next.config.

MCP setup (AI agents)

Supbuddy ships a built-in MCP server on http://127.0.0.1:9877/mcp with static Bearer-token auth. Five clients have one-click install; any other MCP-compatible tool can be configured manually with the same URL + token.

Open Settings → MCP → Add client, pick the client kind, and Supbuddy generates a token, edits the client's config file, and backs up the original (<file>.supbuddy-backup next to it).

Auto-install paths

ClientConfig fileTransport
Claude Code~/.claude.json (user) or <project>/.mcp.json (project)HTTP
Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.jsonstdio shim via npx -y @supbuddy/mcp@latest
Cursor~/.cursor/mcp.json (user) or <project>/.cursor/mcp.json (project)HTTP
Codex CLI~/.codex/config.toml (adds an [mcp_servers.supbuddy] block)HTTP
Windsurf~/.codeium/windsurf/mcp_config.jsonHTTP

Tier gating for MCP tools

Free tier:

  • Read tools (list_mappings, list_projects, get_health, etc.) — full access.
  • read_env_file — keys only, values redacted.
  • tail_request_logs, watch_audit_log — request bodies stripped.
  • 1 connected MCP client at a time. A second client connecting will be rejected with multi_client_on_free.
  • All write/destructive tools are blocked (tier_locked).

Pro tier:

  • Full read access (env values included, request bodies included).
  • Write tools enabled: create_mapping, delete_mapping (soft-delete), register_project, update_project, start_supabase, stop_supabase, restart_supabase, start_compose, stop_compose, restart_compose, read_env_file, write_env_file, write_supabase_config.
  • Multiple MCP clients can connect simultaneously.

Plan / apply for destructive tools

Tools that delete or mutate state (delete_mapping, delete_project, write_env_file, etc.) return a plan with a preview. The MCP client (or you, in the Activity panel) explicitly calls apply with the plan_id to execute. Plans expire after 5 minutes if not applied. Soft-deletes go to the Trash and are recoverable for 7 days.

Per-project AI context sync

Each project has a Context sync — AI tools panel, accessible via the AI Tools tab in the project card, that writes a project-scoped briefing to disk so AI agents working in that repo see your live mappings, services, and isolation state without having to ask. Files written:

  • .supbuddy/README.md, mappings.md, services.md, project.md, mcp.md, do-not.md, docs.md. The full live snapshot, regenerated on each sync.
  • AGENTS.md and CLAUDE.md — a small managed block prepended (or updated in place) telling the agent which project this is and pointing it at .supbuddy/.
  • Editor skill files when detected: .cursor/rules/supbuddy.mdc, .claude/skills/supbuddy/SKILL.md, .codeium/windsurf/rules/supbuddy.md, .continue/rules/supbuddy.md, .github/copilot-instructions.md, .idea/supbuddy.md.
  • .gitignore managed block, ignoring: .supbuddy/meta.json (volatile sync state), *.supbuddy-backup-* (rollback snapshots), and the fully-owned per-editor skill files (.cursor/rules/supbuddy.mdc, .claude/skills/supbuddy/SKILL.md, .codeium/windsurf/rules/supbuddy.md, .continue/rules/supbuddy.md, .idea/supbuddy.md). The rest of .supbuddy/ is intended to be committed; AGENTS.md, CLAUDE.md, and .github/copilot-instructions.md are also kept committable since you may have hand-written content there alongside Supbuddy's managed block.

Sync modes per project:

  • Auto — Supbuddy regenerates the files whenever mappings, services, or project state change.
  • Manual only — files are only written when you click Sync now (or use the tray's Sync AI context for all projects).
  • Off — nothing is written.

The collapsed header shows an at-a-glance status pill: mode (auto / manual / off), a colored dot for the last sync result, and a relative timestamp. Disabled targets (e.g. an editor whose folder isn't present) appear greyed out in the Detected targets list inside the panel.

Settings reference

Open Settings via the gear icon top-right or by clicking the tray icon → Open Dashboard → gear. Four tabs.

General

  • Theme — dark or light.
  • Auto-start at login — registers Supbuddy as a macOS login item. Default: on.
  • Default TLD — applied to new auto-generated mappings. Existing mappings are renamed to the new TLD on save. Default: test.
  • Default isolationhost or vm for newly added projects. Default: host.
  • Auto-subdomain mapping — when on, services and apps detected during a project scan get mappings created automatically. Default: on.
  • Default VM config — CPU / memory / disk for new isolated DinDs.

Network

  • HTTP port — default 8080.
  • HTTPS port — default 8443.
  • DNS port — default 5353.
  • Port forwarding — when on, adds a pfctl rule mapping 80→HTTP port and 443→HTTPS port. Asks for sudo once.
  • LAN sharing — binds Caddy to 0.0.0.0 + starts mDNS responder.
  • Tailscale — paste a tailnet API key to enable split-DNS push.
  • Install / Uninstall CA — installs Caddy's root cert into your Keychain. Uninstall is not yet implemented (manual remove via Keychain Access).

Storage

Trash retention (per-kind), DinD volume sizes, image-cache controls.

MCP

  • Clients — list of connected clients with rotate / revoke / set-primary actions.
  • Activity — audit log with Apply/Cancel/Undo on plan rows.
  • Trash — soft-deleted mappings and projects, restorable for 7 days.
  • Settings: server enabled, port (default 9877), audit_cap (default 5000), trash_ttl_days (default 7).

Tray menu

The macOS menu bar tray icon opens a menu with:

  • Status: … — current proxy state (running / idle).
  • DNS Active (:5353) — shown when proxy is running.
  • LAN Sharing (<ip>) — shown when LAN sharing is on.
  • Tailscale (<ip>) — shown when Tailscale is connected.
  • Start Proxy / Stop Proxy — opens the dashboard.
  • Open Dashboard.
  • Sync AI context for all projects — runs the project-context sync engine for every registered project (writes .supbuddy/, CLAUDE.md, AGENTS.md, etc.).
  • Show Logs — reveals main.log in Finder.
  • Check for Updates... — manual update check (only enabled in packaged builds).
  • Quit.

File locations

All under ~/Library/Application Support/Supbuddy/ on macOS:

  • main.log + main.log.1 — app logs (rotates at 2 MB).
  • state.json — persistent state (projects, mappings, settings, MCP clients, license).
  • caddy-data/ — Caddy's data dir (PKI, autosaves, certs).
  • caddy-data/caddy/pki/authorities/local/root.crt — the local CA cert installed in your Keychain.
  • Caddyfile — generated reverse-proxy config.
  • certs/ — legacy CA from the pre-Caddy era (unused in current builds).

MCP-specific:

  • MCP client tokens (encrypted via Electron's safeStorage): ~/.config/Supbuddy/mcp/<client-id>.bin
  • MCP audit log: under ~/Library/Application Support/Supbuddy/, capped at audit_cap entries (default 5000).

Troubleshooting

Browser shows "Not secure" or certificate warning

The Caddy CA is not trusted. Open Settings → Network → Install Certificate. macOS will prompt for your password. After install, fully restart your browser (Cmd+Q, not just close window). Verify: Keychain Access → System keychain → search for "Caddy Local Authority".

"Docker is not running. Please start Docker Desktop."

Compose and isolated-mode features need Docker. Open Docker Desktop and wait until the whale icon stops animating.

"Docker Compose is not installed"

Compose v2 ships inside Docker Desktop. If you removed Docker Desktop and are using a standalone Docker daemon (e.g. Colima, Rancher), install compose: brew install docker-compose.

Project is in isolated mode but services show stopped after restart

On boot, Supbuddy reconciles each project's isolation state with the actual DinD containers on Docker. If your DinD container was removed (e.g. docker system prune -a), the project is automatically demoted to host mode. Re-toggle to isolated to recreate the container; your supabase/ folder is the source of truth so data persists if Postgres' volume survived.

Indicator shows "running" but the VM isn't

In v2.2.x and earlier, a Docker event for an unrelated host-mode Supabase container could falsely match a project that's in isolated mode but whose VM is stopped. Update to the latest version. Workaround: stop the host Supabase, then re-toggle the project off/on.

MCP client says "Invalid OAuth error" or "JSON Parse error: Unexpected EOF"

The MCP client is trying OAuth discovery and getting an empty 404. Either the token was lost (regenerate it in Settings → MCP → Rotate) or you're on a build older than the OAuth-probe fix. Update to the latest version; the server now answers OAuth discovery paths with a structured 404 instead of an empty body, and 401 responses include WWW-Authenticate: Bearer so the client doesn't fall back to OAuth.

MCP token disappeared after app restart

Fixed in recent builds. If you're on an older version, regenerate the token. Root cause was that addMcpClient didn't trigger state persistence; the client was held in memory only.

Server Actions return 403 in a Next.js app behind Supbuddy

Next.js's CSRF guard rejects POSTs whose Origin isn't in experimental.serverActions.allowedOrigins. Supbuddy detects this and shows an amber warning on the project — click it for a paste-ready snippet, or hit Apply… in the dialog to preview a unified diff and write the change to next.config directly. After applying, restart your dev server.

Port already in use (8080, 8443, 5353, 9877)

Default ports: HTTP 8080, HTTPS 8443, DNS 5353, MCP 9877. Change them in Settings → Network / Settings → MCP. Find what's holding a port: lsof -i :<port>.

Supabase fails to start in isolated mode

Open the Activity tab and watch the Supabase log stream during start. The most common cause is a config.toml port outside the range Docker is willing to bind on the host (e.g. ports below 1024 without root). Supbuddy reads the [db], [api], [studio], [inbucket] ports from your config.toml — keep them in the 30000+ range to avoid conflicts.

"Cannot start Supabase: isolation is enabled but the VM is not running"

Isolated projects keep supabase start inside their DinD container. If the VM (DinD) isn't running, Supbuddy refuses to fall back to host-mode supabase start — host mode binds raw ports 54321/54322 directly, which collides with Caddy's L4 DB-port pinning that fronts your other isolated projects on 54322–54399. Start the isolated environment first (project header → toggle on) and Supabase will start inside it. The Supabase ▶ button is also disabled for isolated projects whose VM is stopped.

Wipe everything and start over

Quit Supbuddy, then:

# Stop and remove all DinD containers
docker ps -a --filter "name=supbuddy-" -q | xargs -r docker rm -f

# Wipe app data (state, certs, Caddyfile, logs)
rm -rf ~/Library/Application\ Support/Supbuddy

# Wipe MCP tokens
rm -rf ~/.config/Supbuddy/mcp

# Optional: remove the trusted CA
sudo security delete-certificate -c "Caddy Local Authority" /Library/Keychains/System.keychain

FAQ

Is Supbuddy free?

Yes — the free tier lets you register unlimited projects with 5 mappings and 2 active projects at a time, full HTTPS, full DNS, full Supabase isolation, and read-only MCP access (1 client at a time). Pro removes the caps and adds MCP write access. See pricing.

Does Supbuddy send my data anywhere?

No. Caddy, the DNS server, and the MCP server all run locally on your Mac. The only outbound traffic is: license validation (when activating a Pro license), Tailscale split-DNS push (only if you enabled it), auto-update checks (GitHub Releases), and Google Analytics on the marketing site (not the desktop app). The desktop app does not send telemetry.

Can I work offline?

Yes. The app works fully offline once the CA is trusted and projects are registered. License validation runs offline using a cached signed token.

Linux / Windows support?

Not in v2. The app is macOS-only. Some code paths (hosts file, certutil, update-ca-certificates) anticipate Windows and Linux but they are not tested or supported.

How many seats does a Pro license cover?

Each Pro license includes exactly 1 seat by default — Pro Monthly, Pro Lifetime, and Gold all start at one seat. A seat is bound to one Mac at a time; you can move it by deactivating the device on the Account page and activating on another Mac.

Need more than one seat? Click Add extra seat on the Account page when all current seats are in use. Pricing for extra seats: $3/mo per seat (subscription) or $29 one-time (lifetime).

Can I use my own TLD?

Yes. Set any TLD in Settings → General → Default TLD. Supbuddy installs /etc/resolver/<project-domain> files that tell macOS to query our DNS server for that project's domain. Avoid TLDs that actually resolve on the public internet (.com, .net, etc.) — your browser will hit the real site for cached entries.

What happens if I delete a project?

The project moves to the Trash (visible in Settings → MCP → Trash) for 7 days, then is permanently deleted by the sweep timer. Restoring brings back the project record and all its mappings. Note: the underlying DinD container is also stopped on delete; restoring the project does not auto-recreate the container — toggle isolated mode again to do that.

How do I uninstall Supbuddy?

  1. Quit the app.
  2. Drag Supbuddy.app from /Applications to the Trash.
  3. Optional cleanup — see "Wipe everything and start over" above.
  4. Optional: sudo security delete-certificate -c "Caddy Local Authority" /Library/Keychains/System.keychain to remove the trusted CA.

Where do I report a bug?

Email support with your version (visible at the bottom of the Settings popover) and the relevant lines from ~/Library/Application Support/Supbuddy/main.log.