Configuration & Secrets: ConfigMaps, Secrets, and Environment Variables
📅 Published: May 2026
⏱️ Estimated Reading Time: 18 minutes
🏷️ Tags: Kubernetes, ConfigMaps, Secrets, Environment Variables, Configuration Management
Introduction: The Configuration Problem
Applications need configuration. Database connection strings, feature flags, API endpoints, environment names—these values change between development, staging, and production. They should not be hardcoded into container images.
Hardcoding configuration leads to:
Different images for each environment
Security risks from embedded secrets
Redeploys for simple config changes
Configuration drift between environments
Kubernetes solves this with ConfigMaps and Secrets. These objects separate configuration from container images, allowing you to change settings without rebuilding or redeploying.
This guide covers how to use ConfigMaps for non-sensitive configuration, Secrets for sensitive data, and environment variables for runtime configuration.
Part 1: Environment Variables in Kubernetes
What Are Environment Variables?
Environment variables are key-value pairs available to processes running in a container. They are the simplest way to pass configuration to your application.
Ways to Set Environment Variables
1. Hardcoded in Pod spec
apiVersion: v1 kind: Pod metadata: name: my-pod spec: containers: - name: app image: nginx env: - name: APP_ENV value: "production" - name: LOG_LEVEL value: "info" - name: PORT value: "8080"
2. Using valueFrom to reference other sources
env: - name: MY_NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName - name: MY_POD_IP valueFrom: fieldRef: fieldPath: status.podIP - name: MY_POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace
3. Using resourceFieldRef for resource limits
env: - name: MY_CPU_LIMIT valueFrom: resourceFieldRef: containerName: app resource: limits.cpu - name: MY_MEMORY_REQUEST valueFrom: resourceFieldRef: containerName: app resource: requests.memory
Part 2: ConfigMaps
What is a ConfigMap?
A ConfigMap is an API object used to store non-sensitive configuration data in key-value pairs. ConfigMaps can be consumed by Pods as environment variables, command-line arguments, or configuration files mounted as volumes.
Think of a ConfigMap as a configuration file that lives inside Kubernetes. Your Pods can read from it without needing to rebuild your container image.
ConfigMap YAML Example
apiVersion: v1 kind: ConfigMap metadata: name: app-config namespace: default data: # Key-value pairs APP_ENV: production LOG_LEVEL: info API_BASE_URL: https://api.example.com # Multi-line values (using pipe) app.properties: | database.host=postgres.db.svc database.port=5432 cache.ttl=3600
Creating ConfigMaps
# From literal values kubectl create configmap app-config --from-literal=APP_ENV=production --from-literal=LOG_LEVEL=info # From a file kubectl create configmap app-config --from-file=./app.properties # From a directory (each file becomes a key) kubectl create configmap app-config --from-file=./config/ # From an env file kubectl create configmap app-config --from-env-file=config.env # From YAML kubectl apply -f configmap.yaml
Using ConfigMaps as Environment Variables
apiVersion: v1 kind: Pod metadata: name: pod-with-configmap spec: containers: - name: app image: nginx env: # Import specific key - name: APP_ENV valueFrom: configMapKeyRef: name: app-config key: APP_ENV - name: LOG_LEVEL valueFrom: configMapKeyRef: name: app-config key: LOG_LEVEL # Import all keys (envFrom) envFrom: - configMapRef: name: app-config
Using ConfigMaps as Volume Mounts
apiVersion: v1 kind: Pod metadata: name: pod-with-configmap-volume spec: containers: - name: app image: nginx volumeMounts: - name: config-volume mountPath: /etc/config volumes: - name: config-volume configMap: name: app-config
When mounted as a volume, each key becomes a file containing the key's value.
Inside the container: /etc/config/APP_ENV → contains "production" /etc/config/LOG_LEVEL → contains "info" /etc/config/app.properties → contains multi-line content
ConfigMap with Items (Selective Mounting)
volumes: - name: config-volume configMap: name: app-config items: - key: app.properties path: application.properties - key: LOG_LEVEL path: log/level.txt
ConfigMap Immutability
apiVersion: v1 kind: ConfigMap metadata: name: immutable-config immutable: true data: important-setting: "value-cannot-change"
Once immutable: true is set, the ConfigMap cannot be changed. This is useful for configuration that must not be altered after deployment.
Part 3: Secrets
What is a Secret?
A Secret is similar to a ConfigMap but designed for sensitive information: passwords, API keys, tokens, SSH keys, TLS certificates. Secrets are stored encoded (not encrypted by default) and are more tightly controlled.
Secret YAML Example
apiVersion: v1 kind: Secret metadata: name: db-secret type: Opaque data: # Values must be base64 encoded username: YWRtaW4= # admin password: c3VwZXJzZWNyZXQ= # supersecret --- # Or using stringData (plain text, Kubernetes encodes automatically) apiVersion: v1 kind: Secret metadata: name: db-secret type: Opaque stringData: username: admin password: supersecret
Creating Secrets
# From literal values kubectl create secret generic db-secret --from-literal=username=admin --from-literal=password=supersecret # From files kubectl create secret generic db-secret --from-file=./username.txt --from-file=./password.txt # From env file kubectl create secret generic db-secret --from-env-file=secrets.env # For TLS certificates kubectl create secret tls tls-secret --cert=tls.crt --key=tls.key # For Docker registry authentication kubectl create secret docker-registry regcred --docker-server=myregistry.io --docker-username=user --docker-password=pass --docker-email=email@example.com # From YAML kubectl apply -f secret.yaml
Using Secrets as Environment Variables
apiVersion: v1 kind: Pod metadata: name: pod-with-secret spec: containers: - name: app image: nginx env: - name: DB_USERNAME valueFrom: secretKeyRef: name: db-secret key: username - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-secret key: password # Import all keys envFrom: - secretRef: name: db-secret
Using Secrets as Volume Mounts
apiVersion: v1 kind: Pod metadata: name: pod-with-secret-volume spec: containers: - name: app image: nginx volumeMounts: - name: secret-volume mountPath: /etc/secrets readOnly: true volumes: - name: secret-volume secret: secretName: db-secret defaultMode: 0400 # Read-only for owner
When mounted as a volume, each key becomes a file containing the decoded secret value.
Secret Types
| Type | Purpose |
|---|---|
| Opaque | Generic key-value secrets (default) |
| kubernetes.io/service-account-token | Service account credentials |
| kubernetes.io/dockercfg | Docker registry credentials (legacy) |
| kubernetes.io/dockerconfigjson | Docker registry credentials |
| kubernetes.io/basic-auth | Basic authentication credentials |
| kubernetes.io/ssh-auth | SSH authentication keys |
| kubernetes.io/tls | TLS certificates |
| bootstrap.kubernetes.io/token | Bootstrap tokens |
Secret Immutability
apiVersion: v1 kind: Secret metadata: name: immutable-secret immutable: true type: Opaque data: api-key: c3VwZXJzZWNyZXQ=
Viewing Secrets
# List secrets kubectl get secrets # Describe secret (does not show values) kubectl describe secret db-secret # Get secret with decoded values kubectl get secret db-secret -o yaml # Decode a specific value kubectl get secret db-secret -o jsonpath="{.data.password}" | base64 -d
Part 4: ConfigMap vs Secret Comparison
| Aspect | ConfigMap | Secret |
|---|---|---|
| Purpose | Non-sensitive configuration | Sensitive information |
| Encoding | Plain text | Base64 encoded |
| Size limit | 1MB | 1MB |
| etcd encryption | No | Optional (enable with flag) |
| RBAC separation | Standard | More restrictive default |
| Use cases | Environment config, feature flags | Passwords, API keys, tokens |
Part 5: Environment Variables from ConfigMaps and Secrets
Complete Example: Pod with Mixed Configuration Sources
apiVersion: v1 kind: Pod metadata: name: app-with-config spec: containers: - name: app image: myapp:latest env: # Hardcoded - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName # From ConfigMap individual keys - name: APP_ENV valueFrom: configMapKeyRef: name: app-config key: APP_ENV # From Secret individual keys - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-secret key: password # Import all ConfigMap keys envFrom: - configMapRef: name: app-config # Import all Secret keys - secretRef: name: db-secret
Updating Configuration
When you update a ConfigMap or Secret, changes are not automatically reflected in running Pods.
Options for updating:
Delete and recreate Pods: Kubernetes eventually restarts them
Use immutable ConfigMaps: Force deployment change
Use a sidecar reloader: Tools like Reloader watch for changes
Mount as volume: Changes appear eventually (may have delay)
# Using Reloader annotation apiVersion: apps/v1 kind: Deployment metadata: name: myapp annotations: reloader.stakater.com/auto: "true" spec: ...
Real-World Scenarios
Scenario 1: Web Application Configuration
A web application needs different configuration for dev, staging, and production.
# config-dev.yaml apiVersion: v1 kind: ConfigMap metadata: name: app-config namespace: dev data: API_URL: https://dev-api.example.com FEATURE_FLAG_X: "true" LOG_LEVEL: debug --- # config-prod.yaml apiVersion: v1 kind: ConfigMap metadata: name: app-config namespace: prod data: API_URL: https://api.example.com FEATURE_FLAG_X: "false" LOG_LEVEL: warn
Scenario 2: Database Credentials Management
Managing database credentials securely.
# Create secrets for each environment kubectl create secret generic db-secret \ --namespace=prod \ --from-literal=username=prod_user \ --from-literal=password=$(openssl rand -base64 32) kubectl create secret generic db-secret \ --namespace=staging \ --from-literal=username=staging_user \ --from-literal=password=$(openssl rand -base64 32)
apiVersion: apps/v1 kind: Deployment metadata: name: api-server namespace: prod spec: template: spec: containers: - name: api image: myapi:latest env: - name: DB_USER valueFrom: secretKeyRef: name: db-secret key: username - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-secret key: password
Scenario 3: TLS Certificate for Ingress
# Create TLS secret kubectl create secret tls example-tls \ --cert=example.crt \ --key=example.key
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-ingress spec: tls: - hosts: - example.com secretName: example-tls rules: - host: example.com http: paths: - path: / pathType: Prefix backend: service: name: web-service port: number: 80
Scenario 4: Application Properties File
An application expects a application.properties file.
apiVersion: v1 kind: ConfigMap metadata: name: app-properties data: application.properties: | server.port=8080 spring.datasource.url=jdbc:postgresql://postgres:5432/mydb spring.datasource.username=${DB_USERNAME} logging.level.com.myapp=DEBUG --- apiVersion: apps/v1 kind: Deployment metadata: name: spring-app spec: template: spec: containers: - name: app image: spring-app:latest env: - name: DB_USERNAME valueFrom: secretKeyRef: name: db-secret key: username volumeMounts: - name: config mountPath: /app/config volumes: - name: config configMap: name: app-properties
Configuration Best Practices
Do's
Use ConfigMaps for environment-specific config: Different ConfigMaps per environment
Use Secrets for sensitive data: Never put passwords in ConfigMaps
Use
envFromsparingly: Explicit keys are easier to trackVersion your ConfigMaps: Use annotations or naming conventions
Set immutable when configuration is stable: Prevents accidental changes
Use RBAC to restrict Secret access: Only necessary Pods should access Secrets
Encrypt Secrets at rest: Enable etcd encryption in production
Don'ts
Never commit Secrets to Git: Use Sealed Secrets or external vault
Don't use environment variables for large config: Use volume mounts instead
Don't embed configuration in images: Separate config from code
Don't share Secrets across namespaces: Create Secrets per namespace
Sealed Secrets for Git
Store encrypted Secrets in Git:
# Install kubeseal wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.5/kubeseal-linux-amd64 -O kubeseal # Create a regular Secret kubectl create secret generic db-secret --dry-run=client --from-literal=password=secret -o yaml > secret.yaml # Seal it kubeseal < secret.yaml > sealed-secret.yaml # Commit sealed-secret.yaml to Git
Summary
| Object | Purpose | Sensitive |
|---|---|---|
| ConfigMap | Non-sensitive configuration | No |
| Secret | Sensitive information | Yes |
| Environment Variables | Runtime config | Either |
Delivery Methods
| Method | Use Case | Update Behavior |
|---|---|---|
| Environment variables | Simple values, flags | Pod restart required |
| Volume mounts | Files, complex config | Auto-update (with delay) |
| Command-line arguments | Startup parameters | Pod restart required |
Practice Questions
What is the difference between a ConfigMap and a Secret?
How do you pass environment variables from a ConfigMap to a container?
Why are Secret values base64 encoded, not encrypted?
How can you make a ConfigMap immutable?
What happens to running Pods when you update a ConfigMap?
Learn More
Practice Kubernetes configuration management with hands-on exercises in our interactive labs:
https://devops.trainwithsky.com/
Comments
Post a Comment