ConfigMaps and Secrets decouple configuration from container images, making your applications portable and configurable across environments. ConfigMaps store non-sensitive data, while Secrets store sensitive data like passwords, API keys, and certificates.
Why Separate Configuration?
flowchart TB BadImage["Without ConfigMaps/Secrets<br/>Image: myapp:v1<br/>DB_HOST=db.prod<br/>DB_PASS=hunter2"] GoodImage["With ConfigMaps/Secrets<br/>Image: myapp:v1<br/>(no config)"] GoodImage --> Config["ConfigMap<br/>DB_HOST=db.prod"] GoodImage --> Secret["Secret<br/>DB_PASS=***"] classDef risky fill:#2a1717,stroke:#d94f4f,color:#f8d7da classDef safe fill:#102a22,stroke:#2ab67d,color:#d8fff0 class BadImage risky class GoodImage,Config,Secret safe
| Problem | ConfigMap/Secret Solution |
|---|---|
| Config baked into image | Configuration injected at runtime |
| Different config per environment | Same image, different ConfigMap per namespace |
| Secrets in code or env files | Secrets stored separately with RBAC controls and encryption at rest |
| Config change = new image build | Update ConfigMap, restart pod (no rebuild) |
ConfigMaps
ConfigMaps store non-sensitive configuration as key-value pairs. Pods consume them as environment variables, command-line arguments, or mounted files.
Creating ConfigMaps
# From literal values
kubectl create configmap app-config \
--from-literal=DB_HOST=db.production.internal \
--from-literal=DB_PORT=5432 \
--from-literal=LOG_LEVEL=info
# From a file
kubectl create configmap nginx-config \
--from-file=nginx.conf=./nginx.conf
# From a directory (each file becomes a key)
kubectl create configmap app-configs \
--from-file=config/ # config/app.json, config/settings.yaml
# From an env file
kubectl create configmap env-config \
--from-env-file=.env
# From YAML
kubectl apply -f configmap.yamlConfigMap YAML
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DB_HOST: "db.production.internal"
DB_PORT: "5432"
LOG_LEVEL: "info"
# Multi-line value
app.properties: |
cache.enabled=true
cache.ttl=3600
max.connections=100Using ConfigMaps in Pods
As environment variables:
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: app
image: my-app:v1
envFrom:
- configMapRef:
name: app-config # All keys become env vars
# OR specific keys:
env:
- name: DATABASE_HOST # Env var name in container
valueFrom:
configMapKeyRef:
name: app-config # ConfigMap name
key: DB_HOST # Key in ConfigMapAs mounted files:
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: app
image: my-app:v1
volumeMounts:
- name: config-volume
mountPath: /etc/config
readOnly: true
volumes:
- name: config-volume
configMap:
name: app-configTip: Mounted ConfigMaps are updated automatically when the ConfigMap changes (with a slight delay). Environment variables are NOT updated — the pod must be restarted to pick up new env var values.
ConfigMap Consumption Methods
| Method | Updates Live? | Best For |
|---|---|---|
| Environment variables | No (requires pod restart) | Simple config values, startup config |
| Mounted volume | Yes (auto-updated within ~60s) | Config files, dynamic settings |
| Command-line arguments | No (requires pod restart) | Entrypoint parameters |
Secrets
Secrets store sensitive data. In GKE, Secrets are encrypted at rest by default.
Secret Types
| Type | Purpose | Example |
|---|---|---|
Opaque | Arbitrary user data (default) | API keys, passwords |
kubernetes.io/tls | TLS certificates | tls.crt, tls.key |
kubernetes.io/dockerconfigjson | Container registry credentials | ~/.docker/config.json |
kubernetes.io/basic-auth | Basic authentication | Username, password |
kubernetes.io/ssh-auth | SSH keys | ssh-privatekey |
Creating Secrets
# From literal values (base64-encoded automatically)
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password='S3cureP@ssw0rd!'
# From files
kubectl create secret generic tls-secret \
--from-file=tls.crt=server.crt \
--from-file=tls.key=server.key
# Docker registry credentials
kubectl create secret docker-registry gcr-secret \
--docker-server=gcr.io \
--docker-username=_json_key \
--docker-password="$(cat keyfile.json)" \
[email protected]
# TLS secret
kubectl create secret tls my-tls-secret \
--cert=cert.pem \
--key=key.pemSecret YAML
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: YWRtaW4= # base64 of "admin"
password: UzNjdXJlUEBzc3cwcmQh # base64 of "S3cureP@ssw0rd!"Note: When writing YAML directly, you must base64-encode values. Use
echo -n 'admin' | base64. When usingkubectl create secret --from-literal, encoding is automatic.
Using Secrets in Pods
As environment variables:
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: app
image: my-app:v1
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: passwordAs mounted files:
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: app
image: my-app:v1
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: db-credentialsAs image pull secrets (private registries):
apiVersion: v1
kind: Pod
metadata:
name: private-app
spec:
imagePullSecrets:
- name: gcr-secret
containers:
- name: app
image: gcr.io/my-project/my-app:v1ConfigMap vs Secret
| Aspect | ConfigMap | Secret |
|---|---|---|
| Purpose | Non-sensitive configuration | Sensitive data |
| Storage | Plain Kubernetes API object data | Base64-encoded Kubernetes API object data, encrypted at rest in GKE |
| Size limit | 1 MiB | 1 MiB |
| Access control | RBAC on the object | RBAC on the object (more restrictive recommended) |
| Best practice | App config, feature flags | Passwords, API keys, certificates |
Warning: Base64 encoding is NOT encryption. Always enable encryption at rest for Secrets in GKE (enabled by default). For production, consider using Google Secret Manager.
Secret Security in GKE
Encryption at Rest
GKE encrypts Secrets at rest by default using Google-managed keys. For additional control:
# Enable Customer-Managed Encryption Keys (CMEK) for application-layer secrets encryption
gcloud container clusters update my-cluster \
--zone=us-central1-a \
--database-encryption-key=projects/PROJECT/locations/REGION/keyRings/KEYRING/cryptoKeys/KEY| Encryption Level | Key Management | Security |
|---|---|---|
| Default | Google-managed keys | Good — encrypted at rest |
| CMEK | Your Cloud KMS key | Better — you control the key |
| Secret Manager | Google Secret Manager | Best — separate from cluster, audit logging |
Using Google Secret Manager
For production workloads, store secrets in Google Secret Manager and access them from pods:
# Enable Secret Manager CSI driver in GKE
gcloud container clusters update my-cluster \
--zone=us-central1-a \
--enable-secret-manager-csi-driver
# Create a secret in Secret Manager
echo -n 'S3cureP@ssw0rd!' | gcloud secrets create db-password --data-file=-# Pod using Secret Manager CSI driver
apiVersion: v1
kind: Pod
metadata:
name: my-app
annotations:
secrets-store.csi.k8s.io/used: "true"
spec:
containers:
- name: app
image: my-app:v1
volumeMounts:
- name: secrets-store
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secrets-store
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "gcp-secret-manager"Key Insight: Secret Manager provides versioning, audit logging, and fine-grained IAM access control. Use it for secrets that need governance and compliance tracking.
Secret Security Best Practices
| Practice | Why | How |
|---|---|---|
| Use Secret Manager for production | Better governance, audit, rotation | CSI driver integration |
| Enable Workload Identity Federation for GKE | No service account keys in pods | --workload-pool=PROJECT.svc.id.goog |
| Restrict Secret RBAC | Limit who can read secrets | kubectl get secrets should be restricted |
Use immutable: true | Prevents accidental modification | Add immutable: true to Secret and ConfigMap |
| Never commit Secrets to Git | Prevents credential leaks | Use Secret Manager or external secret operators |
| Rotate secrets regularly | Reduces blast radius of leaks | Automate rotation with Secret Manager |
Immutable ConfigMaps and Secrets
For performance-sensitive workloads, you can mark ConfigMaps and Secrets as immutable:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
immutable: true # Cannot be changed after creation
data:
LOG_LEVEL: "info"Tip: Immutable ConfigMaps and Secrets get removed from the kubelet’s watch loop, reducing API server load. Use them for config that never changes at runtime.
Common Commands
| Command | Purpose |
|---|---|
kubectl get configmaps | List ConfigMaps |
kubectl describe configmap NAME | View ConfigMap contents |
kubectl create configmap NAME --from-literal=key=value | Create from literal |
kubectl create configmap NAME --from-file=path | Create from file |
kubectl get secrets | List Secrets |
kubectl describe secret NAME | View Secret metadata |
kubectl get secret NAME -o jsonpath='{.data.password}' | base64 -d | Decode a Secret value |
kubectl create secret generic NAME --from-literal=key=value | Create a Secret |
kubectl edit configmap NAME | Edit a ConfigMap in-place |
Warning:
kubectl describe secretshows metadata but redacts values.kubectl get secret -o yamlshows base64-encoded values. Restrict access appropriately.
Common Pitfalls
| Pitfall | Consequence | Fix |
|---|---|---|
| Storing secrets in ConfigMaps | Sensitive data in plaintext | Use Secrets (or Secret Manager) |
Not setting readOnly: true on mounts | Pod can modify mounted config | Always use readOnly: true for config volumes |
| Huge ConfigMaps (>1 MiB) | API server rejects creation | Split into multiple ConfigMaps |
| Hardcoding config in Dockerfiles | Config baked into image | Use ConfigMaps for all environment-specific config |
| Updating ConfigMap env vars | Pods don’t pick up changes | Restart pods after ConfigMap update |
| Not using immutable flag | Unnecessary API server load | Use immutable: true for static config |
TL;DR
- ConfigMaps for non-sensitive config (DB host, log level, feature flags)
- Secrets for sensitive data (passwords, API keys, certificates)
- Consume via environment variables (simple, static) or mounted volumes (auto-updated)
- GKE encrypts Secrets at rest by default; use CMEK or Secret Manager for production
- Never store secrets in ConfigMaps, code, or container images
- Use
immutable: truefor config that never changes at runtime