Secrets
Values live in the Infisical GUI. Git only stores the three operator CRs that say which
project and environment to sync. This page is the process for any app — not a list of Penvoice or Talon keys.
Infra-shaped keys (issuer URL, REDIS_URL, S3 endpoint) are the usual first cells; business keys you add when the app
needs them. Full GUI walkthrough: Provision § Infisical.
flowchart LR You([you, in a browser]) -->|edit a value| Infisical Infisical -->|Machine Identity auth| Op[Secrets Operator] Op -->|owns| Sec[(<app>-api-kc)] Sec --> Pods[app Rollout, via envFrom]
1. Create the Project and environment
Section titled “1. Create the Project and environment”In Infisical: Project per app, environments staging and prod. Create a key once, fill both cells. Infra
examples (not an exhaustive app list): issuer URL for that env’s Keycloak, REDIS_URL=redis://<app>-redis:6379. Never
DB_URL / JDBC (CNPG) or the container port.
2. Create a Machine Identity
Section titled “2. Create a Machine Identity”Project Settings → Machine Identities → Create Identity, Universal Auth, scoped read-only to this project’s environment. Note the Client ID and Client Secret — the secret is shown once.
On the node or locally — Access & consoles → kubectl.
kubectl create secret generic infisical-<app>-identity --namespace <app>-staging \ --dry-run=client -o yaml \ --from-literal=clientId="<staging-client-id>" --from-literal=clientSecret="<staging-client-secret>" \ | kubectl apply -f -kubectl create secret generic infisical-<app>-identity --namespace <app>-prod \ --dry-run=client -o yaml \ --from-literal=clientId="<prod-client-id>" --from-literal=clientSecret="<prod-client-secret>" \ | kubectl apply -f ---dry-run=client -o yaml | kubectl apply -f - makes this safe to re-run — e.g. if you mistype a value, just fix it and
run the line again instead of deleting the Secret first.
This Secret is the operator’s own credential — never committed, recreated by hand on rebuild (see Reference).
3. The three CRs
Section titled “3. The three CRs”apiVersion: secrets.infisical.com/v1beta1kind: InfisicalConnectionmetadata: { name: infisical, namespace: <app>-<env> }spec: address: http://infisical-infisical-standalone-infisical.infisical.svc.cluster.local:8080 # in-cluster, not the public ingress---apiVersion: secrets.infisical.com/v1beta1kind: InfisicalAuthmetadata: { name: <app>-auth, namespace: <app>-<env> }spec: method: universal infisicalConnectionRef: { name: infisical, namespace: <app>-<env> } universal: clientIdRef: { name: infisical-<app>-identity, namespace: <app>-<env>, key: clientId } clientSecretRef: { name: infisical-<app>-identity, namespace: <app>-<env>, key: clientSecret }---apiVersion: secrets.infisical.com/v1beta1kind: InfisicalStaticSecretmetadata: { name: <app>-synced-secrets, namespace: <app>-<env> }spec: infisicalAuthRef: { name: <app>-auth, namespace: <app>-<env> } sources: - projectId: "<project-id-from-infisical-ui>" environmentSlug: staging # overlay sets prod on the other stack secretPath: "/" syncOptions: refreshInterval: 60s targets: - kind: Secret creationPolicy: Owner name: <app>-api-kc namespace: <app>-<env>These three files live in workloads/<app>/base/. Overlays patch namespace, environmentSlug, and projectId. Only
the values stay out of Git.
4. Wire the Rollout
Section titled “4. Wire the Rollout”envFrom: - secretRef: name: <app>-api-kc # Infisical-managed — edit values in the UI, not hereWhat stays out of Infisical
Section titled “What stays out of Infisical”Two exclusions, both deliberate — see Secrets (concept) for why:
env: - name: DB_URL # CNPG mints/rotates this on its own; a copy in Infisical would go stale valueFrom: secretKeyRef: { name: <app>-pg-app, key: uri } - name: API_PORT # must match containerPort + the Service's targetPort — keep it in one file value: "8080"Other out-of-band secrets
Section titled “Other out-of-band secrets”Two more items don’t route through Infisical at all — they’re bootstrap credentials for the platform itself, not per-app config:
| Secret | Namespace | What it holds |
|---|---|---|
<app>-pg-backup-creds | <app>-<env> | Oracle Object Storage S3 keys for that env’s Cluster |
For that one, 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.
GHCR: public package vs imagePullSecret
Section titled “GHCR: public package vs imagePullSecret”The cluster has to pull ghcr.io/<owner>/<app>. Two options:
-
Make the GHCR package public (simplest). The image pulls anonymously — no pull secret needed. Set the package’s visibility to Public in GitHub. Good for images with no embedded secrets (these are static binaries on distroless, so nothing sensitive is baked in).
-
Keep the package private + add an
imagePullSecret. Create a docker-registry secret with a GHCR PAT (scoperead:packages) and reference it from the pod spec:On the node or locally.
Terminal window kubectl create secret docker-registry ghcr-pull \--namespace <app>-<env> \--docker-server=ghcr.io \--docker-username=<owner> \--docker-password=<ghcr-pat-read-packages> \--dry-run=client -o yaml \| kubectl apply -f -Safe to re-run — e.g. after rotating the PAT, run this again instead of deleting the Secret first.
spec:template:spec:imagePullSecrets:- name: ghcr-pull
This platform defaults to option 1 for public-safe images. Talon’s package is currently private with no
imagePullSecret configured — a known gap causing
ImagePullBackOff until either the package goes public or option 2 is wired up; see
Troubleshooting.
That’s the full playbook. Back to the recipe or the Architecture overview.