Pulumi
Infrastructure-as-Code for DNS and Tailscale ACL management. Two independent projects, both using the Python SDK with uv toolchain.
Projects
| Project | Stack | Source | Manages |
|---|---|---|---|
blumeops-dns | eblu-me | pulumi/gandi/ | DNS records for eblu.me via Gandi LiveDNS |
blumeops-tailnet | tail8d86e | pulumi/tailscale/ | ACL policy, device tags, auth keys |
DNS (blumeops-dns)
Manages *.ops.eblu.me wildcard and base records pointing to indri’s Tailscale IP, plus public CNAME records for services routed via flyio-proxy.
Tailnet (blumeops-tailnet)
Manages the ACL policy (policy.hujson), device tags for indri and sifaka, and auth keys for the Fly.io proxy.
CLI Patterns
All operations use mise tasks that wrap pulumi with the correct stack and working directory:
# DNS
mise run dns-preview # Preview DNS changes
mise run dns-up # Apply DNS changes
# Tailscale
mise run tailnet-preview # Preview ACL/tag changes
mise run tailnet-up # Apply ACL/tag changesAuthentication
- Pulumi state: Pulumi Cloud (app.pulumi.com), which also encrypts stack secrets. Every
task sources
mise-tasks/_pulumi_env, which exportsPULUMI_ACCESS_TOKENfromop://blumeops/Pulumi/access-token(unless it is already set) and pointsPULUMI_CREDENTIALS_PATHat a temp dir removed on exit, since pulumi writes any token it uses tocredentials.json. No host needs a storedpulumi login, and none keeps the token. Runningpulumiby hand outside the tasks stores the token in~/.pulumi/credentials.jsonunless you setPULUMI_CREDENTIALS_PATHthe same way. - Provider credentials: The Tailscale OAuth client and the Gandi PAT no longer go through
the mise tasks. The stacks import Pulumi ESC environments (
eblume/blumeops/tailnet,eblume/blumeops/dns), which fetch them from thepulumi-esc1Password vault at open time via thepulumi-escservice account (read-only on that vault). Rotating a credential in 1Password takes effect on the next run; no sync step. - Pulumi ESC: Definitions live in
pulumi/esc/;mise run pulumi-esc-synccreates/updates the environments and sets the service-account token (read fromop://blumeops/pulumi-esc service account/token) as a secret ineblume/blumeops/onepassword. If Pulumi Cloud’s ESC is down, the stacks don’t run anyway (state lives there); if ever lost, the environments are re-creatable frompulumi/esc/.
Stack state backup
A daily CronJob (pulumi-stack-backup on ringtail) exports both stacks’ state
(pulumi stack export --show-secrets) to a PVC, which borgmatic ferries off and archives nightly (see borgmatic).
Config isn’t exported: it’s in git and ESC. The exports contain plaintext
secret values; the encrypted borg repositories are the compensating control. Restore procedure: restore-pulumi-state.
Related
- manage-eblu-me-dns — DNS records workflow
- rotate-gandi-pat — Rotate the Gandi PAT
- update-tailscale-acls — ACL editing and Pulumi workflow
- gandi — DNS hosting
- tailscale — Tailnet configuration
- routing — How DNS records map to services
- restore-pulumi-state — Restore Pulumi stack state from the borg backups