The Problem with Native Kubernetes Secrets
Native Kubernetes Secrets are a bit of a misnomer. They aren’t actually encrypted; they are just Base64-encoded strings. If you commit these to a Git repository, anyone with read access can run echo 'encoded-string' | base64 --decode and own your production database in three seconds. I’ve seen many teams learn this the hard way after a junior developer accidentally pushes a .yaml file containing a Stripe API key to a public repo.
Most organizations eventually graduate to dedicated tools like AWS Secrets Manager or HashiCorp Vault. The friction starts when you try to get those values into your pods. While you could write custom Python scripts or use heavy init-containers, these methods add significant maintenance debt. Mastering the bridge between external vaults and Kubernetes is a non-negotiable skill for building a secure, modern CI/CD pipeline.
The External Secrets Operator (ESO) fills this gap perfectly. It functions as a background controller that pulls data from your provider and injects it into native Kubernetes Secrets for your apps to consume.
Understanding the Core Concepts of ESO
ESO relies on two primary custom resources to handle the heavy lifting:
- SecretStore / ClusterSecretStore: These act as the connection string. They define how to talk to AWS, Vault, or GCP. Use a
SecretStorefor single-namespace isolation or aClusterSecretStoreto share one connection across the entire cluster. - ExternalSecret: This is the manifest that defines what to fetch. It instructs the operator to locate a specific key, like
prod/db/password, and map it to a local Kubernetes Secret.
I frequently toggle between YAML and JSON formats when debugging these manifests to ensure my nested data structures are correct. Keeping a YAML ↔ JSON Converter handy is helpful here. Using a client-side tool ensures you aren’t leaking sensitive architecture patterns to a third-party server during the conversion process.
Step 1: Installing External Secrets Operator
Helm is the standard choice for installation. It simplifies version management and makes the cleanup process straightforward if you ever need to migrate.
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets \
external-secrets/external-secrets \
-n external-secrets \
--create-namespace
Verify the deployment by checking the status of the controller pods:
kubectl get pods -n external-secrets
Step 2: Connecting to AWS Secrets Manager
Avoid static AWS access keys at all costs. Instead, use IAM Roles for Service Accounts (IRSA) to grant the cluster permission to read secrets. You will need an IAM policy that specifically allows secretsmanager:GetSecretValue for your target resources.
Once your IAM role is linked to a Kubernetes ServiceAccount, apply a ClusterSecretStore like this:
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: aws-secretsmgr
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: eso-service-account
namespace: external-secrets
Step 3: Connecting to HashiCorp Vault
Vault setups usually require a bit more configuration. You can use Tokens or AppRoles, but the Kubernetes Auth Method is the cleanest for internal cluster traffic. Below is a standard configuration for a Kubernetes-native auth mount:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: my-app
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "my-role"
serviceAccountRef:
name: "my-app-sa"
Authentication failures are common during the initial setup. If the operator fails to sync, I use a JWT Decoder to check the expiration and claims of the service account token. This quickly reveals if the issue is an expired token or a mismatched role name.
Step 4: Syncing Your First Secret
Let’s put it into practice. Suppose you have an AWS secret named /production/api-key. You want this available in your my-app namespace as a secret named api-credentials.
Apply this ExternalSecret manifest:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: api-key-sync
namespace: my-app
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secretsmgr
kind: ClusterSecretStore
target:
name: api-credentials
creationPolicy: Owner
data:
- secretKey: API_TOKEN
remoteRef:
key: /production/api-key
property: api_token_value
ESO will now fetch the value and generate a standard Kubernetes Secret. Your application pods can mount this as an environment variable or a file volume. If you are migrating legacy secrets, use a Base64 Decoder to compare the new synced values against your old manual ones. This ensures the application won’t crash due to unexpected formatting changes.
Practical Tips from the Field
Managing hundreds of secrets across production clusters has taught me a few hard lessons:
- Watch Your API Costs: AWS Secrets Manager costs $0.40 per secret/month plus $0.05 per 10,000 API calls. Don’t set a 10-second
refreshInterval. A 1-hour interval is usually plenty for most use cases and keeps your bill low. - Standardize Paths: Use a hierarchy like
/env/namespace/app/key. This allows you to write granular IAM or Vault policies that prevent one team from accidentally viewing another team’s credentials. - Monitor Status: If a sync fails,
kubectl describe externalsecret <name>is your best friend. It provides clear event logs detailing whether the issue is a 403 Forbidden error or a network timeout. - Least Privilege: Never give ESO cluster-wide admin access to your vault. Grant it read-only access to specific paths to minimize the blast radius in case of a compromise.
Summary
Ditching manual kubectl create secret commands is a massive win for security and sanity. By implementing External Secrets Operator, you maintain a single source of truth in a hardened vault while keeping your GitOps manifests clean. It automates rotation, reduces human error, and ensures that your sensitive data never touches your source control in plaintext.
The initial setup takes about an hour. However, the time saved on troubleshooting and the security peace of mind it provides are worth far more. Once you see your secrets updating automatically across multiple environments, you’ll never go back to manual encoding.

