✦ For everyone, free.

Practical knowledge for real and everyday life

Home

18.3.2.4 Kubernetes Secrets

A focused guide to Kubernetes Secrets, connecting core concepts with practical Docker and container operations.

Kubernetes Secrets provide a dedicated resource type for sensitive values, but the most important thing to understand explicitly is that base64 encoding, the format Secret data is stored in, provides no actual encryption or confidentiality protection on its own, which is a frequent and consequential misconception for anyone newly comparing this mechanism to Docker's own secrets handling.

Base64 encoding is not encryption

A Secret's data is stored base64-encoded, which is a reversible encoding scheme, not an encryption algorithm, meaning anyone with read access to the Secret object can trivially decode it back to its original, plaintext value:

kubectl get secret db-password -o jsonpath='{.data.password}' | base64 -d
supersecretvalue

This single command fully reveals the original value with no special tooling or effort required, which is precisely why access control over who can read Secret objects in the first place, not the base64 encoding itself, is the actual security boundary protecting this data.

Encryption at rest requires explicit cluster configuration

By default, Secret data is stored in etcd, the cluster's underlying datastore, without encryption at the storage layer unless the cluster has been explicitly configured with an encryption provider; this is a cluster-administrator-level configuration decision, not something automatically enabled simply by using the Secret resource type:

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources: ["secrets"]
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <base64-encoded-encryption-key>

Confirming whether a given cluster has this encryption-at-rest configuration actually enabled is worth checking directly rather than assuming Secret data is automatically protected at the storage layer simply by virtue of using the dedicated resource type.

RBAC as the actual access control mechanism

Role-Based Access Control determines who and what can actually read Secret objects, which is the genuine, enforced security boundary for this data; scoping a service account's permissions narrowly, to read only the specific Secrets a given workload actually needs, rather than broadly across an entire namespace, limits the practical impact of any single compromised pod or credential:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["db-password"]
    verbs: ["get"]

This narrowly scoped role grants read access to exactly one named Secret, rather than the considerably broader and riskier default of granting access to every Secret within a namespace.

Comparing to Docker's own secrets mechanism

Docker's native secrets mechanism, mounting a value as an in-memory file at /run/secrets/, conceptually parallels how a Kubernetes Secret is typically consumed as a mounted volume, both keeping the value out of environment variables and process inspection in a broadly similar way:

volumeMounts:
  - name: secret-volume
    mountPath: /run/secrets
    readOnly: true
volumes:
  - name: secret-volume
    secret:
      secretName: db-password

The meaningful difference is in the underlying storage and access control model: Docker's Swarm-native secrets are encrypted within the Raft consensus log by design, while Kubernetes Secrets require explicit, cluster-level encryption-at-rest configuration to achieve a comparable storage-layer protection, which is the specific gap worth understanding when carrying assumptions from one system directly into the other.

Using an external secrets operator for genuine secret management

For organizations needing the rotation, audit logging, and centralized management a dedicated secrets manager provides, an external secrets operator synchronizes values from Vault, AWS Secrets Manager, or a similar system directly into native Kubernetes Secret objects, combining the centralized management benefit of a real secrets manager with the native consumption pattern application manifests already expect:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
spec:
  secretStoreRef:
    name: vault-backend
  target:
    name: db-password
  data:
    - secretKey: password
      remoteRef:
        key: secret/data/my-api/db

This pattern means application manifests continue referencing an ordinary Kubernetes Secret, while the actual source of truth and rotation lifecycle is managed entirely by the external secrets manager, with the operator handling the synchronization between the two automatically.

Secret types beyond Opaque

Kubernetes defines several specific Secret types beyond the generic Opaque type, each with conventions some tooling specifically recognizes and handles, registry credentials and TLS certificates being the most common examples:

kubectl create secret docker-registry my-registry-secret \
  --docker-server=registry.example.com --docker-username=user --docker-password=pass
kubectl create secret tls my-tls-secret --cert=tls.crt --key=tls.key

Using the correct, specific type rather than a generic Opaque Secret for these particular cases ensures compatibility with tooling, such as imagePullSecrets or ingress TLS configuration, that specifically expects and validates against these recognized type conventions.

Common mistakes

  • Assuming base64 encoding provides actual confidentiality protection, when it is trivially reversible and provides no security benefit on its own.
  • Not confirming whether a cluster has encryption at rest actually configured for Secret data, assuming this protection exists automatically simply by using the resource type.
  • Granting broad, namespace-wide Secret read access through RBAC rather than scoping permissions narrowly to exactly the specific Secrets a given workload genuinely needs.
  • Carrying over assumptions from Docker's own Swarm-native secrets encryption directly onto Kubernetes Secrets without confirming the comparable storage-layer protection has actually been configured.
  • Using the generic Opaque Secret type for registry credentials or TLS certificates rather than the specific, recognized type conventions that tooling expects for these particular cases.

Kubernetes Secrets require deliberate, explicit attention to encryption at rest configuration and RBAC scoping to actually provide meaningful protection, since base64 encoding alone offers none, and understanding this gap clearly, rather than assuming parity with Docker's own secrets handling, is essential before treating the Secret resource type as a genuinely secure place for sensitive production credentials.