Deploy¶
How a code change reaches dev and prod. The pipeline is fully GitOps: nothing is kubectl apply'd by hand in steady state.
High-level flow¶
Full release process¶
Backend/web and the mobile app version independently. A merge auto-deploys to dev; prod is gated by a version tag; the mobile app ships most changes over-the-air.
main (app repo)v*devprod (Xavia)v* tag → prod (together). Mobile: JS ships over-the-air (dev channel on merge, prod on promote); native binaries are built locally only when native code changes.Dev pipeline (backend / web)¶
- Merge to
mainin the tomoda app repo. - Cloud Build trigger fires and builds automatically — no approval gate. It runs tests and pushes the image to Artifact Registry
tomoda-dev-repotagged with the commit SHA. - Argo CD Image Updater polls Artifact Registry; on a new SHA it edits the Kustomize image override in this
devopsrepo and pushes a commit. - Argo CD syncs the
tomodaapplication and rolls the backend/webDeploymentintomoda-dev. The new pod passes readiness before the old one terminates.
Prod pipeline (backend / web)¶
Prod is gated by a version tag, and backend + web ship together on it:
- Cut a release tag
vX.Y.Z— it ships backend + web together:git tag v1.2.3 && git push --tags. - Cloud Build prod trigger fires automatically on the matching tag — the tag is the gate.
- Image is pushed to
tomoda-prod-repotagged with the version; Image Updater commits to the prod overlay; Argo CD syncs thetomoda-prodapplication.
Mobile app (native + OTA)¶
Native binaries are built locally and rarely (only on a native/SDK change);
most changes ship over-the-air through the self-hosted
Xavia OTA server. A JS-only merge publishes to
the dev channel (internal testers); a vetted bundle is promoted to prod.
An update reaches only binaries with a matching runtimeVersion, so a native
change means a new local binary. See the app repo's docs/frontend/native/release.md.
Health checks and disruption¶
Every backend pod has readinessProbe and livenessProbe configured against /health. Argo CD waits for the new pod's readiness probe to pass before the rolling update proceeds.
Prod enforces a PodDisruptionBudget with minAvailable: 1. During a rolling update:
- Argo CD spins up the new replica
- Waits for readiness
- Terminates the old replica
Since prod currently runs a single replica (see Scaling to grow it), the PDB means the old pod stays up until the new one is healthy — preventing a brief 503 window mid-deploy.
PDB with single replica
A minAvailable: 1 PDB with replicas: 1 blocks voluntary disruptions (node drains, autoscaler evictions). Before any planned node maintenance, scale the backend to 2 replicas first.
What you commit, what gets generated¶
| Repo | What you commit | What the pipeline writes |
|---|---|---|
tomoda (app) |
Code changes | Cloud Build builds image |
devops (this repo) |
Manifest changes, infra | Image Updater writes Kustomize image SHA |
If you change a manifest directly in devops/k8s/apps/tomoda/overlays/dev/, Argo CD syncs it on the next poll (default: 3 min). Force a sync with argocd app sync tomoda if you need it immediately.
Verifying a deploy¶
# Watch the rollout (both pools share the same image; check whichever rolled)
kubectl rollout status deployment/tomoda-api -n prod
kubectl rollout status deployment/tomoda-async -n prod
# Confirm the running image SHA
kubectl get deploy tomoda-api -n prod -o jsonpath='{.spec.template.spec.containers[0].image}'
kubectl get deploy tomoda-async -n prod -o jsonpath='{.spec.template.spec.containers[0].image}'
# Hit the health endpoint (served by tomoda-api via the ingress)
curl https://api.tomoda.life/health
If anything looks wrong, see Rollback.