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

# Tailscale

> Connect Capy to your Tailscale tailnet so project machines can reach private services like databases and internal APIs, without exposing them to the public internet.

Connect [Tailscale](https://tailscale.com/) so agent machines can securely reach private services inside your network: internal databases, staging APIs, package registries, anything on your tailnet. Machines in enabled projects join your tailnet as [ephemeral nodes](https://tailscale.com/kb/1111/ephemeral-nodes) at boot, tagged with a tag you choose, so you control exactly what they can access with your Tailscale ACLs. No public exposure, no static IP allow lists.

## Connect your tailnet

<Steps>
  <Step title="Define a tag for Capy machines">
    In your [tailnet policy file](https://login.tailscale.com/admin/acls), add a `tagOwners` entry for the tag Capy machines will carry. We recommend `tag:capy`:

    ```json theme={null}
    "tagOwners": {
      "tag:capy": ["autogroup:admin"],
    }
    ```

    The tag lets your ACL rules single out Capy machines; the owner list is who may assign it.
  </Step>

  <Step title="Create an OAuth client">
    On the [Trust credentials](https://login.tailscale.com/admin/settings/trust-credentials) page, click **Credential** and choose **OAuth**:

    <Frame caption="Trust credentials → Credential → OAuth">
      <img src="https://mintcdn.com/capy-ai/3mWTb3E7iBLTuT3-/images/tailscale/new-credential-oauth.png?fit=max&auto=format&n=3mWTb3E7iBLTuT3-&q=85&s=4b195695923d72f04363040e6384efb2" alt="Tailscale's new credential page with the OAuth option selected" width="1350" height="956" data-path="images/tailscale/new-credential-oauth.png" />
    </Frame>

    On the Scopes step pick **Custom scopes**, expand the **Keys** section, and check **Write** on **Auth Keys**; the tag picker that appears is required for the write scope — select the tag from the previous step. Leave every other scope unchecked:

    <Frame caption="Keys → Auth Keys → Write, with your tag selected">
      <img src="https://mintcdn.com/capy-ai/3mWTb3E7iBLTuT3-/images/tailscale/auth-keys-scope.png?fit=max&auto=format&n=3mWTb3E7iBLTuT3-&q=85&s=6e1b572a57d6a5a54412ab7ae8348327" alt="The Auth Keys scope with Write checked and tag:capy selected" width="1306" height="932" data-path="images/tailscale/auth-keys-scope.png" />
    </Frame>

    Generate the credential and copy the client ID and secret; the secret is shown once and starts with `tskey-client-`.
  </Step>

  <Step title="Connect Capy">
    Go to [Settings → Integrations → Tailscale](https://capy.ai/settings/integrations/tailscale), click **Connect**, and enter the client ID, client secret, and your tag. Capy validates the credentials against your tailnet before saving, and the secret is stored encrypted. Any org member can connect; each Capy org holds one Tailscale connection.
  </Step>

  <Step title="Enable projects">
    The **Projects** section appears on the same page once connected. Turn on each project whose machines should join your tailnet; machines in other projects never join.
  </Step>
</Steps>

## How it works

When a machine in an enabled project boots or wakes, Capy mints a single-use, short-lived auth key from your OAuth client and the machine joins your tailnet as an ephemeral node named `capy-<machine-id>`, carrying your tag. From that point, agent commands on the machine can reach whatever your ACLs allow.

* **Ephemeral and self-cleaning.** Nodes are removed by Tailscale automatically when a machine pauses or is destroyed; a resumed machine rejoins with a fresh key. Your machine list never accumulates stale entries.
* **Never blocking.** A failed join never blocks a machine from booting; the machine runs without tailnet access and rejoins on its next wake.
* **Keys stay on the machine.** Each auth key is minted per machine, expires after five minutes, and is delivered to a root-owned file readable by no other user. Keys never appear in process arguments, environment variables, or logs, and never persist into machine snapshots.

## Controlling access with ACLs

Capy machines have exactly the access your ACL grants their tag, and nothing else by default. For example, to let agents reach a private Postgres instance, declare its address as a host alias and accept traffic to its port:

```json theme={null}
"hosts": {
  "internal-db": "100.101.102.103",
},
"acls": [
  { "action": "accept", "src": ["tag:capy"], "dst": ["internal-db:5432"] },
]
```

A destination can be a `hosts` alias, a Tailscale IP or CIDR, or another tag (for example `tag:database:5432` if your database nodes are themselves on the tailnet).

Scope the tag's rules to the specific services agents need; there is no reason for Capy machines to see your whole tailnet.

## Disconnecting

Turning a project off stops its machines from joining at their next boot or wake. Disconnecting the integration (the **⋯** menu on the connection card) removes the stored credentials and stops all new joins immediately.

Either way, already-running machines keep their tailnet session until they pause or stop. To cut one off immediately, remove its node from your [Tailscale machines list](https://login.tailscale.com/admin/machines), and revoke the OAuth client in Tailscale if the credentials themselves should die.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Connecting fails with an invalid credentials error">Capy verifies the client by minting and immediately revoking a probe key. Check that the OAuth client has **Keys → Auth Keys** write scope, that the tag on the scope matches the tag you entered, and that the tag exists in your policy file's `tagOwners`.</Accordion>
  <Accordion title="The connection shows Reconnect needed">Your tailnet definitively rejected the credentials, usually because the OAuth client was revoked or the tag was removed from `tagOwners`. Machines keep booting without tailnet access until you reconnect with working credentials.</Accordion>
  <Accordion title="A machine isn't in my tailnet">Check that its project's toggle is on; only machines booted or woken after enabling join. A machine whose join failed rejoins on its next wake, so pausing and resuming it retries immediately.</Accordion>
  <Accordion title="Machines join but can't reach my service">That's an ACL question: nodes tagged with your tag need an `accept` rule to the service's host and port. Test from the machine with `tailscale ping <host>`.</Accordion>
  <Accordion title="Do machines have stable IPs I can allow-list instead?">No; machine infrastructure uses dynamic addresses. The tag is the stable identity, which is exactly what the ACL rules key on.</Accordion>
</AccordionGroup>
