When you build applications on Kubernetes, you quickly run into a common problem: your workloads need credentials, API keys, tokens, or certificates, but you do not want to hardcode them into your images or application code.
That is where Secrets come in.
Kubernetes Secrets let you store sensitive values separately from your application and inject them into Pods only when needed. They work similarly to ConfigMaps from the Pod’s perspective, but they are meant for confidential data.
In this article, we will look at what Secrets are, how they differ from ConfigMaps, how to create them, and how to consume them through environment variables or mounted files. We will also cover the security caveats that every developer and cluster operator should know.
What Is a Kubernetes Secret?
A Secret is a Kubernetes object designed to hold a small amount of sensitive data, such as:
- database usernames and passwords
- API tokens
- TLS certificates and keys
- registry credentials
The main benefit is decoupling sensitive configuration from your application code and container image. Instead of baking credentials into an image or a deployment manifest, you store them separately and reference them from the Pod.
This improves portability and reduces the chance of accidental exposure during normal development and deployment workflows.
Secrets Are Not Encryption by Themselves
One of the most important things to understand is this:
Base64 is encoding, not encryption.
If you look at a Secret manifest, you will often see values under the data field that look unreadable. That does not mean the secret is secure by default. Base64 can be decoded very easily.
For example:
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
username: ZGJfdXNlcgo=
password: ZGJfcGFzcwo=
Those values are just base64-encoded strings.
Anyone who can retrieve the Secret data can decode it:
echo 'ZGJfdXNlcgo=' | base64 --decode
That is why you should never treat base64 as a protection mechanism.
Why Secrets Still Matter
If Secrets are not encrypted by default, why use them at all?
Because they still give you an important separation of concerns:
- your app code stays free of hardcoded credentials
- the Secret can be managed independently from the Pod
- access can be controlled with RBAC
- managed Kubernetes platforms often integrate with external secret managers
- Kubernetes provides standard ways to inject credentials into workloads
So Secrets are useful, but they are only part of the security story.
Common Secret Types
The most common Secret type is:
Opaque— a generic key-value secret
You will also encounter built-in types for more specific use cases, such as:
- TLS secrets
- Docker or registry authentication secrets
If you see type: Opaque, it usually means the Secret contains arbitrary key-value pairs.
Creating a Secret with kubectl
In practice, many teams prefer creating Secrets with kubectl instead of writing raw secret values into YAML files.
A simple example:
kubectl create secret generic db-creds \
--from-literal=username=db_user \
--from-literal=password=db_pass
This creates a generic Secret named db-creds.
To list Secrets:
kubectl get secrets
To inspect the object:
kubectl describe secret db-creds
Notice that kubectl describe does not print the actual values. It only shows metadata and the size of each entry.
If you explicitly output the full object, you will still see the base64 data:
kubectl get secret db-creds -o yaml
And if you want to inspect one value:
kubectl get secret db-creds -o jsonpath='{.data.username}' | base64 --decode
echo
That is exactly why storing Secrets in Git as plain manifests is risky. A base64-encoded value is still effectively plain text for anyone who can access the file.
Using a Secret as Environment Variables
One common pattern is mapping specific Secret keys into environment variables.
Here is a Pod example:
apiVersion: v1
kind: Pod
metadata:
name: secret-env-demo
spec:
containers:
- name: my-app
image: busybox:1.36.1
command: ["sh", "-c", "echo \"$DB_USER\" && echo \"$DB_PASSWORD\" && sleep 1800"]
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-creds
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-creds
key: password
Apply it:
kubectl apply -f pod.yaml
Check the logs:
kubectl logs secret-env-demo
The container receives the decoded values, not the base64 strings.
That is convenient, but it also means your application must handle secrets carefully. If your app prints environment variables or logs credentials, the Secret is exposed in plain text at runtime.
Importing All Keys at Once
If you want to expose all keys from a Secret as environment variables, you can use envFrom:
envFrom:
- secretRef:
name: db-creds
That is shorter, but it also makes it easier to expose more data than you really need. In most production setups, mapping only the required keys is the safer option.
Important Runtime Note
If a container consumes Secret data through environment variables, updates to the Secret are not reflected in the running container automatically. The container typically needs a restart to pick up the new values.
Mounting a Secret as Files
Another common pattern is mounting Secret data as files.
This is especially useful when an application expects:
- a certificate file
- a private key
- a config file
- credentials stored at a filesystem path
Example:
apiVersion: v1
kind: Pod
metadata:
name: secret-volume-demo
spec:
containers:
- name: my-app
image: busybox:1.36.1
command: ["sh", "-c", "sleep 1800"]
volumeMounts:
- name: db-secrets
mountPath: /etc/db
readOnly: true
volumes:
- name: db-secrets
secret:
secretName: db-creds
After the Pod starts, each key becomes a file under /etc/db:
kubectl exec -it secret-volume-demo -- sh
cd /etc/db
ls
cat username
cat password
If your Secret contains username and password, Kubernetes will create files with those names.
Mounting Only Selected Keys
You can also mount only selected keys and control the output path:
volumes:
- name: db-secrets
secret:
secretName: db-creds
items:
- key: password
path: dev/password
With that configuration, only the password key is mounted, and it appears at:
/etc/db/dev/password
This is useful when:
- your application expects a specific file layout
- you want to avoid exposing unnecessary keys
- you want cleaner separation between secret values
Secret Security Best Practices
Using Secrets correctly is just as important as creating them.
Here are the practices that matter most:
- Enable encryption at rest for Secrets in your cluster.
- Use least-privilege RBAC so only the right identities can read or modify Secrets.
- Avoid storing Secret manifests with sensitive data in source control.
- Do not log secret values from your application.
- Only expose a Secret to the containers that actually need it.
- Be careful with
kubectl exec: anyone who can exec into a Pod that mounts a Secret may be able to read it. - Consider using a cloud secret manager or an external secret solution for stronger operational security.
A subtle but important point: in Kubernetes, a user who is allowed to create a Pod in a namespace can potentially gain access to Secrets in that namespace by mounting or referencing them. That is why namespace-level permissions must be designed carefully.
Secrets vs ConfigMaps
A simple rule of thumb:
- use ConfigMaps for non-sensitive configuration
- use Secrets for sensitive configuration
From a Pod perspective, both can be consumed in similar ways:
- environment variables
- mounted files
But the intent is different, and the security handling should be different too.
If a value would be a problem when exposed publicly, it belongs in a Secret, not in a ConfigMap.
Managed Kubernetes and External Secret Managers
When you use a managed Kubernetes platform, you often get better options than storing every credential directly in native Secret objects.
Common approaches include integrating Kubernetes with:
- cloud-native secret managers
- external secret operators
- Vault-style centralized secret systems
This can improve rotation, auditability, and lifecycle management.
Native Kubernetes Secrets are still widely used, but many teams treat them as the delivery mechanism rather than the ultimate source of truth.
Final Thoughts
Kubernetes Secrets solve a real operational need: they let you keep sensitive values outside your application code and inject them into Pods in a standard way.
But they are not magically secure by default.
Base64 is not encryption. A Secret becomes plain text again when your container reads it. And weak RBAC or careless logging can undo the benefit very quickly.
So the right mindset is this:
- use Secrets instead of hardcoding credentials
- understand their limits
- apply RBAC carefully
- enable encryption at rest
- protect the runtime path just as much as the stored value
If you do that, Secrets become a practical and useful part of your Kubernetes security model instead of a false sense of safety.
References
- Kubernetes documentation: Secrets - kubernetes[.]io/docs/concepts/configuration/secret/
- Kubernetes documentation: Distribute Credentials Securely Using Secrets - kubernetes[.]io/docs/tasks/inject-data-application/distribute-credentials-secure/
- Kubernetes documentation: Good practices for Kubernetes Secrets - kubernetes[.]io/docs/concepts/security/secrets-good-practices/
- Kubernetes documentation: kubectl create secret generic - kubernetes[.]io/docs/reference/kubectl/generated/kubectl_create/kubectl_create_secret_generic/