Your application needs a database host, a port number, an API key, and a password. How do you pass these to your containers in Kubernetes?
You should not hardcode them in the Docker image. You should not put passwords in your Deployment YAML either.
Kubernetes has two objects for this:
- ConfigMap — for non-sensitive configuration
- Secret — for sensitive data like passwords and API keys
Why Not Hardcode Config?
Imagine you hardcode a database hostname in your Docker image:
DB_HOST = "prod-db.internal"
Now you cannot use the same image in staging. You cannot change the hostname without rebuilding the image. And when the hostname changes, you have to redeploy everything.
The correct approach: build once, configure at deploy time. The image stays the same. The configuration is injected when the Pod starts.
ConfigMaps
A ConfigMap stores key-value pairs of non-sensitive configuration data.
Use ConfigMaps for:
- Database hostnames and ports
- Feature flags
- Log levels
- App configuration files
Creating a ConfigMap
From the command line:
kubectl create configmap myapp-config \
--from-literal=DB_HOST=postgres \
--from-literal=DB_PORT=5432 \
--from-literal=LOG_LEVEL=info
From a YAML file:
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
data:
DB_HOST: "postgres"
DB_PORT: "5432"
LOG_LEVEL: "info"
kubectl apply -f configmap.yaml
kubectl get configmaps
kubectl describe configmap myapp-config
From a file (for config files):
# app.conf is a configuration file you want to mount
kubectl create configmap app-config --from-file=app.conf
Using ConfigMaps as Environment Variables
Inject all keys as environment variables using envFrom:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:1.0
envFrom:
- configMapRef:
name: myapp-config # Inject all keys from the ConfigMap
Or inject specific keys with valueFrom:
containers:
- name: myapp
image: myapp:1.0
env:
- name: DATABASE_HOST # Variable name in the container
valueFrom:
configMapKeyRef:
name: myapp-config # ConfigMap name
key: DB_HOST # Key to read from the ConfigMap
Using ConfigMaps as Mounted Files
Some apps read configuration from files, not environment variables. Mount a ConfigMap as a file:
# configmap-nginx.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
nginx.conf: |
server {
listen 80;
location / {
return 200 'Hello from nginx!';
}
}
# Deployment that mounts the config file
spec:
containers:
- name: nginx
image: nginx:alpine
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: nginx.conf
volumes:
- name: nginx-config
configMap:
name: nginx-config
Secrets
A Secret stores sensitive data — passwords, API keys, TLS certificates. Secrets work like ConfigMaps, but Kubernetes handles them differently: they are stored separately in etcd and can be encrypted at rest.
Important: Base64 is Not Encryption
Secret values are stored as base64-encoded strings. This is not encryption — it is just encoding. Anyone with access to the Secret can decode it instantly.
echo -n "mysecretpassword" | base64
# Output: bXlzZWNyZXRwYXNzd29yZA==
echo "bXlzZWNyZXRwYXNzd29yZA==" | base64 -d
# Output: mysecretpassword
For real encryption, use an external secrets manager. We cover that in the production section below.
Creating a Secret
From the command line (Kubernetes base64-encodes automatically):
kubectl create secret generic db-credentials \
--from-literal=DB_USER=alex \
--from-literal=DB_PASSWORD=supersecret123
From a YAML file (you must base64-encode manually):
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
DB_USER: YWxleA== # base64 of "alex"
DB_PASSWORD: c3VwZXJzZWNyZXQxMjM= # base64 of "supersecret123"
You can also use stringData to skip base64 (Kubernetes encodes it for you):
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
DB_USER: "alex"
DB_PASSWORD: "supersecret123" # Kubernetes will encode this
kubectl apply -f secret.yaml
kubectl get secrets
kubectl describe secret db-credentials # Values are hidden in output
Secret Types
| Type | Use case |
|---|---|
Opaque | Generic key-value pairs (most common) |
kubernetes.io/tls | TLS certificate and key |
kubernetes.io/dockerconfigjson | Docker registry credentials |
kubernetes.io/service-account-token | Service account tokens |
Using Secrets as Environment Variables
containers:
- name: myapp
image: myapp:1.0
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_USER
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_PASSWORD
Or inject all keys at once:
containers:
- name: myapp
image: myapp:1.0
envFrom:
- secretRef:
name: db-credentials
Using Secrets as Mounted Files
Mount a Secret as files in a directory. This is useful for TLS certificates or config files that contain credentials.
containers:
- name: myapp
image: myapp:1.0
volumeMounts:
- name: db-creds
mountPath: /etc/db-creds
readOnly: true
volumes:
- name: db-creds
secret:
secretName: db-credentials
This creates files at /etc/db-creds/DB_USER and /etc/db-creds/DB_PASSWORD. The app reads the files to get the values.
Complete Example: Web App + Database
Here is a realistic example combining ConfigMaps and Secrets:
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
data:
DB_HOST: "postgres-service"
DB_PORT: "5432"
DB_NAME: "myappdb"
---
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: myapp-secrets
type: Opaque
stringData:
DB_USER: "alex"
DB_PASSWORD: "supersecret123"
---
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 2
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:1.0
envFrom:
- configMapRef:
name: myapp-config # Non-sensitive config
- secretRef:
name: myapp-secrets # Sensitive config
resources:
requests:
cpu: "100m"
memory: "64Mi"
limits:
cpu: "250m"
memory: "128Mi"
kubectl apply -f configmap.yaml -f secret.yaml -f deployment.yaml
Verify the environment variables are set:
# Get a pod name first
kubectl get pods -l app=myapp
# Then exec into the pod
kubectl exec -it <pod-name> -- env | grep DB_
Immutable ConfigMaps and Secrets
For performance, you can make a ConfigMap or Secret immutable. Kubernetes stops watching these objects for changes, which reduces API server load significantly in large clusters.
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config-v1
immutable: true
data:
LOG_LEVEL: "info"
Once immutable, you cannot update it. Create a new ConfigMap (e.g., myapp-config-v2) and update your Deployments to reference it.
Production: Use External Secret Managers
For production, do not store real secrets in Kubernetes Secret YAML files. Anyone with git clone access can read them.
The best practice is to use an external secrets manager:
External Secrets Operator (ESO) — the most popular solution; syncs secrets from AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, or GCP Secret Manager into Kubernetes Secrets automatically
Sealed Secrets — encrypts secrets with a public key so you can safely commit them to git
Example with External Secrets Operator:
# ExternalSecret pulls from AWS Secrets Manager
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-store
kind: SecretStore
target:
name: db-credentials # Creates a Kubernetes Secret with this name
data:
- secretKey: DB_PASSWORD
remoteRef:
key: myapp/prod/db
property: password
This way, the actual secret value never appears in git or YAML files.
Common Mistakes
Thinking base64 = encryption
Base64 is encoding, not encryption. A Kubernetes Secret is only as secure as your cluster’s access controls and etcd encryption. Always use external secret managers for production.
Committing Secret YAML files to git
Even with base64 encoding, anyone can decode it. Never commit Secret YAML with real values to git. Use .gitignore to exclude them, or use Sealed Secrets / External Secrets Operator.
Hardcoding secrets as env vars in Deployment YAML
This is worse than using Secrets:
# WRONG — do not do this
env:
- name: DB_PASSWORD
value: "supersecret123" # Visible to anyone who reads the Deployment YAML
Always use secretKeyRef or secretRef to reference a Secret object.
What’s Next?
Your app is running with proper configuration. But what about the database? Databases need persistent storage that survives Pod restarts. Let us cover that next.
Next: Kubernetes Tutorial #5: Persistent Volumes and Storage