Kubernetes lifecycle automation for Tailscale/Headscale nodes. Handles automatic cleanup of stale nodes and DNS records on pod restart, and creates new DNS records after VPN registration.
- Cleanup: Removes old Headscale nodes and DNS records before pod startup
- DNS Creation: Automatically creates DNS A records after Tailscale registers with Headscale
- Pre-built Dependencies: Includes kubectl and Python requests library for fast startup
- Idempotent: Safe to run multiple times, handles "already exists" errors gracefully
initContainers:
- name: lifecycle-cleanup
image: registry.core.svc.cluster.local:5000/tailscale-lifecycle:latest
command: ["/usr/local/bin/python3"]
args:
- "/scripts/lifecycle_cleanup.py"
- "--hostname"
- "my-service.example.com"
- "--record-name"
- "my-service"
env:
- name: HEADSCALE_NAMESPACE
value: "core"
- name: CLOUDFLARE_API_TOKEN
value: "your-token"
- name: CLOUDFLARE_ZONE_ID
value: "your-zone-id"
- name: DOMAIN_NAME
value: "example.com"containers:
- name: lifecycle-dns-create
image: registry.core.svc.cluster.local:5000/tailscale-lifecycle:latest
command: ["/usr/local/bin/python3"]
args:
- "/scripts/lifecycle_dns_create.py"
- "--hostname"
- "my-service.example.com"
- "--record-name"
- "my-service"
env:
- name: HEADSCALE_NAMESPACE
value: "core"
- name: CLOUDFLARE_API_TOKEN
value: "your-token"
- name: CLOUDFLARE_ZONE_ID
value: "your-zone-id"
- name: DOMAIN_NAME
value: "example.com"
- name: MAX_RETRIES
value: "30"
- name: RETRY_INTERVAL
value: "10"Smallest image size (~76MB), fastest startup:
docker build -t registry.core.svc.cluster.local:5000/tailscale-lifecycle:latest .
docker push registry.core.svc.cluster.local:5000/tailscale-lifecycle:latestUses Miniforge and environment.yaml (~200MB), better Python ecosystem:
docker build -f Dockerfile.conda -t registry.core.svc.cluster.local:5000/tailscale-lifecycle:conda .
docker push registry.core.svc.cluster.local:5000/tailscale-lifecycle:conda./build-and-push.sh # Builds Alpine version with :latest tag
./build-and-push.sh conda # Builds Conda version with :conda tagHEADSCALE_NAMESPACE: Kubernetes namespace where Headscale is running (default:core)CLOUDFLARE_API_TOKEN: Cloudflare API token for DNS managementCLOUDFLARE_ZONE_ID: Cloudflare zone ID for the domainDOMAIN_NAME: Base domain name (default:example.com)
MAX_RETRIES: Maximum attempts to wait for Headscale registration (default:30)RETRY_INTERVAL: Seconds between retry attempts (default:10)
Runs before pod startup to clean up old resources:
- Deletes all Headscale nodes matching the hostname (online or offline)
- Deletes all DNS A records matching the record name
- Always exits with code 0 (cleanup failures don't block pod startup)
Runs as sidecar to create DNS records:
- Waits for Headscale node to register and come online
- Extracts the assigned VPN IP address
- Creates DNS A record pointing to the VPN IP
- Handles "record already exists" errors gracefully
- Exits after successful creation (or failure after max retries)
MIT