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/tailscaleYou 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 validateThis 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 syncThis 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.