Managing secrets from an external system with the External Secrets Operator: SecretStore and ExternalSecret architecture, AWS Secrets Manager, Vault, and 1Password integration, syncing through FluxCD, and best practices for secret rotation and audit.

In episode 20 you encrypted secrets with SOPS so they could be committed to Git. But there's a limitation: the private key still has to exist in every cluster, and automatic rotation isn't built in — if a secret changes in the source system, SOPS doesn't know. This is where a different approach comes in: store the secret in an externally managed system, and let an operator pull it into the cluster.
This episode covers the External Secrets Operator (ESO): its architecture and CRDs, integration with popular backends like AWS Secrets Manager and HashiCorp Vault, how to integrate it with Flux, and best practices for the secret lifecycle.
ESO is a controller inside the cluster that works based on the desired state declared through CRDs. The flow:
What's interesting for GitOps: the manifests that enter Git only contain the reference and configuration, not the secret values. The actual values live in the external backend and never exist in the repo.
| CRD | Scope | Function |
|---|---|---|
| SecretStore | Namespace | Connection to the backend, only valid in one namespace |
| ClusterSecretStore | Cluster | Connection to the backend, usable by all namespaces |
| ExternalSecret | Namespace | Pulls values from the store and forms a Secret |
ESO supports many backends. The choice usually follows where an organization already stores its secrets.
| Backend | Suitable for | Notes |
|---|---|---|
| AWS Secrets Manager | AWS teams | Managed rotation, priced per secret |
| AWS Parameter Store | AWS teams | Cheap, good for non-secret configuration |
| GCP Secret Manager | GCP teams | Workload Identity |
| Azure Key Vault | Azure teams | Managed identity |
| HashiCorp Vault | Multi-cloud | Keys, dynamic secrets, full audit |
| 1Password | Small teams | Simple, team account |
An example ClusterSecretStore to AWS Secrets Manager with credentials from a Secret:
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
region: ap-southeast-1
auth:
secretRef:
accessKeyIDSecretRef:
name: aws-eso-creds
key: access-key
secretAccessKeySecretRef:
name: aws-eso-creds
key: secret-access-keyTip
On AWS use IAM Roles for Service Accounts instead of access keys: just give the controller an IAM role with the secretsmanager:GetSecretValue permission, so no static credentials are stored in the cluster.
Once the store is available, declare the secret you want to form. Values are pulled from the backend per key and mapped to the Secret data:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: api-db
namespace: apps
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: api-db
creationPolicy: Owner
data:
- secretKey: DB_PASSWORD
remoteRef:
key: prod/api/db
property: password
- secretKey: DB_USER
remoteRef:
key: prod/api/db
property: usernamerefreshInterval: 1h makes the controller periodically pull the latest values from the backend — rotation at the backend syncs to the cluster without intervention. Check its status with kubectl get externalsecrets -n apps and look at the SYNCED column.
Nothing is technically special — that's exactly the advantage. ESO itself is installed through a HelmRelease, and all its CRDs (ClusterSecretStore, ExternalSecret, including the credential Secret which can be SOPS-encrypted) are declared in Git and synced by Flux:
external-secrets chart is declared in Git together with its HelmRepository.dependsOn on the Kustomization — make sure the ESO CRDs and store are ready before the applications. Use an initContainer or readiness probe if an application fails because the secret isn't there yet.creationPolicy: Owner so the Secret is deleted when the ExternalSecret is deleted, and a matching deletionPolicy to avoid orphaned secrets.refreshInterval or backend events; update secrets without modifying Git.This episode introduced the second approach to secret management: instead of storing ciphertext in Git like SOPS, ESO stores the secret in an external system and creates the Kubernetes Secret automatically. Flux manages the configuration, ESO manages the values, and no secret ever settles in the repo.
The key takeaways:
refreshInterval syncs backend changes to the cluster.ESO is ideal when secrets already live in a managed system. But not every team has Vault or AWS. In the next episode, episode 22, we'll learn about Sealed Secrets — a lightweight alternative that still uses Git as the secret storage place, with Bitnami's public-private key-based encryption. See you in episode 22!