Deploy full-stack preview environments for device testing
Context
Testing feature branches means either deploying to production (risky) or testing locally (doesn't catch infra issues). Preview environments give each feature branch its own backend instance with isolated data.
Decision
Per-branch preview environments deployed to the production cluster via Nomad. Each preview gets:
- A Nomad job running the branch's container image
- An isolated PostgreSQL database (created via the deployer role)
- A deterministic Redis database number (derived from branch name hash, 1-15)
- A Tailscale-only DNS entry:
{branch-slug}.preview.ztp.pghale.com
GitHub Actions deploys on successful PR builds and cleans up when PRs close. Xcode Cloud builds point the iOS app at the preview URL via PreviewConfig.swift injection in ci_post_clone.sh.
Branch slugs are normalized (lowercase, 40 char max) for DNS compatibility. OpenTofu workspaces provide isolation — each preview is a separate Terraform workspace.
Consequences
Feature branches are testable on real infrastructure with real data isolation. iOS TestFlight builds can point at feature-branch backends. The whole thing tears down automatically on PR close.
The cost is shared cluster resources (previews run on production nodes behind Tailscale) and the deployer database role needs careful credential management.