Kubernetes Secret
In the previous article we learned how ConfigMaps store non-sensitive configuration data. But what about passwords, API keys, TLS certificates, and database connection strings? Storing these values in a ConfigMap is a bad idea — ConfigMap data is not encrypted, not access-controlled by default, and appears in plain text whenever you run kubectl get configmap -o yaml.
Kubernetes provides a dedicated object for sensitive data called a Secret. Secrets are designed so that container images can be built without bundling sensitive data inside them. This keeps containers portable across environments — the same image runs in development, staging, and production, and only the Secrets differ.
Secrets are exposed to Pods through explicit declarations in Pod manifests and the Kubernetes API. They leverage native OS isolation primitives: Secret volumes are backed by tmpfs (RAM disks), which means Secret data is never written to a node's physical disk.
Core Concepts
How Secrets Differ from ConfigMaps
Secrets and ConfigMaps look similar on the surface — both are key-value stores consumed by Pods. The differences become important when security matters:
| Feature | ConfigMap | Secret |
|---|---|---|
| Intended data | Non-sensitive configuration | Passwords, tokens, keys, certificates |
| Data encoding | Plain UTF-8 text | Base64-encoded (supports binary data) |
| Volume backing | Regular filesystem | tmpfs (RAM disk) — never written to node disk |
| Size limit | 1 MB | 1 MB |
| RBAC | Cluster-wide read by default | Can be restricted with fine-grained RBAC rules |
| Encryption at rest | Not typically encrypted | Supports EncryptionConfiguration for etcd encryption |
Secret Types
Kubernetes defines several built-in Secret types. The type tells Kubernetes (and human readers) what kind of data the Secret holds:
| Type | Description |
|---|---|
Opaque | The default type. Can hold any arbitrary key-value data. |
kubernetes.io/tls | Holds a TLS certificate and private key. Requires tls.crt and tls.key data keys. |
kubernetes.io/dockerconfigjson | Stores credentials for private container registries. |
kubernetes.io/basic-auth | Stores a username and password. |
kubernetes.io/ssh-auth | Stores an SSH private key. |
The most common types you will use day-to-day are Opaque (for database passwords, API keys, tokens) and kubernetes.io/tls (for HTTPS certificates).
Base64 Encoding — Not Encryption
A common source of confusion: Secret values stored in the data field are base64-encoded, not encrypted. Base64 is a reversible encoding scheme — anyone who can read the Secret can decode the values instantly:
Base64 exists so that Secrets can hold binary data (like TLS certificates) inside YAML, which is a text format. It provides zero security on its own.
If you want to write values in plain text when creating a Secret manifest, use the stringData field instead of data. Kubernetes will base64-encode them automatically when the Secret is stored:
Security Warning: Default Storage
By default, Kubernetes stores Secrets in plain text in etcd — the cluster's key-value store. This means anyone with cluster administration rights can read every Secret. For production clusters, you should:
- Enable encryption at rest using an
EncryptionConfigurationwith a user-supplied key (often integrated with a cloud KMS). - Use RBAC rules to restrict who can read Secrets.
- Consider the Secrets Store CSI Driver to pull secrets directly from your cloud provider's key vault (Azure Key Vault, AWS Secrets Manager, HashiCorp Vault) instead of storing them in etcd at all.
Naming Constraints
Secret and ConfigMap key names must be valid environment variable names. The rules are:
- May begin with a dot (
.), then a letter or digit. - Followed by letters, digits, dots, dashes (
-), or underscores (_). - Dots cannot repeat. Dots cannot be adjacent to dashes or underscores.
Some examples:
| Valid Key Name | Invalid Key Name |
|---|---|
.auth_token | Token..properties |
key.pem | auth file.json |
config_file | _password.txt |
When choosing key names, remember that they become file names in volume mounts. A key named key.pem is much clearer than tls-key when configuring applications to read certificate files.
Hands-On: Kubernetes Commands
Creating Secrets Imperatively
The kubectl create secret command supports several sub-commands depending on the Secret type.
Generic (Opaque) Secrets
Use generic to create an Opaque Secret from literals or files:
You can also load values from files. Each file's name becomes the key, and its content becomes the value:
TLS Secrets
Kubernetes provides a dedicated sub-command for TLS Secrets that validates the certificate and key:
This creates a Secret of type kubernetes.io/tls with keys tls.crt and tls.key.
Docker Registry Secrets
To store credentials for pulling images from a private container registry:
Viewing Secrets
To list all Secrets in the current Namespace:
To view the metadata (without revealing actual values):
This shows key names and byte sizes but not the values — a deliberate safety measure. To see the base64-encoded values:
To decode a specific value on the command line:
Editing a Secret
This opens the Secret in your editor. Values are shown base64-encoded. Remember to encode any new values before saving:
Deleting a Secret
Like ConfigMaps, deleting a Secret that is referenced by a running Pod will cause that Pod to fail if it restarts.
Step-by-Step Example
In this walkthrough, we build a realistic scenario: a .NET Billing API that needs database credentials as environment variables and TLS certificates as files. We will create two Secrets and a Deployment that consumes both.
Step 1: Create the Database Credentials Secret
This Opaque Secret holds the database connection details for our Billing API. Save this as billing-api-db-credentials.yaml:
Every value in the data field is base64-encoded. Here is what each one decodes to:
| Key | Base64 Value | Decoded Value |
|---|---|---|
DB_HOST | cG9zdGdyZXMuaW50ZXJuYWwubG9jYWw= | postgres.internal.local |
DB_PORT | NTQzMg== | 5432 |
DB_NAME | YmlsbGluZ19kYg== | billing_db |
DB_USER | YmlsbGluZ19zdmM= | billing_svc |
DB_PASSWORD | WDlrIXZMcDJAZFFtNyE= | X9k!vLp2@dQm7! |
You can generate base64 values on the command line:
The -n flag is critical — without it, echo appends a newline character, which gets encoded into the value and will cause your application to fail with cryptic connection errors.
Apply the Secret:
Step 2: Create the TLS Secret
This Secret stores the HTTPS certificate and private key. In production, your certificates come from a Certificate Authority (like Let's Encrypt). For this example, we define the Secret type as kubernetes.io/tls. Save this as billing-api-tls.yaml:
In real-world usage, you would replace the placeholder values with the actual base64-encoded contents of your certificate and key files. The easiest way to create a TLS Secret is imperatively:
Kubernetes validates that tls.crt and tls.key are present and that the certificate and key are properly formatted.
Step 3: Create the Deployment That Consumes Both Secrets
This Deployment injects database credentials as environment variables and mounts TLS certificates as files. Save this as billing-api-deployment.yaml:
Apply the Deployment:
Step 4: Understanding the Environment Variable Method
The Deployment uses secretKeyRef to inject individual keys from the Secret as environment variables:
This works exactly like configMapKeyRef for ConfigMaps, but references a Secret instead. In .NET, the double underscore (__) in Database__Host maps to the configuration path Database:Host in IConfiguration.
You can also inject all keys at once with envFrom:
This creates environment variables DB_HOST, DB_PORT, DB_NAME, DB_USER, and DB_PASSWORD — one for each key in the Secret.
Step 5: Understanding the Volume Mount Method
The TLS certificates are mounted as files using a Secret volume:
After the Pod starts, the container will see:
These files are stored on a tmpfs volume (RAM disk). They are never written to the node's physical disk, which reduces the risk of credential leakage even if someone gains access to the node's storage.
Verify the mounted files by exec-ing into a running Pod:
Step 6: Verify the Environment Variables
Confirm that Secret values were injected into the container's environment:
You should see the decoded (plain text) values — Kubernetes automatically decodes base64 when injecting Secret data into environment variables or volume files.
Step 7: Private Container Registry Secrets
A special use case for Secrets is storing credentials for private container registries. When your Pod needs to pull an image from a private registry (like Azure Container Registry or a private Docker Hub repo), Kubernetes needs credentials to authenticate.
Create a registry Secret:
Then reference it in any Pod that pulls from that registry using spec.imagePullSecrets:
imagePullSecrets tells the kubelet to use the specified Secret when authenticating with the container registry. Without it, the Pod will enter an ImagePullBackOff state because the registry will reject the unauthenticated pull request.
If every Pod in a Namespace uses the same registry, you can attach the Secret to the default ServiceAccount so you do not have to repeat it in every Pod manifest:
Step 8: Clean Up
Remove all the resources we created:
Or delete from the manifest files:
Summary
Secrets provide a Kubernetes-native mechanism for storing and distributing sensitive configuration data. Here is what we covered:
- A Secret stores sensitive data (passwords, tokens, certificates) as base64-encoded key-value pairs. It is not the same as encryption — base64 is reversible.
- Kubernetes offers several Secret types:
Opaquefor general data,kubernetes.io/tlsfor certificates, andkubernetes.io/dockerconfigjsonfor registry credentials. - You can create Secrets imperatively with
kubectl create secret(generic, tls, or docker-registry) or declaratively from a YAML manifest usingdata(base64) orstringData(plain text). - Secrets are consumed by Pods in two main ways:
- Environment variables — using
secretKeyReffor individual keys orenvFromwithsecretReffor all keys. - Volume mounts — mounting the Secret as a directory where each key becomes a file on a tmpfs RAM disk.
- Image pull Secrets authenticate with private container registries. They are referenced via
spec.imagePullSecretsor attached to the default ServiceAccount. - By default, Secrets are stored in plain text in etcd. For production, enable encryption at rest, apply RBAC restrictions, and consider using the Secrets Store CSI Driver to integrate with external key vaults.
- Key names in both Secrets and ConfigMaps must follow strict naming rules — no spaces, no repeated dots, no leading underscores. Choose names that make sense as file paths (e.g.,
key.pemovertls-key).