Tailscale
How catch gets a tagged identity and how services get their own tailnet nodes.
Yeet uses Tailscale in two places:
- catch exposes host access through an embedded Tailscale node.
- Services can get their own Tailscale identity with
--net=ts.
Catch joins your tailnet, but the local yeet CLI does not run its own
Tailscale client. Normal yeet commands use HTTP and WebSocket connections to
the catch hostname, so your workstation must have network reachability to that
Tailscale address. In the standard setup, install Tailscale on the workstation
and connect it to the same tailnet. Commands that query local tailnet state,
such as yeet list-hosts, also use the local Tailscale client API.
There are two separate questions here: can your workstation reach catch, and what is the caller allowed to do after it reaches catch? Tailscale handles both layers, but they fail differently.
Catch must join your tailnet as a tagged node, usually tag:catch. Catch
rejects user-owned nodes.
Before you run yeet init, set up Tailscale in this order:
- Add a yeet owner tag, such as
tag:yeet. - Let that owner tag own the catch tag, usually
tag:catch. - Let that owner tag own any service tags you plan to use later, such as
tag:app. - Open
Trust credentials->Credential->OAuthin the Tailscale admin console. - Choose
All - Read & Writefor a simple broad credential, or choose custom scopes with Auth Keys write (auth_keys) for least privilege. - For catch-only installs, select
tag:catchdirectly. For future service--net=ts, select the owner tag, such astag:yeet, so the credential can create keys fortag:catchand service tags such astag:app. - Copy the
tskey-client-...OAuth client secret.
Then run yeet init root@<machine-host> and paste that secret when prompted.
Validate the host with yeet status.
Minimal policy shape:
{
"tagOwners": {
"tag:yeet": ["autogroup:admin"],
"tag:catch": ["tag:yeet"],
"tag:app": ["tag:yeet"],
},
"grants": [
{
"src": ["autogroup:admin"],
"dst": ["tag:catch"],
"ip": ["tcp:41548"],
"app": {
"yeetrun.com/app/yeet": [{ "allow": ["read", "manage", "ssh"] }],
},
},
{
"src": ["tag:catch"],
"dst": ["tag:catch"],
"ip": ["tcp:41548"],
},
],
}
The first grant lets Tailscale admins run the first setup and normal admin
commands. See Tailscale Access Grants
for the read, manage, and ssh permissions attached to that grant.
Keep autogroup:admin directly in the grant src; do not put it inside a
custom policy-file group.
The catch node joins as tag:catch. With the least-privilege owner-tag setup,
the OAuth client uses tag:yeet only so it can mint auth keys for tag:catch
and later service tags.
If you are comfortable with broad Tailscale API access, All - Read & Write is
the simplest OAuth scope choice. If you do not plan to use --net=ts for
services, a least-privilege OAuth client can assign tag:catch directly. The
owner-tag pattern is more flexible because the credential can mint the future
service tags you deliberately allowed.
Interactive init prompts for the OAuth client secret during first setup:
yeet init root@<machine-host>
The OAuth secret belongs to catch itself. If the resulting catch node is not tagged, catch refuses connections. The install may have succeeded; the identity still failed.
yeet init root@<machine-host> uses SSH. Normal commands use the catch Tailscale
hostname and require your workstation to be able to reach that tailnet address:
CATCH_HOST=<catch-host> yeet status
yeet --host=<catch-host> status
yeet run <svc>@<catch-host> ./compose.yml
With no saved or explicit host, catch asks Tailscale for the hostname catch.
For more than one catch host, choose a distinct name during setup:
yeet --host=morpheus-catch init root@<machine-host>
If another tailnet device already owns the requested catch hostname, Tailscale
may assign a suffixed name such as catch-1. Use that assigned catch host, or
remove the stale/conflicting Tailscale device and rerun yeet init. Host names
are state too.
Use ts when a service should get its own tailnet identity, tailnet IP, tags,
ACLs, or Tailscale Serve config.
For most services, use svc,ts instead of plain ts:
yeet run <svc> ./compose.yml --net=svc,ts --ts-tags=tag:app
svc,ts keeps yeet DNS, private service-to-service traffic, and ordinary
internet egress through the catch host. It also gives the service its own
Tailscale identity.
Use plain ts only for deliberately tailnet-only services:
yeet run <svc> ./compose.yml --net=ts --ts-tags=tag:app
yeet ip <svc>
Plain ts does not provide ordinary outbound internet by itself. Configure an
exit node when a ts-only service needs internet access:
yeet run <svc> ./compose.yml --net=ts --ts-tags=tag:app --ts-exit=<exit-node>
For an existing Tailscale service:
yeet tailscale <svc> -- set --exit-node=<exit-node>
Make sure the OAuth client saved during catch setup can assign every tag you
pass with --ts-tags. If the credential cannot mint the tag, deployment fails.
Options:
--ts-tags=<tag>sets service tags.--ts-exit=<node>uses a Tailscale exit node.--ts-ver=<ver>selects a Tailscale version for the service.--ts-auth-key=<key>passes a direct auth key for that service.
See Networking for when to choose svc, lan,
ts, or a combination.
Run Tailscale commands for a service identity:
yeet tailscale <svc> -- status
yeet ts <svc> -- debug daemon-logs
Update the yeet-managed service Tailscale binary:
yeet ts <svc> update
yeet ts <svc> update --version=1.95.112
If you intentionally want Tailscale's own upstream update subcommand, pass it
through after --:
yeet ts <svc> -- update
Expose a service over HTTPS on your tailnet:
yeet ts <svc> serve --bg 3000
Serve requires tailnet HTTPS. With --bg, the Serve config persists across
reboots.
When you remove a service, yeet attempts to delete that service's Tailscale device from your tailnet. Cleanup matters because old service identities are old access paths.
- https://tailscale.com/docs/features/access-control/auth-keys
- https://tailscale.com/docs/features/oauth-clients
- https://tailscale.com/docs/features/tags
- https://tailscale.com/docs/reference/syntax/policy-file
- https://tailscale.com/docs/reference/syntax/grants
- https://tailscale.com/kb/1242/tailscale-serve