> ## Documentation Index
> Fetch the complete documentation index at: https://getfloo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Reach private hosts over Tailscale

> Connect a floo app to databases and services on your tailnet with one floo.app.toml block and one secret.

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.

```toml partial theme={null}
[network.tailscale]
auth_key_secret = "TAILSCALE_AUTHKEY"
forward = ["db.corp.internal:5432"]
```

* `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:

   ```bash theme={null}
   pbpaste | tr -d '\n' | floo env set TAILSCALE_AUTHKEY --stdin --secret --app my-app --env dev
   ```

   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:

```bash theme={null}
printf '%s' "postgres://app_user:$CORP_DB_PASSWORD@127.0.0.1:5432/app" | floo env set CORP_DB_URL --stdin --secret --app my-app --env dev
```

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:

```ts theme={null}
import { ProxyAgent, fetch } from "undici";

const tailnet = new ProxyAgent(process.env.FLOO_TAILNET_PROXY!);
const res = await fetch("http://reports.corp.internal:8000/health", { dispatcher: 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:

```json theme={null}
{
  "grants": [
    { "src": ["tag:floo"], "dst": ["tag:corp-db"], "ip": ["tcp:5432"] }
  ]
}
```

The auth key stays with floo's Tailscale sidecar. Your app container never receives it, and floo never logs it.

## Ports and resources

| Port | Used by |
| - | - |
| `1056` | The tailnet HTTP proxy |
| `1057` | The sidecar's health check |
| `8080` | floo's cron and migration runtime |

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:

```bash theme={null}
floo redeploy --app my-app --rebuild
```

`--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.

```bash theme={null}
floo logs query --app my-app --since 15m --search floo-tailnet
```

## Next steps

* [Networking and egress](https://getfloo.com/docs/guides/networking.md)
* [Environment variables](https://getfloo.com/docs/guides/environment-variables.md)
* [Troubleshooting](https://getfloo.com/docs/guides/troubleshooting.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.