infra/helm/tuist-ops/README.md
Deploys the tuist-ops Phoenix app + its CNPG Postgres + the ESO
ExternalSecrets that feed them. Single deploy, targeted at the production
cluster (same pattern as slack/ — internal Tuist-team tooling lives
alongside the customer workload). Pomerium pods in staging/canary
cross-call this deployment via the tailnet for the policy lookup.
ghcr.io/tuist/tuist-ops:<tag> pod
with Recreate strategy. Reads runtime credentials from the ESO-
synced *-runtime Secret + DATABASE_URL from CNPG's *-app Secret.tailscale.com/expose: true annotation
so Pomerium pods in workload clusters can reach it on the tailnet
as ops.<tailnet>.ts.net.ops.tuist.dev, only routing
/webhooks/slack/*. The Pomerium impersonation policy endpoint
(/api/v1/policy) and any future LiveView paths (/db, etc.) are
reachable only on the tailnet, never publicly.s3://tuist-prod-pg-backups/tuist-ops.tuist-ops-runtime — Slack + Tailscale credentials from TUIST_OPS_BOTtuist-ops-app — SECRET_KEY_BASE from the same 1P itemtuist-ops-backup-credentials — Tigris keys from the shared S3_BACKUP_CREDENTIALSTUIST_OPS_BOT 1P item in the production vault with fields:
tailscale_client_id, tailscale_client_secret, tailscale_tailnetslack_signing_secret, slack_bot_token, slack_approvals_channel_idsecret_key_base — generate via mix phx.gen.secrethttps://ops.tuist.dev/webhooks/slack/slash and the interactivity
request URL to https://ops.tuist.dev/webhooks/slack/interactive.
The bot token and signing secret stay the same as the prior
TAILSCALE_JIT_BOT item — same Slack app, just a different host.users:read scope (only).
The previous client (which had policy_file:write) can be deleted
since the ACL is no longer mutated at runtime.ops.tuist.dev pointing at
the production cluster's ingress-nginx external IP.infra/helm/platform/; same operator the main Tuist server's CNPG cluster uses). No bootstrap needed.Built, pushed, and deployed by .github/workflows/tuist-ops-deployment.yml
on every push to main that touches tuist-ops/**,
infra/helm/tuist-ops/**, or the workflow itself. Two tags per build:
ghcr.io/tuist/tuist-ops:sha-<first-12-of-commit>ghcr.io/tuist/tuist-ops:latestThe deploy job pulls the production kubeconfig from the
kubeconfig: tuist-production document in the tuist-k8s-production 1P vault
(via the OP_SERVICE_ACCOUNT_TOKEN repo secret, scoped to the
server-k8s-production GitHub environment — same env slack-deployment uses)
and runs helm upgrade --install with --atomic --wait --timeout 10m.
PR-side CI (format / credo / test) lives in
.github/workflows/tuist-ops.yml and runs on every PR. No image
build there; the deployment workflow handles both.
helm upgrade --install tuist-ops infra/helm/tuist-ops \
-n tuist-ops --create-namespace \
-f infra/helm/tuist-ops/values-managed-production.yaml \
--set image.tag=sha-<known-good-12-char-sha> \
--kube-context tuist-production
Or trigger the deployment workflow with a specific image_tag input
via gh workflow run tuist-ops-deployment.yml -f image_tag=sha-<sha>.