Skip to main content
Add [network.tailscale] to floo.app.toml and every runtime of the app joins your tailnet: web and API services, workers, cron jobs, and the migration command. Your Dockerfile and start command do not change.
partial
  • auth_key_secret names the floo secret that holds a Tailscale auth key.
  • forward lists host:port targets on your tailnet. Each one listens on localhost:<port> inside your app, with the same port.

Set it up

  1. In the Tailscale admin console, open Settings → Keys and generate an auth key with Reusable and Ephemeral on. Give it a tag, such as tag:floo.
  2. Store the key as a secret in each environment that declares the block. Copy the key, then run:
    On Linux, replace pbpaste with xclip -o -selection clipboard. Repeat with --env prod before you release.
  3. Add the [network.tailscale] block, commit, and push.
Each instance joins as its own ephemeral machine, named after the app, environment, and service (for example floo-my-app-dev-web-ccc67a), and leaves the tailnet when the instance stops.

Connect from your code

A forward is a local TCP port. Point any client at 127.0.0.1 and the forwarded port, and keep the database password in a secret:
Forwards carry any TCP protocol: Postgres, MySQL, Redis, SSH, or HTTP. For HTTP services, you can skip the forward and use the HTTP proxy at the URL in FLOO_TAILNET_PROXY (http://localhost:1056). It supports CONNECT and absolute-form HTTP requests, not SOCKS5. floo does not set HTTP_PROXY or HTTPS_PROXY, so only requests you send through the proxy use the tailnet:
Hostnames resolve through your tailnet’s DNS, including MagicDNS. Forward targets accept a MagicDNS name, a fully qualified domain name, or an IPv4 address.

Limit what the app can reach

The app gets whatever access your tailnet ACLs give the key’s tag. Grant tag:floo only the hosts and ports the app needs:
The auth key stays with floo’s Tailscale sidecar. Your app container never receives it, and floo never logs it.

Ports and resources

A forward cannot use these ports or a port one of your services listens on. The deploy fails validation with the conflicting entry named. The sidecar adds 0.5 vCPU and 256 MiB to every instance, on top of your service’s size. Preview environments run pull-request code, so they do not join the tailnet and do not receive the auth key.

When the tailnet is down

Your app starts only after its instance joins the tailnet. If the join fails, the deploy or migration fails with The app could not join its tailnet and Tailscale’s reason, such as invalid key. Your code did not fail. Replace the key, then run:
--rebuild builds your default branch, which declares the tailnet. A plain floo redeploy restarts the last live build. If that build predates your [network.tailscale] block, the deploy goes live without trying the new key. When a host is unreachable after startup, the sidecar logs floo-tailnet: TAILNET_DIAL_FAILED <host:port>. Through the proxy, the request answers 502 with the header X-Floo-Tailnet: unreachable, so your code can tell a tailnet failure from an error in the upstream service.

Next steps