ADR-083: Tailscale as Private Ingress for Always-On VPS¶
- Status: Accepted
- Date: 2026-05-08
- Authors: Podcast Scraper Team
- Related RFCs: RFC-082
Context & Problem Statement¶
The always-on stack serves an operator-facing api and viewer with TLS expectations, but the project does not want a public internet attack surface on application ports. Home automation, phones, and collaborators should reach services only after tailnet membership and ACL policy.
Decision¶
- Production api and viewer are reached primarily via Tailscale (MagicDNS / tailnet FQDN), not via open public bind to app ports. Hetzner firewall policy remains default deny inbound for the app as described in RFC-082; SSH from the open internet may exist for bootstrap only where the operator explicitly allows it, then preference is tailnet SSH for operations.
- GitHub Actions deploy jobs join the tailnet as
tag:gha-deployerand SSH todeploy@on the target host over the tailnet using dedicated deploy keys (PROD_SSH_PRIVATE_KEY/DRILL_DEPLOY_SSH_PRIVATE_KEY), matching ACL rules such astag:gha-deployer→tag:prod:22andtag:gha-deployer→tag:dr-drill:22intailscale/policy.hujson. - TLS for HTTPS viewer uses Tailscale’s HTTPS story on the tailnet hostname (see RFC-082 and prod runbook); the app does not require a separate public CA front door for hobby operation.
Rationale¶
- Smallest exposed surface — aligns with RFC-082 non-goals (no public open ports for the app).
- Stable operator URLs — MagicDNS names survive IP changes and match mobile bookmark workflows.
- Network-level access control — revoking a collaborator is an ACL change, not a VPS user matrix.
Alternatives Considered¶
- Public reverse proxy + OAuth on every request — Rejected for v1 hobby prod; higher moving parts than tailnet membership for this threat model.
- WireGuard manual only (no Tailscale) — Rejected; Tailscale already encodes mesh identity, DNS, and ACL distribution for the operator tailnet.
- Cloudflare Tunnel without tailnet — Possible future additive; not the chosen primary ingress in RFC-082.
Consequences¶
- Positive: Headless callers (Home Assistant, scripts) use stable tailnet routes; CI deploys without exposing GHCR pull secrets on the public listener.
- Negative: Every operator or automation path needs a tailnet identity; debugging "from a random laptop" requires Tailscale or bastion patterns.
- Neutral: Pre-prod (Codespaces) remains a separate ingress story from prod tailnet hosting.
Implementation Notes¶
- Paths:
tailscale/policy.hujson,infra/terraform/(firewall, Tailscale auth key material),.github/workflows/deploy-prod.yml,.github/workflows/drill-deploy.yml - Docs: PROD_RUNBOOK.md