Skip to main content

GETTING STARTED WITH TAILSCALE IN SWAMP

In this tutorial, we will pull the Tailscale extension into a Swamp repository and write a behavior that holds a tailnet access policy. Then we will have Tailscale check that policy and read the tailnet's live policy into Swamp.

What we will build

We are going to create one behavior, tailnet-policy, of type @swamp/tailscale/policy-file. It holds a small access policy that lets every member of the tailnet reach their own devices. We will run two methods on it. validate has Tailscale check the policy without saving it. sync reads the policy the tailnet currently uses into Swamp state. Neither method changes the tailnet.

Prerequisites

  • Swamp installed (see Install Swamp)
  • A Swamp repository where you are signed in with swamp auth login (see Your First Automation)
  • A Tailscale tailnet and an API access token for it

Pull the Tailscale extension

The Tailscale behavior types come from the @swamp/tailscale extension. First, we pull it into the repository:

swamp extension pull @swamp/tailscale

You will see output like:

INF extension·pull: Pulling "@swamp/tailscale"@"2026.10.13.1"
INF extension·pull: Identity verified: "@swamp/tailscale"@"2026.10.13.1"
INF extension·pull: Pulled "@swamp/tailscale"@"2026.10.13.1"
INF extension·pull: Extracted 54 files:

The repository now has the @swamp/tailscale behavior types. We use @swamp/tailscale/policy-file.

Write the policy behavior

Now we create a behavior named tailnet-policy from the @swamp/tailscale/policy-file type, as described in Create and Run a Behavior. We give it this global argument:

globalArguments:
  policy: |
    {
      "acls": [
        {
          "action": "accept",
          "src": ["autogroup:member"],
          "dst": ["autogroup:self:*"]
        }
      ]
    }

Notice that policy is a block of text, not nested YAML. The type takes the policy file as HuJSON, the JSON format with comments and trailing commas that Tailscale uses for policy files.

Give Swamp a Tailscale credential

Methods that call the Tailscale API need a credential. In the terminal where we will run Swamp, we set the TAILSCALE_API_KEY environment variable to our API access token. Our behavior does not set the apiKey global argument, so it reads the token from that variable. We also leave tailnet unset, so the behavior works on the tailnet that the token belongs to.

The Storing Secrets tutorial shows how to keep a token like this in a vault.

Check the policy with Tailscale

Next, we ask Tailscale to check our policy:

swamp behavior run tailnet-policy validate

This command sends the policy to Tailscale, which checks it and runs its tests without saving it, and Swamp writes Tailscale's response to the behavior's result data.

The tailnet still uses the same policy as before. validate does not save anything.

Read the live policy

Now we read the policy that the tailnet uses right now:

swamp behavior run tailnet-policy sync

This command reads the tailnet's current policy file from Tailscale and writes it to the behavior's state.

Notice that sync only reads. The type's update method refuses to replace a policy that has changed since the last get or sync. A later update therefore compares against the copy we read with sync.

What we built

We pulled @swamp/tailscale and wrote a tailnet-policy behavior that holds an access policy. Tailscale checked that policy without saving it, and we read the tailnet's live policy into Swamp state. Understanding Tailscale automation in Swamp explains why the policy-file type protects its updates this way.