Network model (Tailscale)
Carabase Host is designed around zero public ports. The desktop client reaches the host across a private mesh network that only your devices can see — by default, that mesh is Tailscale.
This page covers:
- Why Tailscale (and what happens if you skip it)
- Installing it on the machine running the host
- Connecting the desktop client
- The localhost-only fallback for local-loop development
Why Tailscale
Section titled “Why Tailscale”The security model assumes the host is never reachable from the public internet. The HTTP listener doesn’t speak TLS, doesn’t require auth on /api/v1/health, and isn’t designed to fend off the kind of background scanning every IPv4 address gets. Putting the host directly on a public IP is not a supported configuration — credentials would leak within hours.
Tailscale gives us a WireGuard-based overlay network where:
- Every device joins the same “tailnet” with a single login (Google/GitHub/Microsoft/email)
- Devices get stable hostnames (MagicDNS) and IPs that don’t change
- Traffic is end-to-end encrypted between devices, not just to a relay
- No port-forwarding, no DDNS, no public DNS records
It’s free for personal use (1 user, 100 devices) and runs as a small daemon.
1. Install Tailscale on the host
Section titled “1. Install Tailscale on the host”On macOS:
brew install --cask tailscaleopen -a TailscaleThe Tailscale app prompts you to sign in — pick whichever identity provider you want this tailnet to live under. After login, the menubar icon turns blue.
2. Get the host’s hostname
Section titled “2. Get the host’s hostname”In a terminal:
tailscale statusYou’ll see a line like:
100.123.45.67 my-mac-mini tailnet-name@ macOS -The my-mac-mini part is your MagicDNS hostname. Combined with your tailnet’s domain (shown in the Tailscale admin console), it becomes a fully-qualified name like my-mac-mini.tailnet-name.ts.net.
That’s the address the desktop client uses. Either form works:
http://my-mac-mini:3000— short MagicDNS name (works between devices on the same tailnet)http://my-mac-mini.tailnet-name.ts.net:3000— fully qualified (more explicit)http://100.123.45.67:3000— raw tailscale IP (works but breaks if the host’s IP changes)
3. Make sure the host binds correctly
Section titled “3. Make sure the host binds correctly”The host defaults to HOST=:: in every .env.<env>.example, which is dual-stack (IPv4 + IPv6) and accepts connections on every interface — including the Tailscale virtual interface. Don’t change this to 127.0.0.1 unless you genuinely want a local-loop-only host (see the fallback section below).
Verify the host is listening on the Tailscale interface:
lsof -i :3000 | grep LISTEN# Look for entries on the tailscale0 interface or a wildcard (*) bind4. Install + connect the desktop client
Section titled “4. Install + connect the desktop client”On the device that will run the desktop client:
- Install Tailscale and sign in with the same identity you used on the host
tailscale statusshould now list both devices- Open the desktop app → Settings → Host Connection → enter the host URL (e.g.
http://my-mac-mini:3000) - The connection indicator should go green within a few seconds
If you see a timeout: try the raw Tailscale IP instead of MagicDNS — MagicDNS resolution can take a moment after a fresh install.
5. Locking it down further (optional)
Section titled “5. Locking it down further (optional)”Tailscale supports ACLs (access control lists) at the tailnet level. For a single-user setup with one host + a few clients, the default “anyone in this tailnet can reach anyone else” is fine. If you start sharing the tailnet with collaborators or family, write an ACL that restricts port 3000 on the host to specific tagged users.
See the Tailscale ACL docs — out of scope here.
Local-loop-only fallback
Section titled “Local-loop-only fallback”If you don’t want Tailscale, you can run Carabase in local-loop-only mode where the host is only reachable from the same machine:
# In .env.dev:HOST=127.0.0.1PORT=3000This works for the Admin SPA at http://localhost:3000/admin/ and for any local development.
It does not work for the desktop client — the desktop is a separate macOS process that needs to reach the host across a network boundary. With localhost-only binding, the only way the desktop can reach the host is if it’s running on the same machine, which defeats most of the point.
If you go this route:
- The “no public ports” guarantee is satisfied trivially (the listener is unreachable from anywhere except
127.0.0.1) - The Admin SPA is still your full configuration surface — every connector, OAuth app, and sync rule is reachable
- Background workers (harvest, sync, dreams, curation) run normally — they’re in-process, not network-bound
This is a perfectly valid mode for headless installations, server deployments where you SSH-tunnel into the box, or developers who never use the desktop client.
What if I want public access anyway?
Section titled “What if I want public access anyway?”You probably don’t, but: put a reverse proxy (Caddy, nginx, Cloudflare Tunnel) in front of the host with TLS + a separate auth layer. Carabase’s own routes do not implement public-internet authentication — they assume the network boundary is the auth layer. Adding TLS termination is necessary but not sufficient.
This is unsupported. The single-tenant assumption (one workspace per host, one user per host) doesn’t translate to a public deployment, and there’s no rate limiting, no per-user authentication, and no audit logging on the routes themselves.
If you’re trying to share access with another person, the right answer is to add their device to your tailnet — not to expose the host publicly.