Skip to main content
Yoryantra

DevOps · Comparison

External Secrets Operator vs Kubernetes Secrets: Storage, Rotation, and Access

Compare native Kubernetes Secrets with External Secrets Operator across etcd storage, source of truth, rotation, RBAC, provider access, failure modes, and CSI alternatives.

By Varoun Sonawane

A native Kubernetes Secret and External Secrets Operator (ESO) are often compared as if you must choose one or the other. In most ESO deployments, though, ESO reads a value from an external provider and creates or updates a native Kubernetes Secret. They do different jobs. Kubernetes stores a Secret for workloads to read. ESO can copy its value from an external secret manager and keep the Kubernetes copy updated. The decision is about who owns the real value, how updates reach the application, and which systems can read it. You also need to decide whether a Kubernetes Secret object is acceptable in your security model.

Neither option is automatically safer. Start by deciding where the value should be stored and who is allowed to access it.

Compare the two approaches

QuestionNative Kubernetes SecretExternal Secrets Operator
Where is the source of truth?Kubernetes API / your deployment workflowExternal secret provider
What does the application usually consume?Kubernetes SecretKubernetes Secret created/updated by ESO
Does secret material normally exist in Kubernetes?YesYes, in the usual ExternalSecret -> Secret flow
Who updates the Secret?Human, CI/CD, GitOps controller, application, or another controllerESO reconciliation
Rotation sourceWhatever process changes the Kubernetes SecretExternal provider + ESO refresh policy
Extra controller / CRDs requiredNoYes
External provider requiredNoUsually yes
Can it keep plaintext values out of Git?Only if your delivery process avoids committing themYes: Git can contain references instead of the secret value
Does ESO automatically mean “no secret in etcd”?Not applicableNo
Best fitSimple cluster-local secret lifecycleExternal provider is authoritative and Kubernetes needs a synchronized copy

The important difference is who manages the value. In both common designs, a Kubernetes Secret usually exists for the application to read. ESO keeps that copy in sync with an external secret manager. A CRD (custom resource definition) is how a Kubernetes extension such as ESO adds new kinds of objects to the cluster.

Native Secrets are Kubernetes API objects

A Kubernetes Secret is a first-class API object for sensitive data. A simple manifest looks like this:

apiVersion: v1
kind: Secret
metadata:
  name: database-credentials
  namespace: payments
type: Opaque
data:
  username: YXBw
  password: c2VjcmV0

The values under data are Base64-encoded. That is encoding, not encryption. Kubernetes stores cluster data in a database called etcd. By default, Secret values in that database are not encrypted at rest unless you configure that protection. Encoding the data as Base64 does not encrypt it. That does not make native Secrets inherently unusable. It means a production design has to include controls such as:

  • encryption at rest;

  • least-privilege RBAC;

  • namespace isolation;

  • careful backup handling;

  • restricting which Pods and people can read the Secret.

Pods can still consume the Secret through a volume or environment variables without embedding credentials into application code.

ESO synchronizes an external value into Kubernetes

ESO introduces Kubernetes custom resources and a controller. A simplified ExternalSecret can look like this:

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: database-credentials
  namespace: payments
spec:
  refreshPolicy: Periodic
  refreshInterval: 1h
  secretStoreRef:
    name: production-secret-store
    kind: SecretStore
  target:
    name: database-credentials
  data:
    - secretKey: password
      remoteRef:
        key: prod/payments/database
        property: password

In a typical deployment, the data flows like this:

External secret provider
        ↓
SecretStore / provider authentication
        ↓
External Secrets Operator
        ↓
ExternalSecret reconciliation
        ↓
Kubernetes Secret
        ↓
Pod

ESO’s API documentation says an ExternalSecret describes what data should be fetched and how it should be saved as Kind=Secret. The YAML here is illustrative; check the CRD/API version and provider configuration installed in your cluster before applying it. That is why ESO is best understood as a synchronization/control-plane layer, not as a replacement for the Kubernetes Secret API.

Where the current value comes from

The source of truth is where these approaches differ most.

Native Secret

Your secret lifecycle might be:

password generated
    ↓
operator / CI / kubectl creates Secret
    ↓
Kubernetes Secret becomes current value
    ↓
Pods consume Secret

If someone changes the credential in an external database or SaaS platform, Kubernetes does not automatically know. You need a process that updates the Secret.

External Secrets Operator

Your lifecycle becomes:

password stored/rotated in external provider
    ↓
ESO reads provider
    ↓
ESO updates Kubernetes Secret
    ↓
Pods consume updated Secret

The provider becomes authoritative. Kubernetes holds a synchronized materialization for workloads. This distinction is especially useful in organizations that already centralize secrets in:

  • cloud secret managers;

  • enterprise vaults;

  • central security platforms.

The application team can reference an external value without copying the actual secret into Git.

ESO usually still stores the secret in Kubernetes etcd

An external source of truth doesn’t mean the value stays outside the cluster. If ESO creates:

kind: Secret

then the resulting secret is still a Kubernetes API object. Kubernetes’ normal Secret storage and RBAC rules still apply. So this reasoning is wrong:

"We use External Secrets Operator,
therefore passwords never exist in Kubernetes."

In the standard ESO flow, they do. The architecture changes the source of truth and synchronization process, but the resulting Kubernetes Secret can still be stored in etcd. You should still consider:

  • Kubernetes encryption at rest;

  • least-privilege access to Secret objects;

  • etcd and backup protection;

  • permissions to create Pods that can mount Secrets.

ESO does not erase those responsibilities.

If “never persist the secret as a Kubernetes Secret” is a hard requirement

That is a different architecture question. Kubernetes’ own Secret good-practices documentation points to external secret-store providers, including the Secrets Store CSI Driver pattern. The CSI driver can retrieve secret material from an external provider and mount it into a Pod as a volume. Its Kubernetes-Secret synchronization feature is optional. So if your requirement is:

external provider
    ↓
Pod-mounted secret

without maintaining a normal Kubernetes Secret object, evaluate a CSI/direct-integration pattern. Do not select ESO merely because the word “external” sounds like it keeps the value outside Kubernetes.

Keep secret values out of Git

Native Kubernetes Secret manifests are awkward in Git because Base64 does not protect the value. This:

data:
  password: c2VjcmV0

is trivially reversible. Kubernetes’ own good-practices documentation warns against sharing or committing Secret manifests as if Base64 provided confidentiality. ESO changes what you can safely keep in the repository. Instead of committing the value, Git can contain:

remoteRef:
  key: prod/payments/database
  property: password

The sensitive value remains in the external provider. That is a useful GitOps pattern. But synchronization now depends on a running ESO controller, cluster access, and a reachable provider with valid credentials. When any of those fails, the Kubernetes Secret may stop receiving updates.

SecretStore and ClusterSecretStore: who can access which credentials?

ESO separates the secret reference from the configuration used to reach the provider. A namespaced:

kind: SecretStore

belongs to one namespace. ESO’s documentation says it cannot reference resources across namespaces by design. That makes it easier to keep provider access aligned with one team’s namespace. A:

kind: ClusterSecretStore

is cluster-scoped and can be referenced from multiple namespaces. That is convenient for shared provider integrations, but it creates a larger trust surface. ESO’s current ClusterSecretStore documentation supports namespace conditions that can restrict where the store may be used. The architecture decision therefore includes:

Who may create ExternalSecrets?
Which store may they reference?
Which provider paths can that store read?
Which namespaces may use a cluster-scoped store?

A broad ClusterSecretStore plus broad provider IAM can turn one convenient integration into a very large credential-reading capability.

ESO needs permissions in Kubernetes and the external provider

With native Secrets, the main authorization questions live inside Kubernetes:

Who can create/read/update Secrets?
Who can create Pods that mount them?
Who can access etcd/backups?

ESO adds another permission system:

What identity does ESO use with the external provider?
Which provider keys can it read?
Can one namespace cause the controller to fetch another team's secrets?

For example, ESO’s provider documentation supports workload/service-account based authentication patterns and role assumption. That is useful because you can avoid static provider credentials. It also means your threat model needs to include:

Kubernetes RBAC
+
ESO controller permissions
+
SecretStore configuration
+
external provider IAM/policies

Moving the source of truth outside Kubernetes does not eliminate access-control work. It moves some of it to another control plane.

Rotation has three separate steps

“Automatic secret rotation” can mean at least three different events:

1. value changes in external provider
2. Kubernetes Secret gets the new value
3. running application actually uses the new value

ESO primarily helps bridge steps 1 and 2. Native Secrets can also be updated through CI/CD, GitOps, an operator or an administrative process; ESO is useful when the external provider should be authoritative. The ExternalSecret API supports refresh policies including:

Periodic
CreatedOnce
OnChange

With the default Periodic policy, ESO can periodically read the provider and update the target Secret. That does not automatically prove step 3.

Environment variables do not update in a running container

Suppose a Deployment uses:

env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: database-credentials
        key: password

ESO refreshes the Kubernetes Secret. The running process does not automatically receive a new environment variable value. Kubernetes documentation explicitly says a container consuming a Secret through environment variables will not see a Secret update unless the container is restarted. So this claim is too broad:

"ESO rotates application credentials automatically."

A more accurate statement is:

ESO can synchronize a changed external value into
the Kubernetes Secret automatically.

Whether the application sees it depends on how it consumes that Secret and whether Pods/applications reload.

Secret volumes have different update behavior

If a Pod consumes a Secret through a normal Secret volume, Kubernetes updates the projected volume using an eventually consistent mechanism. There can be a propagation delay based on kubelet synchronization/cache behavior. The application also needs to notice the file change. And a Secret mounted using subPath does not receive automatic Secret updates. This matters in rotation design. You need to decide whether the workload:

  • reads the secret once at startup;

  • watches a mounted file;

  • rereads on every connection;

  • requires a Deployment restart after rotation.

ESO’s refresh policy is only one part of that chain.

When secret synchronization fails

Native-only and ESO architectures fail differently.

Native Secret failure modes

Examples:

Secret never updated after external password rotation
wrong value applied through CI
secret deleted accidentally
RBAC too broad
etcd/backups insufficiently protected
secret manifest leaked

The secret path is simpler, but your delivery system owns synchronization.

ESO failure modes

You still have the Kubernetes Secret concerns, plus:

provider unavailable
provider API rate limit
ESO controller unavailable
provider authentication failure
SecretStore misconfiguration
ExternalSecret references wrong remote key
refresh policy not matching expected rotation
provider IAM too broad

ESO trades manual synchronization for a reconciliation dependency. That trade may be worth making, provided the team owns the additional failure modes.

Availability: what happens when the provider is down?

Once ESO has created a target Kubernetes Secret, an existing workload can continue using that Kubernetes object even if the external provider temporarily becomes unavailable. But ESO cannot refresh to a new value while it cannot read the provider. The exact operational impact depends on:

  • whether the target Secret already exists;

  • refresh policy;

  • provider outage duration;

  • whether the external credential rotates while ESO is disconnected;

  • whether Pods restart and still have access to the target Secret.

This is another reason not to reduce the architecture to “external is safer.” Externalizing the source adds a network/provider dependency to reconciliation.

Payment API example: CI-managed Secret or ESO

Consider a payment API that needs a database password.

Native Secret approach

Deployment flow:

CI obtains password
    ↓
CI applies Kubernetes Secret
    ↓
Kubernetes API stores Secret
    ↓
Pod reads Secret

Possible manifest:

apiVersion: v1
kind: Secret
metadata:
  name: payments-db
  namespace: payments
type: Opaque
stringData:
  password: REPLACE_DURING_DEPLOYMENT

The real challenge is:

Where did CI get the value?
How is it protected?
Who rotates it?
How is the new value pushed to the cluster?

ESO approach

Deployment flow:

Security/platform team owns value in provider
    ↓
ExternalSecret references provider key
    ↓
ESO fetches value
    ↓
ESO creates/updates payments-db Secret
    ↓
Pod reads payments-db Secret

Example:

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: payments-db
  namespace: payments
spec:
  refreshPolicy: Periodic
  refreshInterval: 1h
  secretStoreRef:
    name: payments-store
    kind: SecretStore
  target:
    name: payments-db
  data:
    - secretKey: password
      remoteRef:
        key: prod/payments/database
        property: password

The Pod can continue referencing:

secretKeyRef:
  name: payments-db
  key: password

The application does not need to know whether the Secret was created by CI or ESO. That compatibility is one of ESO’s strengths.

Which setup fits your environment?

SituationNative SecretESOWhy
Small cluster, few rarely changed secrets, no external providerStrong fitOften unnecessaryNative object + proper encryption/RBAC may be simpler
Organization already uses AWS/GCP/Azure/Vault-style secret managerPossible, but requires sync processStrong fitESO makes external store the source of truth
Git must not contain plaintext/Base64 secret valuesNeeds another protection/delivery mechanismStrong fitGit stores references instead of secret values
Frequent centrally managed rotationCustom sync requiredStrong fitExternalSecret reconciliation can update target Secret
Requirement: secret must never be persisted as Kubernetes SecretDoes not fitStandard ESO flow does not fitEvaluate CSI/direct external-store access
App consumes Secret as env var and must see rotation instantlyRestart/reload requiredESO alone does not solveKubernetes env vars do not refresh in running container
Teams require isolated provider access per namespaceStraightforward Kubernetes RBACPrefer carefully scoped SecretStoreNamespaced store narrows the integration boundary
Shared platform store across many namespacesNative duplication/automation neededClusterSecretStore can fitMust constrain namespace/provider privileges carefully
Lowest operational complexity is priorityStrong fitAdds operator/CRDs/provider dependencySimpler may be safer operationally
Central audit/rotation/source-of-truth outside cluster is priorityRequires separate processStrong fitExternal provider owns the value lifecycle

What the Kubernetes Secret Decoder can tell you

The Yoryantra Kubernetes Secret Decoder answers a local inspection question:

“What plaintext values are represented by the Base64 data in this Secret YAML?”

That is useful whether the Secret was created manually, by CI, or by ESO. It can help you review:

data:
  username: YXBw
  password: c2VjcmV0

before deployment or during debugging. It cannot tell you:

  • who owns the source of truth;

  • whether the secret came from an external provider;

  • whether ESO is reconciling successfully;

  • whether etcd encryption is enabled;

  • whether provider IAM is safe;

  • whether a running application reloaded a rotated value.

Those are architecture/runtime questions, which is why this article has a separate job from the decoder.

Four questions to settle before choosing

Before adding ESO to a cluster, answer these questions about the workload, not just the configuration tool:

  • Where does the authoritative value live? If a cloud secret manager or Vault already manages rotations, ESO can keep the Kubernetes copy synchronized. If a small cluster already handles updates safely, another controller may be unnecessary.

  • May the value be stored as a Kubernetes Secret? Standard ESO synchronization still creates that object. If the answer is no, investigate CSI-mounted secrets or direct provider access instead.

  • Who owns the access boundary? Kubernetes RBAC remains relevant with either design. ESO also requires carefully scoped provider identity, store permissions, and namespace boundaries.

  • How will the running application see a rotation? An updated Secret doesn’t refresh environment variables in existing containers. Workloads may need a restart or reload strategy.

ESO is useful when those additional dependencies serve a real requirement. It is not an automatic security upgrade over a properly configured native Secret.

Sources