Inject Secrets as Env Vars
Learn how to securely inject Kubernetes secrets as environment variables into pods. This lesson covers step-by-step methods, best practices, and common pitfalls.
Focus: inject secrets as environment variables
Hardcoding database passwords, API tokens, or TLS certificates directly into your application code (or even worse, into container images) is a security nightmare — one accidental commit to a public repo and your entire infrastructure is compromised. Kubernetes Secrets solve this by decoupling sensitive data from your pod definitions, and one of the most common ways to feed that data to your containers is by injecting secrets as environment variables. This lesson will show you exactly how to do it securely and efficiently, without exposing your secrets in version control or container registries.
The problem this lesson solves
Imagine you deploy a Python app that connects to a PostgreSQL database. The database password is currently hardcoded in a config.py file inside your Docker image. Every time you rebuild and push that image, you risk that password being inspected if someone pulls the image. Worse, if you store that image in a public registry, the password is effectively public.
Kubernetes Secrets are designed to store small amounts of sensitive data (like passwords, tokens, and keys) separately from your pod definitions. But storing them is only half the battle — your application needs to read them at runtime. The most straightforward way to make secrets available to your container's process is by injecting them as environment variables. This approach is easy to understand, quick to implement, and works with almost every application language and framework.
Core concept / mental model
Think of a Kubernetes Secret as a secure, namespaced file cabinet. You place your sensitive values inside it, and the cabinet is locked (base64-encoded at rest). When a pod is scheduled, Kubernetes can hang a copy of those values into the container's memory as environment variables — like handing your app a slip of paper with the password, not the whole file cabinet.
- Secret → The storage object holding your sensitive data (e.g.,
password: supersecret). - Environment Variable → A named value injected into the container's operating system process, accessible by your app via
os.environ['PASSWORD'](in Python) orprocess.env.PASSWORD(in Node.js). - Injection → The act of mapping a key from a Secret to an environment variable name in your pod specification.
Key terms
- Secret key: The name of the field inside the Secret data (e.g.,
password). - Environment variable name: The name your application uses to read the value (e.g.,
DB_PASSWORD). env: The pod spec section that defines container environment variables.valueFrom: The subfield that tells Kubernetes to fetch a value from a Secret.secretKeyRef: A reference pointing to a specific Secret and key within that valueFrom.
Pro tip: The difference between ConfigMaps and Secrets is important. ConfigMaps are for non-sensitive configuration data (like feature flags). Secrets are for anything that gives access to a system — passwords, API keys, SSH keys. Always use Secrets for sensitive data, never ConfigMaps.
How it works step by step
Injecting a secret as an environment variable is a two-step process: define the Secret, then reference it from your pod spec.
Step 1: Create the Secret
You can create a Secret imperatively with kubectl or declaratively from a YAML file. The Secret data values must be base64-encoded. You can use the --from-literal flag to let kubectl handle the encoding for you.
kubectl create secret generic db-secret \
--from-literal=password=supersecret
This creates a Secret named db-secret with one key password and the value supersecret (base64-encoded).
Step 2: Reference the Secret in a pod spec
In your pod's container definition, use env and valueFrom with secretKeyRef.
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod
spec:
containers:
- name: my-app
image: my-app:latest
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
When this pod starts, the container process will have an environment variable DB_PASSWORD with value supersecret.
Step 3: Verify the injection
Exec into your pod and check the environment variables:
kubectl exec my-app-pod -- env | grep DB
Expected output:
DB_PASSWORD=supersecret
Hands-on walkthrough
Let's build a complete example: a busybox pod that prints the injected secret.
Example 1: Simple secret injection
-
Create the Secret:
bash kubectl create secret generic app-secret \ --from-literal=api-key=abc123 \ --from-literal=token=xyz789 -
Create a pod YAML (
secret-env-pod.yaml): ```yaml apiVersion: v1 kind: Pod metadata: name: secret-env-demo spec: containers:- name: demo
image: busybox
command: ["/bin/sh", "-c", "sleep 3600"]
env:
- name: API_KEY valueFrom: secretKeyRef: name: app-secret key: api-key
- name: TOKEN valueFrom: secretKeyRef: name: app-secret key: token ```
- name: demo
image: busybox
command: ["/bin/sh", "-c", "sleep 3600"]
env:
-
Apply the pod:
bash kubectl apply -f secret-env-pod.yaml -
Verify:
bash kubectl exec secret-env-demo -- envOutput will include:
API_KEY=abc123 TOKEN=xyz789
Example 2: Injecting environment variables from a Secret with optional keys
You can also inject all keys from a Secret at once using envFrom.
apiVersion: v1
kind: Pod
metadata:
name: secret-env-all-demo
spec:
containers:
- name: demo
image: busybox
command: ["/bin/sh", "-c", "sleep 3600"]
envFrom:
- secretRef:
name: app-secret
optional: true
With envFrom, every key in the Secret becomes an environment variable with the same name. The optional: true flag means the pod starts even if the Secret doesn't exist yet.
Pro tip: Use
envFromwhen you have many secrets and don't want to rename them. Be careful with key name collisions — if a key already exists from another source, Kubernetes will skip the duplicate silently.
Example 3: Injecting a secret from a YAML file
Sometimes you'll want to define secrets declaratively. Here's how:
-
Create a secret YAML (
db-secret.yaml):yaml apiVersion: v1 kind: Secret metadata: name: db-secret type: Opaque data: db-password: c3VwZXJzZWNyZXQ=The valuec3VwZXJzZWNyZXQ=is the base64 encoding ofsupersecret. You can generate it with:bash echo -n "supersecret" | base64 -
Apply the Secret:
bash kubectl apply -f db-secret.yaml -
Reference it in a pod spec as before.
Compare options / when to choose what
There are several ways to get secret data into your container. Here's how they compare:
| Method | Advantages | Disadvantages | Best for |
|---|---|---|---|
env[].valueFrom.secretKeyRef |
Explicit mapping, rename keys, selective injection | Verbose for many keys | Injecting a few specific secrets |
envFrom[].secretRef |
Injects all keys at once, clean YAML | No key renaming, collision risk | Many secrets, same keys |
| Volume mount | Secrets updated without restart, filesystem access | Slower to read, app must watch files | Long-lived secrets that change |
| Downward API | Exposes pod metadata (e.g., namespace) | Not for arbitrary secrets | Pod metadata, not secrets |
Choice rule: Use
env[].valueFrom.secretKeyReffor two or fewer secrets when you need to rename them. UseenvFromfor more than three secrets when the key names match what your app expects. Avoid storing secrets in ConfigMaps.
Troubleshooting & edge cases
Secret not found or key doesn't exist
If your pod stays in ContainerCreating status, run:
kubectl describe pod <pod-name>
Look for events like:
Warning Failed Error: secret "non-existent" not found
Fix: Check the secret exists in the same namespace: kubectl get secrets.
Environment variable is empty or ""
Your app might reference the wrong key name. First, verify the Secret's data:
kubectl get secret app-secret -o jsonpath='{.data}'
Fix: Ensure the key name in secretKeyRef matches exactly.
Secret values appear as base64 in logs
If your app accidentally logs the raw Secret value, it could still leak. Fix: Never log environment variables that contain secrets. Use environment variables for startup configuration, not for logging.
"optional" secret not ignored on errors
If you use optional: true but the pod still fails, check the env section - optional only works on envFrom, not on individual env entries.
Fix: For individual env vars, wrap the Secret reference in an operator that handles missing values, or use envFrom with optional: true.
What you learned & what's next
You now know how to inject secrets as environment variables into Kubernetes pods. You've seen:
- How to create Secrets with
kubectl create secret generic. - How to reference Secrets in your pod spec using
envwithvalueFrom.secretKeyRef. - How to inject all keys at once using
envFrom. - When to choose env injection over volume mounts or ConfigMaps.
- Common pitfalls like missing secrets and key mismatches.
You can now securely configure your applications without exposing sensitive data in code or images. This is a foundational step in building production-grade Kubernetes deployments.
Next up: Learn how to mount secrets as files in a volume — useful when your app expects configuration in a config file rather than environment variables.
Practice recap
Create a Kubernetes Secret with two keys: username and password. Then deploy a pod that runs busybox and injects both as environment variables (using separate env entries). Finally, exec into the pod and verify the variables are set correctly. For an extra challenge, recreate the same setup using envFrom.
Common mistakes
- Using
envFromwhen you need to rename keys — useenv[].valueFrom.secretKeyRefinstead for explicit mapping. - Forgetting to namespace your Secrets — a pod and its Secret must be in the same namespace.
- Hardcoding the Secret name in multiple places instead of using a variable or Helm value.
- Using a ConfigMap for credentials — ConfigMap data is not encrypted and can be viewed by anyone with access.
Variations
- Use
envFromwithprefixto add a prefix to all injected environment variable names. - Inject secrets from a Secret that has multiple keys using separate
enventries for each key. - Combine with fieldRef (Downward API) to inject both secret and pod metadata as env vars.
Real-world use cases
- A Django app reading database credentials (DB_HOST, DB_USER, DB_PASSWORD) from Kubernetes Secrets injected as env vars.
- A Node.js API server that authenticates with an external service using an API token stored in a Kubernetes Secret.
- A Python ETL pipeline that needs a third-party service account key (JSON) injected as an environment variable.
Key takeaways
- Secrets are base64-encoded, not encrypted — enable encryption at rest in production.
- Use
valueFrom.secretKeyReffor selective injection with key renaming. - Use
envFromfor bulk injection when secret key names match your app's expectations. - Always inject secrets as environment variables, never hardcode them in images.
- Secrets are namespace-scoped — ensure your pod and Secret live in the same namespace.
- For rotating secrets without restarting pods, mount them as volumes instead of env vars.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.