Both objects do the same mechanical job: they hold key-value data outside the container image so the same image can run in dev, staging and production with different settings. The difference is what Kubernetes and your cluster administrators do around them. A ConfigMap is ordinary configuration. A Secret is configuration that the cluster treats with extra care, but only if the cluster has been set up to do so.
Most interview questions on this topic are really one question in disguise: “Is a Secret secure?” The right answer is “it depends on three things outside the Secret object”, and the rest of this guide explains those three things. Examples target Kubernetes 1.30 and later.
Same shape, different handling
Here is the configuration for an orders API split the way most teams split it:
apiVersion: v1
kind: ConfigMap
metadata:
name: orders-config
namespace: shop
data:
LOG_LEVEL: "info"
PAYMENT_TIMEOUT_MS: "2500"
FEATURE_SPLIT_SHIPMENTS: "true"
---
apiVersion: v1
kind: Secret
metadata:
name: orders-db
namespace: shop
type: Opaque
stringData:
username: orders
password: "pg-Hunter2!"stringData is a write-only convenience: the API server base64-encodes it into data when it stores the object. The log level and timeout are in a ConfigMap because leaking them hurts nobody. The database password is in a Secret because leaking it does.
ConfigMaps and Secrets share a 1 MiB size limit, and both are namespaced, so a Pod can only reference ones in its own namespace. Secrets also have types. Opaque is the generic one; kubernetes.io/tls expects tls.crt and tls.key, and kubernetes.io/dockerconfigjson holds registry credentials used by imagePullSecrets. The type lets the API server validate the keys.
Base64 is a format, not a lock
This is the misconception interviewers check for first. Generate the Secret above with kubectl and look at it (this used --dry-run=client, which builds the object locally without contacting a cluster):
kubectl create secret generic orders-db -n shop \
--from-literal=username=orders \
--from-literal=password='pg-Hunter2!' \
--dry-run=client -o yamlapiVersion: v1
data:
password: cGctSHVudGVyMiE=
username: b3JkZXJz
kind: Secret
metadata:
name: orders-db
namespace: shopDecoding takes one command and no key:
$ echo cGctSHVudGVyMiE= | base64 -d
pg-Hunter2!Base64 exists so binary values (a keystore, a TLS key) can travel inside JSON and YAML. It hides nothing. So if you commit that YAML to Git, you’ve committed the password. Teams that want Secrets in Git use Sealed Secrets, SOPS, or the External Secrets Operator pulling from Vault or a cloud secret manager, so only an encrypted form or a reference is stored in the repository.
What actually protects a Secret
Three mechanisms, none of them visible in the Secret manifest.
RBAC. Who can get, list or watch Secrets in a namespace? list is worth singling out: a role that can list Secrets receives every value in the response, not just the names. A common audit finding is a CI service account with secrets: ["*"] across the cluster. Check with:
kubectl auth can-i get secrets -n shop --as=system:serviceaccount:shop:ci-deployerAlso note that anyone who can create a Pod in a namespace can mount any Secret in that namespace into it and read it. Pod-creation rights are effectively Secret-read rights for that namespace. See RBAC and least privilege.
Encryption at rest. By default the API server stores Secrets in etcd unencrypted (the base64 form). Cluster admins enable encryption with an EncryptionConfiguration file passed to the API server, ideally with a KMS v2 provider so the key lives in a cloud KMS or HSM. Managed services such as EKS, GKE and AKS either do this by default or offer it as a setting. If someone gets an etcd backup from a cluster without it, they have every password.
Node scoping. The kubelet fetches a Secret only when a Pod scheduled on that node references it, and Secret volumes are stored in tmpfs, so they don’t land on the node’s disk. A compromised node exposes the Secrets of Pods running on it, not the whole cluster’s.
If an interviewer asks “Are Kubernetes Secrets secure?”, walk through those three, then mention that for high-value credentials many teams keep the source of truth in an external secret manager and use short-lived credentials where possible.
Environment variables or mounted files
There are two ways to get the values into a container, and they behave differently when the value changes.
apiVersion: v1
kind: Pod
metadata:
name: orders
namespace: shop
spec:
containers:
- name: api
image: registry.example.com/orders:4.2.0
envFrom:
- configMapRef:
name: orders-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: orders-db
key: password
volumeMounts:
- name: db-creds
mountPath: /etc/orders/db
readOnly: true
volumes:
- name: db-creds
secret:
secretName: orders-db
defaultMode: 0400Environment variables are read once, at container start. If you rotate the password in the Secret, DB_PASSWORD in the running container still holds the old value until the container restarts. Env vars also leak more easily: they show up in crash dumps, /proc/<pid>/environ, and debug endpoints that print the environment.
Mounted volumes refresh. When the Secret changes, the kubelet updates the files in /etc/orders/db after a delay (its sync period plus the cache TTL, typically under a minute or two). Your application still has to re-read the file; most don’t, unless they watch for changes or open the file on every new connection. One exception catches people out: a volume mounted with subPath never receives updates.
If a referenced Secret or ConfigMap doesn’t exist, or the key is missing, the container won’t start and the Pod shows CreateContainerConfigError. That’s covered in the CreateContainerConfigError guide.
Interview tip
For config that must never change under a running Pod, set
immutable: trueon the ConfigMap or Secret. The kubelet stops watching it, which also reduces API server load in large clusters. To change it, create a new object with a new name (for exampleorders-config-v7) and roll the Deployment.
Rolling out a changed value
Since env vars don’t refresh and most apps don’t re-read files, the dependable way to apply a new value is a rollout. Two common patterns:
- Put a hash of the config in a Pod template annotation (Helm’s
checksum/configpattern, or Kustomize’sconfigMapGenerator, which appends a content hash to the name). Changing the config changes the template, so the Deployment rolls. - Run
kubectl rollout restart deployment/orders -n shopafter updating the object, which stamps a restart annotation onto the template.
The second is fine by hand but easy to forget; the first ties the rollout to the change itself. More detail is in how ConfigMap updates reach Pods.
Interview practice
“A developer stores the Stripe API key in a ConfigMap, arguing that Secrets are only base64 anyway, so there’s no difference. Is the developer right?”
Answer
They’re right that base64 isn’t protection, but wrong about there being no difference. The difference isn’t in the encoding; it’s in how the cluster treats the two kinds of object.
Clusters commonly grant broad read access to ConfigMaps, since they hold non-sensitive settings: developers, dashboards and monitoring agents often have get and list on them. Secrets are where RBAC restrictions are applied, where encryption at rest is configured (an EncryptionConfiguration usually lists secrets as a resource, not configmaps), and where audit policies log access. Many tools also redact Secret values in output and avoid logging them. Putting the Stripe key in a ConfigMap opts it out of all of those controls.
I’d move it to a Secret, check that only the payment service’s service account and a small ops group can read Secrets in that namespace, confirm encryption at rest is on, and consider sourcing it from an external secret manager so rotation doesn’t depend on someone editing YAML.
Where to go next
- Kubernetes interview questions
- Kubernetes MCQs
- ConfigMap updates and running Pods
- CreateContainerConfigError
- Official docs: Secrets