Every service in this platform gets its own Tailscale node via a sidecar container. This gives each service a private MagicDNS hostname (e.g. searxng.your-tailnet.ts.net), automatic HTTPS via Tailscale Serve with Let's Encrypt certificates, and zero port conflicts — ~46 services coexist without any port mapping.
scripts/setup.sh automates most of the Tailscale setup (installing the CLI, joining the tailnet, seeding credentials). But four things must be configured manually in the Tailscale admin console first.
Go to https://tailscale.com and sign up with any SSO provider. The free tier supports 100 devices and 3 users — more than enough for this platform.
If you already have a Tailscale account, skip to step 2.
Every container sidecar runs --advertise-tags=tag:container. The Tailscale control plane rejects registration unless this tag is declared in the access control policy.
- Go to https://login.tailscale.com/admin/acls/file
- Find (or add) the
tagOwnerssection and declaretag:container:If a{ "tagOwners": { "tag:container": ["autogroup:admin"] } }tagOwnersblock already exists, add the"tag:container"line to it.autogroup:adminmeans anyone who can administer the tailnet — for a personal tailnet, that's you. - Click Save
Without this: auth key generation fails with "tag:container is invalid" and every container sidecar crashloops with requested tags [tag:container] are invalid or not permitted.
Most commonly missed step. HTTPS Certificates live on a different admin-console page (DNS) from the keys page, so it's easy to skip. Every Tailscale sidecar in this platform uses Tailscale Serve, which cannot function without this setting. If you come back to this doc because your services are unreachable after setup, check this first.
Container sidecars use Tailscale Serve to expose each service as https://<name>.<tailnet>.ts.net. Tailscale provisions these certificates via Let's Encrypt, but only after you opt in.
- Go to https://login.tailscale.com/admin/dns
- Scroll to "HTTPS Certificates"
- Click "Enable HTTPS"
Free, takes effect immediately.
Without this: containers run but HTTPS URLs return nothing. Sidecar logs show: serve proxy: this node is configured as a proxy that exposes an HTTPS endpoint to tailnet... but it is not able to issue TLS certs.
- Go to https://login.tailscale.com/admin/settings/keys
- Click "Generate auth key"
- Settings:
- Reusable: ON — all ~46 sidecars register with the same key
- Ephemeral: OFF
- Tags: check
tag:container(must have completed step 2 first) - Expiration: up to 90 days
- Copy the key (starts with
tskey-auth-)
Alternatively, you can use an OAuth client credential (tskey-client-...) which does not expire. See the README Credentials section for that approach.
The API token is used by setup.sh for preflight checks that catch common misconfigurations before any container starts. It's also used by the web admin's Tailscale health panel.
- Go to https://login.tailscale.com/admin/settings/keys (same page)
- Scroll to "API access tokens" and click "Generate access token"
- Expiration: up to 90 days
- Copy the token (starts with
tskey-api-)
Once you have your auth key and API token, setup.sh takes care of:
- Tailscale CLI — installs via
curl -fsSL https://tailscale.com/install.sh | shif not already present - Tailnet join — runs
tailscale up --authkey=... --hostname=$(hostname) --ssh --accept-routes - Domain detection — auto-detects
TS_DOMAIN(e.g.tail1234.ts.net) fromtailscale status --json - Credential storage — seeds
TS_AUTHKEY,TS_API_TOKEN, andTS_DOMAINinto Infisical - Preflight checks — validates ACL tag, auth key validity, and HTTPS availability via
scripts/lib/tailscale-preflight.js
Provide credentials to setup.sh in one of three ways:
# Environment variables (for automation / cloud-init)
TS_AUTHKEY=tskey-auth-... TS_API_TOKEN=tskey-api-... bash scripts/setup.sh
# Interactive prompt (run from a terminal without env vars set)
bash scripts/setup.sh
# Subsequent runs (fetches from Infisical automatically)
bash scripts/setup.shTS_AUTHKEYis stored in Infisical at/shared/TS_AUTHKEY— seeded by setup.sh, rotated via the web admin Configuration tab orinfisical secrets setscripts/all-containers.shfetches it from Infisical at start time viainfisical export --path=/shared- Docker Compose picks up
${TS_AUTHKEY}from the shell environment — no.envfile, never written to disk in plaintext - Each container's Tailscale node identity is persisted at
<first-mount>/tailscale-state/<container-name>/(theTS_STATE_HOST_DIRenv var, generated automatically byscripts/generate-env.js), so containers keep their tailnet identity across restarts
Three networking patterns are used in compose files. Most containers use the sidecar with network pattern: a shared Docker network between the app and its Tailscale sidecar, with Tailscale Serve proxying HTTPS to the app's port. A few (e.g. minecraft) use network_mode: service:ts to expose all ports via the tailnet. Rare containers use network_mode: host for direct LAN access.
See Networking Options in the README for details and trade-offs.
| What | When | Details |
|---|---|---|
| Preflight checks | Before every all-containers.sh --start |
scripts/lib/tailscale-preflight.js validates ACL tag, auth key (reusable, tagged, not expired), and HTTPS availability |
| Cron health check | Every 15 minutes | system-health-check.sh checks Tailscale device connectivity, warns if the auth key expires within 14 days |
| Web admin | On demand | Live Tailscale health panel using the same preflight checks |
| Problem | Symptom | Fix |
|---|---|---|
tag:container not in ACL |
Sidecars crashloop: requested tags [tag:container] are invalid |
Add the tag to your ACL (step 2) |
| HTTPS not enabled | HTTPS URLs return nothing; sidecar logs: not able to issue TLS certs |
Enable HTTPS Certificates (step 3) |
| Auth key expired | Preflight fails; new sidecars won't register | Generate a new key, update via web admin Configuration tab or infisical secrets set |
| Auth key not reusable | Only the first sidecar registers; the rest fail | Generate a new key with Reusable=ON |
| Auth key missing tag | Preflight reports "key does not have tag:container" | Generate a new key with tag:container selected |
| Infisical down | all-containers.sh refuses to start Tailscale containers |
Start Infisical first — it has start order 000 and starts before other containers |
TS_DOMAIN not detected |
setup.sh can't auto-detect domain | Verify host joined the tailnet: tailscale status |
# Verify host is connected to the tailnet
tailscale status
# Check a specific container's sidecar logs
docker logs <container-name>-ts
# Run preflight checks manually (needs TS_API_TOKEN in env)
node scripts/lib/tailscale-preflight.js