Skip to main content
Secret values never live in git. They are stored in your secret backend and projected into the cluster by the External Secrets Operator. You configure very few of them by hand.

How secrets are delivered

make seed-secrets  ──▶  secret backend (GSM / AWS / Vault / ...)  ──▶  ESO  ──▶  Kubernetes Secret
   (random values)        (durable source of truth)              (ExternalSecret)   (consumed by pods)
Secret delivery contract showing names in git, secret values in the backend, External Secrets materialization, and runtime Kubernetes Secrets For the default gcpsm backend, make seed-secrets writes the internal random values via gcloud. For any other backend it has no assumed CLI, so it prints the key list plus the ESO provider docs link and you create the values yourself (scripts/seed-secrets.sh). Every ExternalSecret references one store named secret-store, so ESO materialises them as native Kubernetes Secrets at runtime. The backend is the single source of truth, which is why secrets survive a cluster rebuild.

The secrets

SecretSourceNeeded when
litellm-master-key, litellm-salt-keyauto-generated (make seed-secrets)always
vllm-api-keyauto-generatedalways
litellm-db-password, litellm-grafana-ro-passwordauto-generatedalways
n8n-encryption-keyauto-generatedn8n enabled
oauth2-proxy-cookie-secretauto-generatedidentity enabled
dex-admin-password, dex-admin-hashauto-generated (password plus bcrypt hash; password restored to secrets/dex-admin-password)identity enabled
dex-*-client-secret (6)auto-generatedidentity enabled
cloudflare-api-tokenyou providedns.automate with dns.provider: cloudflare
anthropic-api-keyyou providerouting the Anthropic egress provider
hf-tokenyou provideserving a gated Hugging Face model
After make seed-secrets, a typical fork has zero to three secrets left to create by hand. A minimal run (local models, no public DNS) needs none.

Backend access

How the cluster authenticates to the backend itself:
SetupCredential
GKE + Google Secret Manager (default)none, keyless Workload Identity
Off-GKE + GSMone service-account JSON key (secret-store-key), seeded once
AWS / Azure / Vaultthat provider’s ESO auth, per the ESO provider docs
No hosted storethe ESO Kubernetes provider, see below

No hosted secret store

You do not need a cloud secret manager. If you have none (bare metal, or you already manage secrets with Sealed Secrets or kubectl), point the store at the ESO Kubernetes provider, which reads Secrets from an in-cluster source namespace. ESO stays the single mechanism; only the store’s provider block changes.
# platform/external-secrets/config/clustersecretstore.yaml  (rendered as a placeholder when
# secret_backend is non-gcpsm; fill it in once, the resolver will not clobber it)
spec:
  provider:
    kubernetes:
      remoteNamespace: secret-source        # the namespace holding your source Secrets
      server:
        url: https://kubernetes.default.svc
        caProvider: { type: ConfigMap, name: kube-root-ca.crt, key: ca.crt }
      auth:
        serviceAccount: { name: eso-reader, namespace: external-secrets }  # + RBAC: read Secrets in secret-source
You then create the source Secrets in secret-source however you like, including committing them as Sealed Secrets (encrypted, safe in git) that the sealed-secrets controller decrypts in place. Use the secret contract for the exact names and keys. One adaptation to know: a Kubernetes Secret is a key/value map, not a single string, so the Kubernetes provider needs remoteRef.property to name the data key (a hosted string backend like GSM does not). The platform’s ExternalSecrets are single-value, so under this provider add property: to each, matching the data key of your source Secret.

Secret contract

What each ExternalSecret produces. A non-GSM source (Kubernetes provider, Vault, etc.) must supply these keys; the consuming workloads mount the Kubernetes Secret by name regardless of backend.
NamespaceKubernetes SecretKeyfrom backend key
argocdargocd-secretoidc.dex.clientSecretdex-argocd-client-secret
dexdex-secretsDEX_ADMIN_PASSWORD_HASH, OAUTH2_PROXY_CLIENT_SECRET, ARGOCD_CLIENT_SECRET, LITELLM_CLIENT_SECRET, GRAFANA_CLIENT_SECRET, OPENWEBUI_CLIENT_SECRET, TABBY_CLIENT_SECRETdex-admin-hash, then the matching dex-*-client-secret
oauth2-proxyoauth2-proxy-secretsclientSecret, cookieSecretdex-oauth2-proxy-client-secret, oauth2-proxy-cookie-secret
monitoringgrafana-oidcclientSecretdex-grafana-client-secret
monitoringgrafana-datasource-litellmpasswordlitellm-grafana-ro-password
litellmlitellm-secretsmaster, salt, vllm, genericClientSecretlitellm-master-key, litellm-salt-key, vllm-api-key, dex-litellm-client-secret
litellmlitellm-pg-apppasswordlitellm-db-password
litellmlitellm-grafana-ropasswordlitellm-grafana-ro-password
servingvllm-api-keyapi-keyvllm-api-key
experiencekey-portalLITELLM_MASTER_KEYlitellm-master-key
experienceopen-webui-oidcclientSecretdex-open-webui-client-secret
n8nn8n-core-secretsN8N_ENCRYPTION_KEYn8n-encryption-key
external-dns, cert-managercloudflare-api-tokenapi-tokencloudflare-api-token
agentgateway-systemanthropic-api-keyAuthorizationanthropic-api-key
kservehf-tokentokenhf-token
The static Dex admin password is never committed. make seed-secrets stores the retrievable operator copy in the backend as dex-admin-password, stores the bcrypt hash in dex-admin-hash, and mirrors the password into gitignored secrets/dex-admin-password. Dex reads only the hash from env via staticPasswords[].hashFromEnv. Existing hash-only forks cannot recover the password; run make reset-dex-admin once to rotate and persist both backend keys. See Configure for backend, DNS, and SSO setup.