GitOps install (ArgoCD)
The GitOps path deploys the same product as the helm install, but ArgoCD continuously reconciles it from a per-environment Git repo, secrets come from Vault → External Secrets Operator (never in git), and the product runs the released OCI chart pinned to a version. Identity is declarative and reproducible — a rebuilt cluster comes back with the same admin + SSO.
When to use this
Choose GitOps when you want continuous drift-correction, an audit trail of every change, and credentials that never touch git. For a straightforward one-shot install, the helm path is simpler. Both are production paths and share the same identity model.
What you'll create
- A per-environment GitOps repo — the desired state for this cluster: the app-of-apps, the OCI-chart pin, vendored CRDs, and per-environment values. You scaffold it with the
customer:initgenerator. - A bootstrap — the one imperative step that stands up ArgoCD + Vault + a few operators, seeds Vault from your secrets, and points ArgoCD at your repo. ArgoCD deploys everything else.
Prerequisites
- A conformant Kubernetes cluster (see Requirements) with a default StorageClass and DNS for
*.your-domain. - The product tooling checked out (the
Taskfile+bootstrap/machinery that performs the bootstrap). - A Git host for your environment repo + a read-only deploy key ArgoCD can use (or use the bundled in-cluster Git mirror for an air-gapped / no-external-repo install).
1. Scaffold your environment repo
CUSTOMER=acme DOMAIN=gw.acme.com \
REPO_URL=git@github.com:acme/aigw-gitops.git \
HA=true ACCESS_MODE=lb STORAGE_CLASS=<your-default-sc> DNS_ZONE=<your-dns-zone> \
TLS_MODE=letsencrypt \
task customer:initThe knobs bake your environment's shape into the generated repo (validated on GKE, zonal + regional):
| Flag | Effect |
|---|---|
HA=true | false | HA (3× DB/Redis, 2× control + data plane) vs standalone (1 each) |
ACCESS_MODE=lb | tunnel | LoadBalancer + DNS A-records vs Cloudflare Tunnel |
STORAGE_CLASS=<sc> | your cluster's real default RWO class (standard-rwo on GKE, etc.) — else the bootstrap PVCs hang Pending |
DOMAIN / DNS_ZONE | your gateway domain (baked at scaffold time) + the DNS-01 zone |
DOMAIN must be final at scaffold time
Set the customer's real domain now. Changing baseDomain after the platform is up leaves the gateway's governance plugins scoped to the old domain → /v1 requests 404 until a clean re-reconcile.
This writes a ready-to-commit repo under ./gitops-acme/:
config.yaml— the one file that defines this environment (domain, repo URL, TLS mode, namespace prefix).version.yaml— the released component matrix + the OCI product-chart pin.platform/+platform-config/— the ArgoCD app-of-apps + ESO/namespaces.platform-values/— foundation config re-rendered for your domain.- vendored
manifests/crds/gateway-api+vendor/charts/mimir-standalone(the non-OCI deps). secrets.example.yaml+ aREADME.
Commit and push it, then add the deploy key to ArgoCD's repo credentials.
2. Fill in secrets
Copy secrets.example.yaml to a git-ignored secrets.yaml and fill it — provider keys, OIDC client secret, database passwords, and the identity values:
gateway:
platformAdminPassword: "<initial break-glass admin password>"
bootstrapIdpClientSecret: "<your SSO OAuth client secret>"
# …the rest of the REPLACE_ME values…These are seeded into Vault at bootstrap; ESO then materializes them as Kubernetes Secrets. They never enter git.
3. Turn on the platform admin + SSO
In your environment's product-chart overlay (platform-values/gitops-overrides-prod.yaml in the generated repo — customer:init seeds it with these blocks), enable the declarative identity:
keycloak:
platformAdmin:
enabled: true
username: admin@acme.example # created once into the admin group; console-authoritative after
bootstrapIdp:
enabled: true
providerId: google # google | oidc
clientId: "<your-oauth-client-id>"
hostedDomain: acme.example # optional Google workspace gate3b. Optional: the platform-engineering agent runtime
The platform-engineering chat is powered by a separate, licensed agent runtime. It ships as an independent GitOps app — its own ArgoCD Application, its own namespace, and its own version pin — that you enable only if you license the module. When it's off, nothing about it is deployed and the rest of the product is unaffected.
To turn it on in your environment's config.yaml:
peRuntime:
enabled: true # off by defaultThe runtime container image is a private, licensed artifact hosted on GitHub Container Registry (ghcr.io/opsta/opsta-pe-agent, internal visibility). Every cluster pulls it with an image-pull secret that External Secrets builds from your credentials, alongside the runtime's own LLM and GitHub-App credentials — add them to your git-ignored secrets.yaml:
peRuntime:
ghcrPull: # a read:packages token for the licensed image
username: "<ghcr-username>"
token: "<ghcr-read-token>"
llm: # the LLM endpoint the runtime uses to scaffold
baseUrl: "https://…"
apiKey: "<key>"
githubApp: # the GitHub App that opens the scaffold PRs
appId: "<id>"
installationId: "<id>"
org: "<github-org>"
privateKeyPem: |
<the App private key PEM>These are seeded into Vault at bootstrap; the runtime chart's ExternalSecrets materialize them in its namespace. On upgrade, the runtime is pinned in the component matrix (version.yaml) and promoted like any other component — no separate release step.
4. Bootstrap
One command stands up ArgoCD + Vault + External Secrets + the operators, seeds Vault from your secrets file, adds the read-only deploy key as ArgoCD's repo credentials, and applies the root app-of-apps pointed at your repo. ArgoCD reconciles everything else. It auto-detects a reachable GKCTX_PROD and skips the local-k3d provisioning (validated on GKE), so on your own cluster it just runs the foundation steps against it.
GHCR_USER=<user> GHCR_TOKEN=<token-with-read:packages> \
task gitops:bootstrap:prod \
GKCTX_PROD=<your-kube-context> GCLUSTER_PROD=<cluster-name> \
PROD_REPO_URL=git@github.com:acme/aigw-gitops.git \
PROD_DEPLOY_KEY=~/.ssh/acme-aigw \
GITOPS_SECRETS=./gitops-acme/secrets.yaml \
NS_PREFIX=acme- REGISTRY=ghcr.io STORAGE_CLASS=<your-default-sc>Pass the routing vars as CLI args, and set STORAGE_CLASS
- The
GKCTX_PROD/PROD_REPO_URL/ … routing vars are safest passed as CLI args after the task name (as above), not as leading env vars — onlyGHCR_USER/GHCR_TOKENare env. The task prints a target banner (context / repo / secrets) before it acts — read it to confirm you're aiming at the right cluster. - Set
STORAGE_CLASSto your cluster's real default RWO class, or theargocd-redis/vault-raftbootstrap PVCs hangPending.
ArgoCD then reconciles the whole stack. Watch it converge:
kubectl -n argocd get applications -w5. Verify and sign in
When every Application is Synced / Healthy, point wildcard DNS at the gateway and open https://console.your-domain:
- Sign in with SSO (if you enabled
bootstrapIdp), or - the platform admin —
keycloak.platformAdmin.usernamewith the initial password you set.
Add/remove admins and identity providers in the console afterward — those changes persist across upgrades.
Realm changes need one extra sync
Enabling platformAdmin/bootstrapIdp (or any realm change) updates a ConfigMap the identity importer reads. Because the importer runs before the product app updates that ConfigMap in a combined sync, trigger re-run the identity import after the deploy settles so the change is applied — then verify the admin exists and can sign in. Use the task (it patches the keycloak Application to re-run config-cli against the current realm ConfigMap):
KCTX=<your-kube-context> task gitops:apply-realmAir-gapped
The bundled in-cluster Git mirror removes the need for an external Git host, and the product chart + images can be mirrored into your own registry. See Air-gapped install.
Next steps
- High availability — multi-replica production mode.
- Backup & DR — the self-hosted backup stack.
- Configuration — the full config surface.