Personal Tailscale tailnet managed entirely in code with Pulumi.
gitops-tailscale/
├── infra/ # Pulumi infrastructure code
│ ├── Pulumi.yaml # Project configuration
│ ├── Pulumi.prod.yaml # Production stack config
│ ├── index.ts # Main entry point
│ ├── acl/ # Access Control Lists
│ │ └── index.ts
│ ├── dns/ # DNS configuration
│ │ └── index.ts
│ ├── keys/ # Auth keys management
│ │ └── index.ts
│ └── settings/ # Tailnet settings
│ └── index.ts
├── .github/
│ └── workflows/
│ └── pulumi.yml # CI/CD pipeline
└── README.md
| Resource | Description |
|---|---|
| ACLs | Access control policies, groups, tag owners |
| DNS | MagicDNS, nameservers, search paths, split DNS |
| Auth Keys | Pre-auth keys for automated device onboarding |
| Tailnet Settings | Device approval, key expiry, network settings |
| Webhooks | Event notifications (optional) |
- Pulumi CLI installed
- Node.js 18+ installed
- Tailscale OAuth client credentials (recommended) or API key
-
Install dependencies:
cd infra npm install -
Configure Tailscale credentials:
# Option 1: OAuth Client (Recommended for GitOps) export TAILSCALE_OAUTH_CLIENT_ID="your-client-id" export TAILSCALE_OAUTH_CLIENT_SECRET="your-client-secret" # Option 2: API Key (simpler but expires) export TAILSCALE_API_KEY="your-api-key"
-
Set your tailnet:
pulumi config set tailscale:tailnet your-tailnet-id -
Preview changes:
pulumi preview
-
Deploy:
pulumi up
- Use OAuth Clients instead of API keys for CI/CD (they don't expire)
- Store secrets in Pulumi config with encryption:
pulumi config set --secret - Restrict API access - Only allow changes through this GitOps workflow
- Enable audit logging in Tailscale admin console
- Use branch protection and require PR reviews for infrastructure changes
# Preview what will change
pulumi preview
# Apply changes
pulumi up
# View current state
pulumi stack output
# Import existing resources
pulumi import tailscale:index/acl:Acl my-acl acl
# Refresh state from Tailscale
pulumi refresh
# Destroy all resources (careful!)
pulumi destroyThis repo includes GitHub Actions workflow that:
- Runs
pulumi previewon all PRs - Runs
pulumi upon merge tomain - Stores state in Pulumi Cloud (free tier)