Your two deploy pipelines compared
GitLab CI → SSH → compose vs GitHub Actions → OIDC → AKS. Two philosophies, both defensible.
Know your own system
The side-by-side, with the specific mechanisms that make each work.
Traverse: GitLab CI → SSH → docker-compose
Pipeline (.gitlab-ci.yml): build → test (with ci-setup-db.sh replaying real migrations) → deploy job over SSH to ubuntu@155.248.241.153 → scripts/deploy-remote.sh:
rm docker-compose.yml && git checkout -f && git fetch && git reset --hard FETCH_HEAD docker-compose up -d --build # --build is load-bearing (dist is baked, src is mounted) health gate: 20 × 5s fetch /api/health/db
Why it's sound: the host is treated as disposable and stateless (git is truth; state lives in volumes/S3), the deploy is idempotent (reset + rebuild reproduces the commit exactly), the health gate makes failure loud (pipeline red, not silent breakage), and migrations gate the container start (Day 34). The known traps documented in the script itself: --build omission serves stale code; git stash -u would delete wal-archive/ and break WAL shipping. Documented traps are a maturity marker — the next operator doesn't rediscover them at 2 a.m.
Weaknesses to own: SSH keys on the runner (a long-lived secret — OIDC-style short-lived credentials would be better), single host (deploy = downtime window), and no canary/gradual rollout.
Zoth: GitHub Actions → OIDC → ACR → AKS
Pipeline (.github/workflows/): build & push images to ACR tagged by commit SHA → az login via OIDC federated credentials (app registration zoth-github-actions, no stored Azure secrets — the security win to highlight: nothing to rotate, nothing to leak) → kubectl apply of the Kustomize prod overlay with kustomize edit set image ...:<sha> → kubectl rollout status wait → in-cluster verification (curl the health endpoints from inside the cluster, so the check tests the real network path, not a laptop's port-forward).
Why it's sound: declarative desired state (Kustomize overlays: base + prod image/requests/env), immutable image tags by SHA (no latest ambiguity, and rollback = apply the previous SHA — a real advantage over traverse's rebuild-based rollback), keyless auth (OIDC), and rollout status as the gate (K8s tells you if pods actually became ready). Plus probes (Day 22) mean a bad rollout self-heals and reports.
Weaknesses to own: no automated rollback trigger (rollout failure requires a human to re-apply the previous SHA — the fix is kubectl rollout undo in the failure path or Argo Rollouts), single-node cluster (no real blue/green), and no canary.
The comparison table to recite
| Traverse | Zoth | |
|---|---|---|
| Trigger | GitLab CI | GitHub Actions |
| Auth | long-lived SSH key | OIDC federated (no secret) |
| Artifact | built on host | image by SHA in ACR |
| Config | compose file in git | Kustomize overlay + ConfigMap |
| Gate | HTTP health poll | rollout status + in-cluster curl |
| Rollback | git reset + rebuild (slow, downtime) | apply previous SHA (fast, but manual) |
| Downtime | minutes | rolling, probe-gated |
The synthesis line: "Traverse optimizes for a $6 VPS with one operator: git-as-truth, rebuild, health-gate. Zoth optimizes for declarative infrastructure: immutable images, keyless auth, rollout gates. Both treat the host as disposable — which is the actual principle; the mechanisms differ with the substrate."
Bulletproof your resume
Say these answers aloud before moving on
These are the follow-ups an interviewer will actually ask about your projects.
Why is OIDC federation better than storing cloud credentials in CI?
Because there's no long-lived secret to leak or rotate: GitHub mints a short-lived OIDC token per workflow run, Azure validates it against a federated credential on the app registration, and the resulting access token expires in minutes. A stolen runner or leaked environment variable yields nothing durable. In traverse, by contrast, the SSH key is a long-lived secret on the runner — the honest comparison is that traverse's pipeline is simpler to reason about but has a standing credential, and I'd move it to short-lived certificates or a pull-based agent if it were production-critical.
What's your rollback story in each system?
Zoth: apply the previous image SHA through Kustomize — immutable tags make this exact and fast (and kubectl rollout undo handles the in-progress case). Traverse: git reset --hard <prev-sha> + rebuild + health-gate — correct but slower, with a downtime window. Both share the same caveat: the database isn't rolled back, so rollback safety depends on migrations being forward-compatible — which is why expand/contract is my pre-req for any destructive change. The gap in both: no automatic rollback on failed health gate; a human reads the failure and acts.
What does 'the host is disposable' actually require?
That no truth lives on the host: code from git, state in volumes or object storage, config from env/K8s objects, and secrets injected at runtime. Traverse enforces it with git reset --hard (any drift is erased) and by keeping wal-archive/ on a volume rather than the tree; zoth enforces it by having no host state at all (images + ConfigMaps + PVCs). The payoff: a deploy is reproducible from a commit, a rebuild after disaster is mechanical, and 'what's running?' has a single answer — the commit SHA.