Kubernetes — ConfigMap + Secret
A ConfigMap holding non-sensitive application configuration — including a multi-line file stored as a
key — paired with a Secret for database credentials and API keys, using stringData for
human-readable values.
Overview
Kubernetes separates configuration from application code through two resource types.
A ConfigMap stores arbitrary non-confidential key-value data — environment names,
log levels, feature flags, and even the content of entire configuration files. A
Secret stores sensitive data such as passwords, tokens, and private keys, and is
treated differently by the cluster: it can be encrypted at rest (if configured), is not shown in
plain text in kubectl describe output, and can be restricted with RBAC policies.
Both resources can be consumed by Pods in two ways: as environment variables (individually with
valueFrom or in bulk with envFrom), or as files mounted into the container
filesystem via a volume. This example uses the environment variable pattern in the
Deployment manifest. The
multi-line app.properties key in the ConfigMap is designed to be mounted as a file.
kubectl apply -f configmap-secret.yaml.
If the Secret does not exist when a Pod starts, the Pod will remain in Pending state
with an InvalidRef error until the Secret is created.
Full YAML Copy-paste ready
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-config
namespace: default
data:
APP_ENV: production
LOG_LEVEL: info
API_BASE_URL: https://api.example.com
app.properties: |
server.port=3000
server.timeout=30
feature.new-ui=true
---
apiVersion: v1
kind: Secret
metadata:
name: my-app-secrets
namespace: default
type: Opaque
stringData:
database-url: postgresql://user:password@db-host:5432/myapp
jwt-secret: change-me-to-a-random-secret-at-least-32-chars
api-key: my-secret-api-key
Key sections explained
ConfigMap for non-sensitive configuration
The ConfigMap's data field is a flat map of string keys to string values. Simple values
like APP_ENV: production or LOG_LEVEL: info can be injected directly as
environment variables in a Deployment. The key name becomes the environment variable name, and the
value becomes the environment variable value. Using a ConfigMap instead of hardcoding values in the
Deployment manifest means you can update configuration without changing the Deployment — and without
triggering a rolling restart, unless you have a mechanism like
Reloader watching for
ConfigMap changes.
ConfigMaps can also be mounted as volumes, where each key becomes a file in the mounted directory. This is ideal for applications that read configuration from files rather than environment variables — like Nginx, Prometheus, or any JVM application.
The | block scalar for multi-line file content
The app.properties key uses YAML's block scalar indicator | (literal block
style) to store multiple lines of text as a single string value. Every indented line after the
| becomes part of the value, with newlines preserved. This is a clean way to embed an
entire configuration file inside a Kubernetes manifest. When this ConfigMap key is mounted as a
volume, the resulting file in the container will contain exactly those three lines, making it
indistinguishable from a regular file on disk.
stringData vs data in Secrets
Kubernetes Secrets have two fields for storing values: data and stringData.
The data field requires all values to be base64-encoded — which is error-prone and makes
the manifest harder to read and review. The stringData field accepts plain-text values
and Kubernetes automatically base64-encodes them when storing the Secret in etcd. When you read the
Secret back with kubectl get secret my-app-secrets -o yaml, you will see the values
under the data field (base64-encoded), not stringData.
You can mix data and stringData in the same Secret — for example, if you
need to store a binary certificate (in data) alongside a plain-text password
(in stringData). If the same key appears in both, stringData wins.
type: Opaque
Opaque is the generic, unstructured Secret type. It places no constraints on the keys
or values — you can store anything. Kubernetes also has built-in types for specific use cases:
kubernetes.io/tls for TLS certificates (used automatically by cert-manager),
kubernetes.io/dockerconfigjson for image pull credentials, and
kubernetes.io/service-account-token for service account tokens. Using the right type
gives Kubernetes and tooling additional context, but for general application secrets,
Opaque is the correct choice.
Secrets are not encrypted at rest by default
Despite the name, Kubernetes Secrets are stored in etcd base64-encoded but not encrypted by default. Anyone with direct etcd access or broad RBAC permissions can read them. For production clusters, you should enable encryption at rest for Secrets in the API server configuration. A better long-term approach is to use an external secret manager such as HashiCorp Vault, AWS Secrets Manager, or GCP Secret Manager, integrated into Kubernetes via the External Secrets Operator. These tools keep sensitive data out of etcd entirely and provide audit logs, rotation, and fine-grained access control. Never commit a Secret manifest containing real credentials to version control — use placeholder values and override them at deploy time.
Tips & variations
Mount a ConfigMap key as a file
To mount the app.properties key as a file at /etc/my-app/app.properties
inside the container, add volumes and volumeMounts to the Deployment:
volumes:
- name: config-volume
configMap:
name: my-app-config
items:
- key: app.properties
path: app.properties
containers:
- name: my-app
volumeMounts:
- name: config-volume
mountPath: /etc/my-app
Load all ConfigMap keys as environment variables
Instead of mapping keys one by one with valueFrom.configMapKeyRef, use envFrom
to inject all keys from a ConfigMap (or Secret) as environment variables at once:
envFrom:
- configMapRef:
name: my-app-config
- secretRef:
name: my-app-secrets
Create a Secret from the command line
You can create a Secret without writing a manifest — which keeps plaintext values out of your repo:
kubectl create secret generic my-app-secrets --from-literal=database-url='postgresql://...' --from-literal=jwt-secret='...'.
Combine this with a CI/CD secret store for automated deployments.