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

PropertyAWS Secrets Manager + ESOAzure Key Vault + ESO (or akv2k8s)Google Secret Manager + ESOHashiCorp Vault + ESO
Where it runsAWS managedAzure managedGCP managedAnywhere — incl. air-gap
Authentication to clusterIRSA (OIDC + IAM role)Azure Workload IdentityWorkload Identity FederationKubernetes auth method
Dynamic secretsNative rotation LambdasLimitedLimitedFirst-class, multi-engine
Internal PKI / CAACM (separate product)Key Vault certs (limited)CAS (separate product)Built-in PKI engine
Operational footprintZero (managed)Zero (managed)Zero (managed)You operate Vault
Cost shapePer secret/month + APIPer secret/month + APIPer version/month + APIFree OSS / paid HCP / Enterprise
GitOps friendliness (ESO)ExcellentExcellentExcellentExcellent
Works across cloudsAWS onlyAzure onlyGCP onlyMulti-cloud + on-prem
Air-gap supportNoNoNoYes
Disaster recovery storyCross-region replicationGeo-redundancyMulti-region replicationRaft snapshots + auto-unseal
Number of providers via ESOn/an/an/aVault is one of 50+ in ESO
Best forEKS shops, all-AWSAKS shops; akv2k8s if Azure-onlyGKE shopsOn-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.