← Portal Domain Docs

Deployment

Staging deploys itself; production is deliberately manual. The whole flow is GitOps: what runs in a DC is whatever its Git-tracked Helm values say.

Two halves, one image

The Portal buildflow GitHub Actions workflow in ts-polaris builds two images and stitches them:

PR gates

Before merge: lint (oxlint + ESLint), unit tests, and the Portal DB Migration Gate, which boots a real MySQL 8 and Redis 7 in CI and validates the Sequelize migrations against them. A branch that is behind master is refused a build unless explicitly forced.

Staging: automatic, with a self-cleaning escape hatch

Production: manual GitOps, four locks deep

  1. PR the tag. An engineer updates the target DC's env/<dc>/kubernetes/helm/lf-portal-express/version.yaml with the new ECR tag; codeowners review and merge. Production tags are never updated automatically.
  2. Deployment window. Production deploys happen in scheduled windows; an out-of-band hotfix must be announced and approved in Slack (#deployment, #production_issues).
  3. Okta privilege elevation. Nobody holds standing ArgoCD write access to production. The engineer requests temporary elevation for that DC through the Okta Access Requests app in Slack.
  4. Manual ArgoCD Sync. After the merge, ArgoCD shows the app Out of Sync; the engineer clicks Sync. ArgoCD first runs the presync migration Job (Sequelize migrations against the DC's MySQL, with locking), then performs the rolling update of the pods.
Mental model: CI produces immutable images; Git holds the desired state per DC; ArgoCD reconciles the cluster to Git. Staging shortens the loop by editing Git for you; production keeps every edit human.
Open full screen ↗

Next: Infrastructure - what a DC actually is and who runs it.