apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: aws-prod
spec:
provider:
aws:
service: SecretsManager
region: eu-west-1
auth:
jwt:
serviceAccountRef:
name: external-secrets
namespace: external-secrets
conditions:
- namespaceSelector:
matchLabels:
env: prod
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: checkout-db
namespace: shop
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: aws-prod
target:
name: checkout-db
creationPolicy: Owner
template:
data:
DATABASE_URL: "postgres://{{ .username }}:{{ .password }}@db.shop.svc:5432/checkout"
data:
- secretKey: username
remoteRef:
key: prod/checkout/database
property: username
- secretKey: password
remoteRef:
key: prod/checkout/database
property: password
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout
namespace: shop
annotations:
reloader.stakater.com/auto: "true"
spec:
replicas: 3
selector:
matchLabels:
app: checkout
template:
metadata:
labels:
app: checkout
spec:
containers:
- name: checkout
image: ghcr.io/acme/checkout:2.4.1
envFrom:
- secretRef:
name: checkout-dbKubernetes graph
External Secrets Operator: from a cloud secret manager to a Pod, with rotation
How a secret travels from AWS Secrets Manager to a container: a ClusterSecretStore that authenticates with a ServiceAccount, an ExternalSecret that writes a Kubernetes Secret, the Deployment that reads it and Reloader restarting Pods on rotation.
Keeping secrets out of Git usually means keeping them in a secret manager and copying them into the cluster. The External Secrets Operator does the copying. An ExternalSecret says which keys to fetch and what Secret to write; a store object says where to fetch from and how to authenticate.
The copy is one-way and repeated. When the value changes upstream, the Secret follows at the next refresh. Whether the application notices is a separate question, and the last part of this graph.
Interactive graph
Click a resource to see what it does, or follow the setup step by step.
How it works
All 8 resources and what they do
- AWS Secrets Manager prod/checkout/database
- The source of truth: a JSON secret with
usernameandpassword, rotated every 30 days by a Lambda function. - ServiceAccount external-secretsv1
- The operator's identity. Its annotation maps it to an IAM role that may read secrets under
prod/, so no long-lived AWS key sits in the cluster. - ClusterSecretStore aws-prodexternal-secrets.io/v1
- One connection to one backend, usable from any namespace the
conditionsallow. A namespacedSecretStoredoes the same for a single namespace. - ExternalSecret checkout-dbexternal-secrets.io/v1
- Which remote keys to fetch, how often, and the Secret to write. The template builds a connection string from the two fetched values. This object holds no secret and is safe in Git.
- Secret checkout-dbv1
- Written and owned by the operator. With
creationPolicy: Owner, deleting the ExternalSecret deletes it. Edits made by hand are overwritten at the next refresh. - Deployment checkoutapps/v1
- Reads
DATABASE_URLfrom the Secret as an environment variable. The annotation asks Reloader to restart it when a Secret it uses changes. - Pod checkout-…v1
- Environment variables are read once, when the container starts. A running Pod keeps the old password until it is replaced.
- Deployment reloaderapps/v1
- Stakater Reloader. It watches Secrets and ConfigMaps, and when one changes it patches the Pod template of the workloads that use it, which starts an ordinary rolling update.
Common mistakes
SecretSyncedErrorwithcould not get secret data from providerusually means the remote key orpropertyis wrong, or the IAM role can't read that path.kubectl describe externalsecretshows the provider's message.- A refresh updates the Secret, not the process. Environment variables need a restart; files mounted from the Secret update by themselves after about a minute, and the application still has to reread them.
- With
refreshInterval: 1h, a rotated password can be stale for up to an hour. Keep the old credential valid for longer than the interval, or annotate the ExternalSecret to force a refresh. - A ClusterSecretStore without
conditionslets every namespace read everything its role can. Scope it by namespace label, or give teams their own SecretStore and role. creationPolicy: Ownerdeletes the Secret with the ExternalSecret. Removing the ExternalSecret from Git with pruning on takes the application's credentials with it.
The manifests you write
Everything above that is marked "You write it", in one file. The rest comes from add-ons and controllers.
Keep going
More graphs
- Deployment, Service and Ingress: how a request reaches your Pods
- Gateway API: GatewayClass, Gateway and HTTPRoute with a canary split
- PVC, StorageClass and CSI: how a cloud disk ends up in your Pod
- RBAC: how a ServiceAccount gets its permissions
- cert-manager with Let's Encrypt: Certificate, CertificateRequest, Order and Challenge
- A private CA with cert-manager and trust-manager
- Istio traffic management: Gateway, VirtualService, DestinationRule and policies
- Flux dependencies: Git and OCI sources, Kustomizations and HelmReleases
- Argo CD: ApplicationSet, AppProject and the Applications it generates
- Inside an OCI image: tag, index, manifests, config and layers
- OCI artifacts: Helm charts, Flux artifacts and image volumes
- A Helm release: chart, values, revisions and the objects Helm owns
- Kustomize: a base, two overlays, a component and generated ConfigMaps
- A StatefulSet database: headless Service, per-Pod volumes, snapshots and restore
- Autoscaling end to end: KEDA, the HPA it creates and Karpenter adding nodes
- Prometheus Operator: ServiceMonitor, PrometheusRule and the path to an alert
- Kyverno CEL policies: ValidatingPolicy, MutatingPolicy, GeneratingPolicy and exceptions
- Cilium egress to a domain: CiliumNetworkPolicy, the DNS proxy and toFQDNs
- Istio ambient mode: ztunnel, waypoint proxies and authorization without sidecars
- Argo Rollouts canary: traffic weights on an HTTPRoute and analysis from Prometheus