Secrets Management
Shoehorn references every credential through a typed *SecretRef shape: {name, key}, the same shape as Kubernetes’ native valueFrom.secretKeyRef. The chart never creates Secrets; it only reads them. Bring whatever secret tooling you already use (kubectl, Sealed Secrets, External Secrets Operator, CSI Secret Store) and the chart consumes the Secrets it produces.
There are two operating modes:
- One Secret for everything. Set
secret.defaultNameonce. Every*SecretRefmay then omitname:and just supplykey:. Best for kubectl + Sealed Secrets workflows. - Per-credential Secrets. Set
name:on each*SecretRefpointing at a different K8s Secret synced from a different upstream source. Best for ESO + Vault / AWS / GCP / Azure where each credential domain rotates independently.
Quick path: kubectl + one Secret
Section titled “Quick path: kubectl + one Secret”Use this for getting started, local dev, or any workflow where one Kubernetes Secret holds every credential.
kubectl create secret generic shoehorn-credentials -n shoehorn \ --from-literal=postgres_password='<postgres-password>' \ --from-literal=db_password='<app-user-password>' \ --from-literal=valkey_password='<valkey-password>' \ --from-literal=meilisearch_master_key="$(openssl rand -base64 32)" \ --from-literal=jwt_secret="$(openssl rand -hex 32)" \ --from-literal=auth_encryption_key="$(openssl rand -base64 32)" \ --from-literal=secrets_encryption_key="$(openssl rand -base64 32)"secret: defaultName: shoehorn-credentials
postgresql: superuserPasswordSecretRef: key: postgres_password # name defaults to shoehorn-credentials passwordSecretRef: key: db_passwordvalkey: passwordSecretRef: key: valkey_passwordmeilisearch: masterKeySecretRef: key: meilisearch_master_keyauth: session: jwtSecretRef: key: jwt_secret encryptionKeyRef: key: auth_encryption_key secretsEncryptionKeyRef: key: secrets_encryption_keyThe chart also ships an examples/values-minimal.yaml showing this pattern end-to-end.
Sealed Secrets (GitOps)
Section titled “Sealed Secrets (GitOps)”Use this when you want the encrypted Secret committed in git. The shape is identical to the kubectl path — secret.defaultName plus per-ref key: — only the way you create the Secret changes.
- Generate the literal Secret manifest as in the kubectl path, but pipe through
kubeseal:Terminal window kubectl create secret generic shoehorn-credentials -n shoehorn \--dry-run=client -o yaml --from-literal=... \| kubeseal --controller-name=sealed-secrets --format=yaml > shoehorn-credentials.sealed.yaml - Commit
shoehorn-credentials.sealed.yamlto git. - Apply it (manually or via Argo CD / Flux). The Sealed Secrets controller decrypts and creates the matching
Secret. - The chart reads it through
secret.defaultName: shoehorn-credentials— no other config changes.
See bitnami/sealed-secrets for installation and operator details.
External Secrets Operator + Vault
Section titled “External Secrets Operator + Vault”Each credential domain (database, auth, session, search, cache, GitHub, SMTP) gets its own Vault path, its own ExternalSecret, and its own Kubernetes Secret. Rotating one credential doesn’t touch the others’ resourceVersion, so unrelated pods don’t get restarted, and ESO refreshInterval can differ per source (DB: 1h, JWT: 24h).
The chart ships a worked example at examples/values-eso-vault.yaml. The shape:
secret: defaultName: "" # no shared default — each ref names its own Secret
postgresql: superuserPasswordSecretRef: name: shoehorn-db-creds # synced from secret/data/shoehorn/database key: postgres_password passwordSecretRef: name: shoehorn-db-creds key: db_password
auth: okta: clientSecretRef: name: shoehorn-okta-creds # synced from secret/data/shoehorn/okta key: client_secret apiTokenSecretRef: name: shoehorn-okta-creds key: api_token session: jwtSecretRef: name: shoehorn-session-keys # synced from secret/data/shoehorn/session key: jwt_secret # ...Apply the matching ExternalSecret resources alongside (one per domain). Example for the database credentials:
apiVersion: external-secrets.io/v1kind: ExternalSecretmetadata: name: shoehorn-db-creds namespace: shoehornspec: refreshInterval: 1h secretStoreRef: name: vault-backend kind: ClusterSecretStore target: name: shoehorn-db-creds # matches postgresql.*SecretRef.name data: - secretKey: postgres_password remoteRef: key: secret/data/shoehorn/database property: postgres_password - secretKey: db_password remoteRef: key: secret/data/shoehorn/database property: db_passwordAnd for the Okta credentials:
apiVersion: external-secrets.io/v1kind: ExternalSecretmetadata: name: shoehorn-okta-creds namespace: shoehornspec: refreshInterval: 6h secretStoreRef: name: vault-backend kind: ClusterSecretStore target: name: shoehorn-okta-creds data: - secretKey: client_secret remoteRef: key: secret/data/shoehorn/okta property: client_secret - secretKey: api_token remoteRef: key: secret/data/shoehorn/okta property: api_tokenPre-requisites: External Secrets Operator installed, a ClusterSecretStore (e.g. vault-backend) configured against your Vault, and the listed Vault paths populated.
External Secrets Operator + AWS Secrets Manager
Section titled “External Secrets Operator + AWS Secrets Manager”Same shape, different SecretStore. Each *SecretRef.name points at a Secret synced from a distinct AWS Secrets Manager ARN.
apiVersion: external-secrets.io/v1kind: ClusterSecretStoremetadata: name: aws-secretsmanagerspec: provider: aws: service: SecretsManager region: us-east-1 auth: jwt: serviceAccountRef: name: external-secrets namespace: external-secrets---apiVersion: external-secrets.io/v1kind: ExternalSecretmetadata: name: shoehorn-db-creds namespace: shoehornspec: refreshInterval: 1h secretStoreRef: name: aws-secretsmanager kind: ClusterSecretStore target: name: shoehorn-db-creds data: - secretKey: postgres_password remoteRef: key: arn:aws:secretsmanager:us-east-1:111122223333:secret:prod/shoehorn/db-postgres - secretKey: db_password remoteRef: key: arn:aws:secretsmanager:us-east-1:111122223333:secret:prod/shoehorn/db-appThe chart values block is identical to the Vault example. Only the SecretStore provider changes.
CSI Secret Store driver (GCP / Azure)
Section titled “CSI Secret Store driver (GCP / Azure)”The CSI Secret Store driver mounts secrets as files but can also sync them to Kubernetes Secrets via secretObjects. Reference those synced Secrets the same way:
apiVersion: secrets-store.csi.x-k8s.io/v1kind: SecretProviderClassmetadata: name: shoehorn-db-credsspec: provider: gcp # or `azure`, `aws`, `vault` parameters: secrets: | - resourceName: "projects/your-project/secrets/shoehorn-postgres-password/versions/latest" path: "postgres_password" - resourceName: "projects/your-project/secrets/shoehorn-db-password/versions/latest" path: "db_password" secretObjects: - secretName: shoehorn-db-creds type: Opaque data: - objectName: postgres_password key: postgres_password - objectName: db_password key: db_passwordThen mount the SPC on a workload (so the driver creates the K8s Secret) and reference shoehorn-db-creds from postgresql.passwordSecretRef.name.
Field reference
Section titled “Field reference”Every credential the chart consumes, mapped to its *SecretRef values path and the env var the chart emits.
| Credential / env var | Values path |
|---|---|
POSTGRES_PASSWORD | postgresql.superuserPasswordSecretRef |
DB_PASSWORD (and APP_USER_PASSWORD) | postgresql.passwordSecretRef |
VALKEY_PASSWORD | valkey.passwordSecretRef |
MEILISEARCH_API_KEY (clients), MEILI_MASTER_KEY (server) | meilisearch.masterKeySecretRef |
JWT_SECRET | auth.session.jwtSecretRef |
AUTH_ENCRYPTION_KEY | auth.session.encryptionKeyRef |
SECRETS_ENCRYPTION_KEY | auth.session.secretsEncryptionKeyRef |
ZITADEL_SERVICE_USER_PAT (optional, orgdata) | auth.zitadel.serviceUserPatSecretRef |
OKTA_CLIENT_SECRET (required when provider: okta) | auth.okta.clientSecretRef |
OKTA_API_TOKEN (optional, orgdata) | auth.okta.apiTokenSecretRef |
ENTRA_CLIENT_SECRET (required when provider: entra-id) | auth.entraId.clientSecretRef |
ARGOCD_TOKEN (optional) | auth.argocd.tokenSecretRef |
UPCLOUD_TOKEN (optional) | cloudProviders.upcloud.tokenSecretRef |
SMTP_PASSWORD (required when smtp.enabled) | smtp.passwordSecretRef |
Public identifiers are plain values, not secret refs:
| Env var | Values path |
|---|---|
ZITADEL_PROJECT_ID | auth.zitadel.projectId |
ZITADEL_CLIENT_ID | auth.zitadel.clientId |
GITHUB_APP_ID | auth.github.appId |
GITHUB_APP_INSTALLATION_ID (and GITHUB_INSTALLATION_ID) | auth.github.installationId |
GITHUB_FORGE_APP_ID | auth.github.forge.appId |
GITHUB_FORGE_INSTALLATION_ID | auth.github.forge.installationId |
GITHUB_FORGE_ORGANIZATION | auth.github.forge.organization |
See also
Section titled “See also”- Identity providers — overview of supported IdPs
- Okta integration, Zitadel integration — per-provider setup walkthroughs
examples/values-minimal.yamlandexamples/values-eso-vault.yamlin the Helm chart — copy-pasteable starting points for the two operating modes