Storing plaintext API keys, database credentials, or TLS certificates inside Kubernetes manifests remains one of the fastest paths to a security incident. A common pitfall is committing a base64-encoded Kubernetes Secret to Git, assuming that obfuscation equals encryption. It does not. Base64 is merely an encoding scheme. Anyone with repository read access can decode your production credentials with a single shell command in less than two seconds.
This highlights a clear conflict in modern infrastructure. GitOps requires declarative, version-controlled state. Security compliance, on the other hand, demands that secrets live in hardened vaults with granular audit logs and automated rotation. Bridging this gap without leaking credentials into commit history requires a reliable synchronization layer.
Comparing Kubernetes Secret Management Approaches
Platform engineers typically weigh three primary strategies when handling cluster secrets:
1. Sealed Secrets (Bitnami)
Sealed Secrets uses asymmetric cryptography. You encrypt raw values client-side using a public key tied to an in-cluster controller, then commit the generated SealedSecret resource to Git. The controller decrypts the payload into a native Kubernetes Secret using its private key.
2. Secrets Store CSI Driver
The Secrets Store CSI Driver mounts external secrets directly into Pod volumes via Container Storage Interface (CSI) plugins. Secrets stream straight from external backends—such as AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, or Google Secret Manager—into volatile memory inside the pod.
3. External Secrets Operator (ESO)
External Secrets Operator is a dedicated Kubernetes operator. It polls metadata from external APIs and generates standard v1/Secret resources inside your target namespaces.
Pros & Cons of Each Strategy
Every pattern involves specific operational trade-offs:
Sealed Secrets
- Pros: Self-contained within Git; requires no cloud IAM plumbing; runs without vendor lock-in.
- Cons: Secret rotation requires manual re-encryption and git commits; controller private key backups introduce single points of failure during disaster recovery; offers no integration with corporate secret engines.
Secrets Store CSI Driver
- Pros: Credentials bypass etcd by default; payloads sit in memory-backed
tmpfsvolumes inside pods. - Cons: Tightly tied to pod lifecycles; requires volume mounts even for apps expecting simple environment variables; mirroring values back to native Secrets requires enabling alpha/beta controller flags and adds configuration overhead.
External Secrets Operator (ESO)
- Pros: Generates standard
v1/Secretobjects, ensuring complete compatibility with existing Helm charts and Deployments; connects to multiple backends concurrently; provides built-in templating and configurable polling intervals; runs decoupled from pod start sequences. - Cons: Secrets persist in etcd (requiring KMS etcd encryption at rest); introduces an active controller that must be monitored and maintained.
Recommended Architecture: ESO with Enterprise Backends
Across production clusters managing hundreds of microservices, ESO consistently delivers the cleanest boundary between operations and development. Security teams centrally control IAM policies and lifecycle rules in AWS Secrets Manager or Vault. Meanwhile, application developers write concise ExternalSecret manifests right alongside their standard workloads.
When drafting YAML definitions for SecretStores and ExternalSecrets, verifying key hierarchies beforehand saves time. If you need to convert JSON payloads into YAML or test structured dictionaries, check out the ToolCraft YAML to JSON Converter.
ToolCraft runs all transformations entirely client-side in your browser without transmitting payloads, making it safe for mocking non-production schemas. To quickly inspect existing cluster secrets or verify base64 strings locally, the Base64 Encoder/Decoder is equally fast and secure.
Step-by-Step Implementation Guide
Step 1: Install External Secrets Operator
Deploy the ESO Helm chart into your cluster:
helm repo add external-secrets https://charts.external-secrets.io
helm repo update
helm install external-secrets \
external-secrets/external-secrets \
-n external-secrets \
--create-namespace \
--set installCRDs=true
Confirm that the controller, webhook, and cert-controller pods are up and healthy:
kubectl get pods -n external-secrets
Step 2: Connect AWS Secrets Manager via SecretStore
IAM Roles for Service Accounts (IRSA) provides secure token exchange on AWS EKS without hardcoded credentials. Grant your IAM role secretsmanager:GetSecretValue and secretsmanager:DescribeSecret permissions, bind it to your ServiceAccount, and declare a SecretStore:
cat <<EOF | kubectl apply -f -
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-backend
namespace: default
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: eso-aws-sa
EOF
Step 3: Connect HashiCorp Vault via SecretStore
For HashiCorp Vault, the native Kubernetes auth method maps pod service accounts directly to Vault policies. This removes the need to juggle static root tokens:
cat <<EOF | kubectl apply -f -
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: default
spec:
provider:
vault:
server: "https://vault.internal.corp:8200"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "app-role"
serviceAccountRef:
name: default
EOF
Step 4: Declare ExternalSecret Resources
Next, define an ExternalSecret manifest to pull values and shape the resulting Kubernetes Secret. If your upstream vault stores JSON dictionaries, format and inspect keys using the ToolCraft JSON Formatter before writing mappings.
cat <<EOF | kubectl apply -f -
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: payment-service-secret
namespace: default
spec:
refreshInterval: "1h"
secretStoreRef:
name: aws-secrets-backend
kind: SecretStore
target:
name: payment-api-secret
creationPolicy: Owner
data:
- secretKey: DB_PASSWORD
remoteRef:
key: production/payment-service
property: password
- secretKey: API_KEY
remoteRef:
key: production/payment-service
property: api_key
EOF
Step 5: Verify Synchronization and Bind to Pods
Inspect the sync status of your resource:
kubectl get externalsecret payment-service-secret
kubectl describe externalsecret payment-service-secret
Look for Status: SecretSynced in the output. Then confirm that ESO populated the native secret:
kubectl get secret payment-api-secret -o yaml
Your application can now bind the synchronized keys as standard environment variables:
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: payment-service
template:
metadata:
labels:
app: payment-service
spec:
containers:
- name: app
image: nginx:alpine
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: payment-api-secret
key: DB_PASSWORD
EOF
Whenever credentials rotate upstream, ESO retrieves the updated values within the configured refreshInterval. Pair this setup with a controller like Stakater Reloader to automatically trigger rolling pod updates the moment downstream secrets change.
