Where to Hide Kubernetes Secrets: AWS, Azure, GCP, and On-Prem Compared
The Kubernetes Secret object is a YAML manifest with a base64-encoded
value in it. That’s the entire encryption story. Anyone with get
permission on the namespace can read every credential the workload
holds. Base64 is not encryption. It is barely obfuscation.
Teams discover this the wrong way — usually during a security review, occasionally during an incident — and the next question is always the same: so where do the real secrets live?
The answer in 2026 is no longer “pick a tool.” It’s a pattern: keep the real secrets in a purpose-built vault, pull them into the cluster on demand via an operator, never let them touch Git. The operator that wins this game across every cloud and every on-prem story is External Secrets Operator (ESO). The vault choice is where AWS, Azure, GCP, and on-prem genuinely diverge.
This post is the comparison.
The pattern, in one paragraph
You install External Secrets Operator in the cluster. You define
a SecretStore (or ClusterSecretStore) CRD that authenticates
to an external secret backend — AWS Secrets Manager, Azure Key
Vault, Google Secret Manager, HashiCorp Vault, 1Password, Doppler,
fifty-plus more. You define an ExternalSecret CRD that says
“fetch this secret from that store, template it into a Kubernetes
Secret with this shape, refresh it on this interval.” ESO does the
fetching, the templating, the rotation. Your pods consume the
resulting Kubernetes Secret like any other — envFrom, volume
mount, projected volume, however you already do it.
The thing that doesn’t exist in Git is the actual secret. The thing
that does live in Git is the reference — “secret named
prod/api/stripe from store aws-prod.” That’s a manifest a
reviewer can read in a pull request without leaking anything.
This is the architecture this whole post is about. The rest is which backend you put behind it.
Why I am not recommending SOPS or Sealed Secrets
You’ll see SOPS (Mozilla) and Sealed Secrets (Bitnami) recommended all over r/kubernetes for the GitOps-secrets problem. Both encrypt secret material into your Git repo — SOPS with KMS/age/PGP, Sealed Secrets with a per-cluster public key — so the encrypted blob can sit safely in a public-ish repo and decrypt only inside the cluster.
I will not recommend either. The honest version:
- SOPS is a key-management and a YAML-editing problem added to every contributor’s workflow. Every secret change is a re-encryption ceremony. The age/PGP key story is its own operational surface forever. CI needs the decryption key — another footgun. The tool works, the day-to-day cost is high.
- Sealed Secrets has a per-cluster controller key. Rebuild the cluster — DR, region failover, fresh build — and every sealed secret in Git is decryptable only by a key that no longer exists. Recovery is “re-seal every secret against the new controller.” A bad day waiting to happen.
Both are workable; neither is what I’d reach for today. ESO + a real vault solves the same problem with one less operational surface and a much better disaster-recovery story. Skip them.
What ESO actually looks like in YAML
So you have a sense of the shape, here’s the minimal example. A
SecretStore pointing at AWS Secrets Manager:
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: aws-prod
namespace: payments
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: eso-aws-payments
And the ExternalSecret that pulls one secret:
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: stripe-api
namespace: payments
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-prod
kind: SecretStore
target:
name: stripe-api
creationPolicy: Owner
data:
- secretKey: STRIPE_SECRET_KEY
remoteRef:
key: prod/payments/stripe
property: secret_key
That’s the whole pattern. Apply both manifests, ESO creates a
Kubernetes Secret named stripe-api in the payments namespace,
the pod’s envFrom: secretRef: name: stripe-api works as if you’d
created it by hand. Refreshes every hour. No secret value ever
touched Git.
Install ESO itself with one helm install:
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
-n external-secrets --create-namespace
Same operator does AWS, Azure, GCP, Vault, and forty-plus other
backends. The CRD shape is identical; only the provider block
under SecretStore changes. Portability is the whole pitch.
AWS
The AWS-native default in 2026 is AWS Secrets Manager + External Secrets Operator. It is what most teams should pick and most teams do pick.
AWS Secrets Manager is the managed vault: per-secret rotation schedules, native rotation Lambdas for RDS / Redshift / DocumentDB, KMS-backed encryption at rest, IAM-controlled access, full CloudTrail audit. Per-secret pricing — $0.40/secret/month plus API call cost — adds up if you have thousands of secrets, but for most production stacks the bill is unremarkable.
ESO authenticates with IAM Roles for Service Accounts (IRSA) on
EKS — the service account in the cluster maps to an IAM role via an
OIDC trust relationship, and the role’s policy grants
secretsmanager:GetSecretValue on the secrets it’s allowed to read.
Nothing about this requires long-lived AWS access keys in a
Kubernetes Secret. That’s the point.
AWS Systems Manager Parameter Store + ESO is the cheaper alternative. Parameter Store has a free tier for standard parameters and a much lower per-parameter price for advanced parameters. ESO supports it as a provider equally well. The trade-off is feature parity: Parameter Store has no native rotation, no per-secret resource policies, no replication. For configuration values that happen to be sensitive — a feature flag, an LLM endpoint URL — it’s the right pick. For real credentials, Secrets Manager.
AWS Secrets and Configuration Provider (ASCP) for the
Kubernetes Secrets Store CSI Driver is Amazon’s own way to mount
Secrets Manager values as files in pods. Pod-scoped, requires the
CSI driver and a SecretProviderClass per workload, doesn’t
produce a real Kubernetes Secret object by default. ESO gives you a
Secret object the rest of the ecosystem (Helm charts, operators,
dashboards) understands. ESO unless you have a hard reason not to.
The pick: EKS + IRSA + ESO + AWS Secrets Manager. Swap Secrets Manager for Parameter Store on the non-rotating values to keep the bill sensible. SOPS and Sealed Secrets do not belong on this list — IAM-scoped access to a managed vault is strictly better than ciphertext-in-Git on every axis that matters.
Azure
The Azure picture has two real options and one outdated alternative.
Azure Key Vault + akv2k8s is what I have personally run in
production. akv2k8s is a Norwegian-built Kubernetes controller
(github.com/SparebankenVest/public-helm-charts and friends) that
syncs Azure Key Vault secrets, keys, and certificates into the
cluster. The CRD is AzureKeyVaultSecret; you point it at a Key
Vault and a secret name, and the controller mirrors it as a
Kubernetes Secret. There’s also an injector mode that injects
secrets as environment variables directly into pods at startup
without ever creating a Kubernetes Secret object — useful when you
really don’t want the secret to exist at rest in etcd.
Authentication is via Azure Workload Identity (formerly
AAD Pod Identity, which is end-of-life in 2024 — workload identity
replaces it). The service account in the cluster is federated to an
Azure user-assigned managed identity, the identity has
get/list on the Key Vault, ESO/akv2k8s asks for an OAuth token
on behalf of the service account, the token is short-lived. Same
no-long-lived-credentials story as IRSA on AWS.
I have run akv2k8s on AKS for a regulated workload where the
auditor wanted every secret in a Key Vault with diagnostic logs
flowing to Log Analytics. akv2k8s did that job cleanly: the Key
Vault was the source of truth, the cluster never held a secret that
wasn’t fetched from it, the diagnostic logs gave the audit team
exactly what they wanted. The injector mode (AzureKeyVaultSecret
with output.transforms and a kind: env-injector config) was the
specific thing that won me over — secrets in the process environment,
never in etcd, gone when the pod terminates.
Azure Key Vault + External Secrets Operator is the multi-cloud-friendly choice and what I’d reach for on a new cluster today. The provider in ESO uses the same Azure Workload Identity federation as akv2k8s. The trade-off vs akv2k8s:
- ESO is multi-backend by design. If you’re going to run a mix of Azure Key Vault and HashiCorp Vault and something else, ESO is one operator for all of them. akv2k8s is Azure-only and proud of it.
- akv2k8s has a first-class env-injector that ESO doesn’t quite match — ESO generates a Kubernetes Secret, the pod consumes it. Identical security in practice; akv2k8s is one less object.
- ESO has the larger community in 2026 — CNCF Sandbox, monthly releases, fifty-plus backends. akv2k8s has a smaller, focused Norwegian-shop maintainer base. Both are healthy projects; ESO has more momentum.
If your cluster is Azure-only and likely to stay that way, akv2k8s is still a fine pick. If your cluster is multi-cloud, on-prem with some Azure tenancy, or you just want one operator pattern across every environment, ESO + Azure Key Vault is the answer.
Azure Key Vault Provider for Secrets Store CSI Driver — the older option — exists and works. Same trade-off as the AWS CSI provider: pod-scoped, awkward if you also want a real Kubernetes Secret object. Microsoft’s docs nudge you toward it; ESO is the cleaner story in 2026.
GCP
Google Secret Manager + External Secrets Operator is the GCP answer most teams should default to, and I’d recommend the same.
Google Secret Manager is the per-project managed vault — versioned secrets, CMEK-encrypted, IAM-scoped, regional or multi-regional replication. Pricing is per-secret-version per month plus access operations, comparable to AWS Secrets Manager but slightly cheaper on the access side.
ESO on GKE authenticates with Workload Identity Federation: the
GKE service account is bound to a Google Service Account, the GSA
has roles/secretmanager.secretAccessor on the secret, ESO uses
the service account’s federated token to call the Secret Manager
API. Same pattern as IRSA on AWS, same pattern as Azure Workload
Identity. Three clouds, three names, one architecture.
Berglas — Google’s open-source library that encrypts secrets
with KMS and stores them in GCS — predates Secret Manager and has
been quietly de-emphasized since. The Berglas docs themselves point
users at Secret Manager for new work. There are still Berglas
deployments in the wild, and the mutating webhook (berglas exec
sidecar) still works. For new clusters in 2026, Secret Manager + ESO.
Secret Manager CSI driver for GCP exists, same trade-offs as the AWS and Azure CSI drivers. Same recommendation: ESO unless you have a specific reason.
The GCP pick: GKE + Workload Identity Federation + ESO + Google Secret Manager. Clean, native, IAM-scoped, multi-region if you need it.
On-Prem, Multi-Cloud, Air-Gapped
This is the environment where the choice gets the most interesting, because the cloud-native vaults aren’t on the table. The answer in my experience is HashiCorp Vault + External Secrets Operator.
I have run HashiCorp Vault in production. Specifically, the open-source build, deployed as a 3-node HA Raft cluster on bare-metal RKE2 nodes, auto-unsealed via a cloud KMS as the seal mechanism, with Kubernetes auth method enabled for in-cluster workload login. Vault held the database root credentials with dynamic-secret leases (the app would request a credential, Vault would provision a short-lived PostgreSQL role, the credential would expire 4 hours later), TLS PKI for internal service-to-service certs (Vault as the internal CA, short-lived certs auto-rotated), and the long-tail of “every other thing you’d put in a vault.”
The reason Vault keeps winning the on-prem story:
- It runs anywhere. Bare metal, VMs, Kubernetes, dev laptop. Same binary, same API, same auth backends.
- Dynamic secrets. The killer feature nothing in the cloud trio quite matches. Vault generates a credential on demand — database role, AWS IAM session, SSH certificate — with a TTL, and revokes it when the lease expires. The blast radius of a leaked credential drops to the remaining seconds of its lease. Once a team is built around this, they don’t go back to “static secret you rotate every 90 days.”
- PKI and SSH CA secrets engines. Vault as your internal CA with short-lived certs and automated rotation. The PKI engine is the second reason I keep reaching for Vault.
- Air-gap installation is first-class. Single Go binary, no required external dependencies in OSS, runs inside a network that never sees the internet.
The ESO integration with Vault uses the Vault provider in the
SecretStore — point it at the Vault address, authenticate the ESO
service account via the Kubernetes auth method against a specific
Vault policy, and ESO fetches secrets the same way it fetches them
from AWS or Azure. Vault’s KV v2 engine is the standard target;
Vault’s database secret engine for dynamic secrets is supported via
the dataFrom block in ExternalSecret.
The alternative inside the Vault world is Vault Agent Injector —
the official HashiCorp sidecar pattern that injects rendered secret
templates as files into pods via a mutating webhook. It works, it
predates ESO, it ties you to a single Vault as your backend forever.
ESO + Vault gives you the option to not be tied to Vault — the
same ExternalSecret manifests with a different SecretStore
provider work against any other backend. Portability matters. I
pick ESO + Vault over the Agent Injector for new deployments.
1Password Secrets Automation + ESO is the surprise dark horse of on-prem 2026. 1Password’s Secrets Automation product exposes Connect servers that ESO speaks to natively. For small teams that already live in 1Password, this is a remarkably ergonomic story — human-friendly password manager is the developer interface, and the same secrets sync into the cluster via ESO. I have not run this in production. The architecture is clean and the maintainers are serious.
Doppler + ESO is the same shape with a different vendor — a SaaS-hosted secret store, ESO as the cluster fetcher. Doppler has a generous free tier. Same caveat: not personally tested. Evaluate on its own merits.
Vault remains the answer. For on-prem, multi-cloud, or air-gapped: HashiCorp Vault + ESO. If you’re a small team already inside the 1Password world, 1Password Connect + ESO is a genuinely interesting alternative.
The comparison table
| Property | AWS Secrets Manager + ESO | Azure Key Vault + ESO (or akv2k8s) | Google Secret Manager + ESO | HashiCorp Vault + ESO |
|---|---|---|---|---|
| Where it runs | AWS managed | Azure managed | GCP managed | Anywhere — incl. air-gap |
| Authentication to cluster | IRSA (OIDC + IAM role) | Azure Workload Identity | Workload Identity Federation | Kubernetes auth method |
| Dynamic secrets | Native rotation Lambdas | Limited | Limited | First-class, multi-engine |
| Internal PKI / CA | ACM (separate product) | Key Vault certs (limited) | CAS (separate product) | Built-in PKI engine |
| Operational footprint | Zero (managed) | Zero (managed) | Zero (managed) | You operate Vault |
| Cost shape | Per secret/month + API | Per secret/month + API | Per version/month + API | Free OSS / paid HCP / Enterprise |
| GitOps friendliness (ESO) | Excellent | Excellent | Excellent | Excellent |
| Works across clouds | AWS only | Azure only | GCP only | Multi-cloud + on-prem |
| Air-gap support | No | No | No | Yes |
| Disaster recovery story | Cross-region replication | Geo-redundancy | Multi-region replication | Raft snapshots + auto-unseal |
| Number of providers via ESO | n/a | n/a | n/a | Vault is one of 50+ in ESO |
| Best for | EKS shops, all-AWS | AKS shops; akv2k8s if Azure-only | GKE shops | On-prem, multi-cloud, regulated |
Where I land on the recommendation
If you take one architectural pattern from this post: External
Secrets Operator is the abstraction layer. Pick the backend that
matches the environment you’re already in, but standardize on the
ESO CRD shape across every cluster. The day you add a second cloud
or move a workload between environments, the manifests do not
change. Only the SecretStore provider does. That portability is
worth the small operational overhead of running ESO itself — which
is one helm chart and a handful of CRDs.
The per-environment picks, in plain words:
- AWS: EKS + IRSA + ESO + AWS Secrets Manager. Use Parameter Store for the non-rotating values to keep the bill sensible.
- Azure: AKS + Azure Workload Identity + ESO + Azure Key Vault. If you’re Azure-only and likely to stay so, akv2k8s is still a fine, focused pick — it’s what I have run, and it’s the only Azure-specific operator I’d hand-recommend.
- GCP: GKE + Workload Identity Federation + ESO + Google Secret Manager. Skip Berglas for new work.
- On-prem / multi-cloud / air-gapped: HashiCorp Vault HA + ESO. Use Vault’s dynamic secret engines once you have the basics working — that’s where Vault stops being “another vault” and starts being the operationally interesting one.
What does not belong in the recommendation in 2026: SOPS, Sealed Secrets, and the various “encrypt the YAML in Git” patterns that were the right answer in 2020. They still work. The day-to-day cost and the disaster-recovery story do not pencil out next to ESO + a real vault.
The Kubernetes Secret object will keep being base64. The
interesting question is what’s behind it. ESO in every cluster, the
right vault in every environment, IAM/identity federation gluing the
two together — that’s the pattern that survives a new cloud, a new
region, a new audit, and a 3 a.m. rebuild.
Pick the vault, install the operator, never look at a base64 string in Git again.