Skip to content

Secrets

On Project Loom, Git is the source of truth for everything except secret values. Manifests reference Secrets by name, but the values themselves never land in the repo. There are three kinds of secret on the cluster.

flowchart TD
  subgraph InGit[in Git: names + wiring only]
    Ref[manifests reference<br/>secrets by name]
  end
  CNPG[CNPG operator] -->|auto-generates| G1[(&lt;app&gt;-pg-app<br/>keycloak-pg-app<br/>infisical-pg-app)]
  You([you, in a browser]) -->|type a value| Infisical[Infisical]
  Infisical -->|synced by the K8s operator| G2[(&lt;app&gt;-api-kc<br/>owned end-to-end)]
  Hand[you, via kubectl] -->|out of band, bootstrap only| G3[(infisical-secrets<br/>&lt;app&gt;-pg-backup-creds<br/>keycloak-config-cli-sa)]
  Ref -.secretKeyRef.-> G1
  Ref -.envFrom.-> G2
  Ref -.secretKeyRef.-> G3

When you declare a CNPG Cluster, the operator mints the DB credentials and writes the <cluster>-app Secret. You never create these and never see the password — you just reference the generated key:

- name: DB_URL
valueFrom:
secretKeyRef:
name: <app>-pg-app # created by CNPG, not by you
key: uri

keycloak-pg-app and infisical-pg-app work the same way for their respective databases. See Operators.

2. Infisical-managed (the default for app secrets)

Section titled “2. Infisical-managed (the default for app secrets)”

Every app’s runtime env vars — secret or not — are managed through Infisical, a self-hosted GUI. You edit a value in the browser; the Infisical Secrets Operator syncs it into a Kubernetes Secret the app already reads via envFrom. No kubectl, no redeploy for a value change — the Rollout just needs its pods to restart to pick it up (bump the redeploy annotation, or let the next natural rollout do it).

envFrom:
- secretRef:
name: <app>-api-kc # owned end-to-end by the secrets operator

Three CRs wire an app’s namespace to Infisical — InfisicalConnection (where the Infisical server is), InfisicalAuth (a Machine Identity credential, Universal Auth), and InfisicalStaticSecret (which project/environment to sync, and into what Secret name, creationPolicy: Owner). Full recipe: Onboard → Secrets.

Not every env var belongs in Infisical. Two exclusions, both deliberate:

  • Anything an operator mints and rotates on its own (DB_URL from CNPG) — a copy in Infisical would silently go stale the moment CNPG rotates it.
  • Anything coupled to the Kubernetes manifest itself — a port that must match containerPort and the Service’s targetPort has no business being editable from a GUI independent of those two YAML fields; keep it hardcoded, next to them, in one file.

What’s left after Infisical is small: the credentials needed to bootstrap Infisical and Keycloak’s automation themselves — chicken-and-egg items nothing else can generate for you. Created by hand, not in Git, recreated on rebuild:

SecretNamespaceWhat it holds
infisical-secretsinfisicalInfisical server’s own AUTH_SECRET/ENCRYPTION_KEY/DB_CONNECTION_URI/REDIS_URL
infisical-<app>-identity<app>-staging, <app>-prodMachine Identity clientId/clientSecret per env
keycloak-config-cli-sakeycloak-staging, keycloak-prodconfig-cli service-account (see Operators)
<app>-pg-backup-credsmatching <app>-<env> / keycloak nsOracle Object Storage S3 keys for that Cluster

For the backup credentials, the access key is clean hex; the secret key contains +/= — don’t swap them, or a / ends up in the SigV4 credential and breaks request signing.

Manifests carry names and references, never values. This keeps the private loom repo safe to read. A new node is Provision (Git + GUI values + a handful of bootstrap Secrets). Restore after a live cluster: Restore.