On this page
Every application needs configuration: database hostnames, log levels, feature flags, service URLs. If you bake these values into your container image, you need a different image for development, staging and production. That breaks one of the core promises of containers: build once, run anywhere.
A ConfigMap is a Kubernetes object that holds non-sensitive configuration as key-value pairs. Think of it as the settings sheet you hand to an app when it starts. The app (the image) stays the same, and each environment hands it a different sheet.
Kubernetes combines the ConfigMap with your Pod right before the container starts. The container can see the data as environment variables, as files in a small read-only folder, or as command-line arguments. Your code and your runtime configuration stay cleanly apart.
What you will learn
- What a ConfigMap is and why it beats environment variables scattered across manifests
- How to create, inspect, edit and delete ConfigMaps with
kubectl - The three ways a Pod consumes a ConfigMap: environment variables, files and command-line arguments
- How to feed a .NET app its settings and an
appsettings.jsonfile from ConfigMaps - What happens to running Pods when you change a ConfigMap
What a ConfigMap is
A ConfigMap is a dictionary of string keys and string values, stored in the cluster as a Kubernetes object. You can picture it in two ways:
- As a small filesystem: each key becomes a file name and each value becomes the file content.
- As a set of environment variables: each key-value pair becomes a variable inside the container.
ConfigMaps are for non-sensitive data: log levels, service URLs, feature toggles, connection strings without passwords. A ConfigMap can hold at most 1 MiB, so it is meant for settings, not for large files.
A ConfigMap lives in a namespace. Pods in the same namespace can reference it. Pods in another namespace cannot.
Why not put environment variables straight into the Pod spec?
You could, but it gets painful fast:
- Configuration is scattered across many Pod and Deployment manifests instead of living in one place.
- Changing one value means editing every manifest that uses it.
- Environment variables cannot give your app a whole configuration file, such as
appsettings.json, at a known path.
ConfigMaps fix all three. You define the configuration once, reference it from as many Pods as you like and update it in one place.
Three ways to consume a ConfigMap
| Method | How it works | Best for |
|---|---|---|
| Environment variables | Each key-value pair becomes an environment variable inside the container. | Simple values: log levels, ports, feature flags. |
| Volume (files) | The ConfigMap is mounted as a folder. Each key becomes a file and its value becomes the file content. | Structured config files: appsettings.json, nginx.conf. |
| Command-line arguments | Environment variables that come from the ConfigMap are referenced in the container's command or args with $(VAR_NAME). | CLI tools that take flags or positional arguments. |
Working with ConfigMaps from the command line
The fastest way to create a ConfigMap is kubectl create configmap. You can feed it literal values, files or whole directories.
From literal values
Use --from-literal to pass key-value pairs directly:
This creates a ConfigMap named app-settings with two keys: LOG_LEVEL and MAX_RETRIES.
From a file
Suppose you have a file called my-config.txt:
Load it with --from-file. You can combine --from-file and --from-literal in one command:
The file name (my-config.txt) becomes the key and the whole file content becomes the value. To choose a different key, write --from-file=<key>=<path>, for example --from-file=settings.txt=my-config.txt.
From a directory
Point --from-file at a folder to load every file in it:
Each regular file in the folder becomes one key. Subfolders are skipped.
Generate a manifest instead
If you prefer to keep configuration in Git, let kubectl write the YAML for you without creating anything in the cluster:
View a ConfigMap
Print the full YAML, including the data section:
Expected output (metadata such as uid and creationTimestamp trimmed):
The file content is stored word for word under the key my-config.txt. The | in YAML keeps the newlines of a multi-line value.
For a quick summary, or to list ConfigMaps:
Edit a ConfigMap
This opens the ConfigMap in your default editor. Save and close to apply the change. Mounted files pick up the change on their own, but environment variables do not. Step 9 below shows what that means in practice.
Delete a ConfigMap
CreateContainerConfigError or ContainerCreating until you recreate it. If a reference is truly optional, add optional: true to it.Hands-on: configure a .NET Notification API
You will build a realistic setup: a .NET Notification API that needs SMTP settings, a log level and rate-limit settings. You will create two ConfigMaps and a Deployment that reads them through environment variables and a mounted file.
The Deployment uses Microsoft's public sample image mcr.microsoft.com/dotnet/samples:aspnetapp. It is a small ASP.NET Core app that listens on port 8080 and answers health checks on /healthz. It stands in for your own Notification API image, so every command below works on a local cluster such as kind, minikube or Docker Desktop.
Step 1 — Create the key-value ConfigMap
This ConfigMap holds simple settings for SMTP, retries, logging and CORS. Save it as notification-api-config.yaml:
Apply it:
Expected output:
Every value in a ConfigMap is a string. That is why "587" and "3" are quoted: without quotes YAML reads them as numbers and the API server rejects the manifest. Your application parses the strings into the right types.
Step 2 — Create the file-based ConfigMap
This ConfigMap holds a whole appsettings.Production.json file, for apps that expect a structured configuration file at a known path. Save it as notification-api-appsettings.yaml:
Apply it:
When you mount this ConfigMap as a volume, the key appsettings.Production.json becomes the file name and the JSON after the | becomes its content. Your .NET app reads it like any other appsettings file.
Step 3 — Create the Deployment that uses both ConfigMaps
This Deployment uses both environment-variable methods and a volume mount in one resource. Save it as notification-api-deployment.yaml:
Apply it and wait until both replicas are ready:
Expected output:
The next three steps take this Deployment apart, one ConfigMap method at a time.
Step 4 — Pick individual keys with valueFrom
The env list picks specific keys from a ConfigMap and gives each one the environment variable name you choose:
Use this when the ConfigMap key names do not match what your app expects. In .NET, a double underscore (__) separates configuration sections, so SmtpSettings__Host becomes SmtpSettings:Host in IConfiguration.
Step 5 — Load every key with envFrom
The envFrom block injects every key in the ConfigMap as an environment variable with the same name:
The container now has SMTP_HOST, SMTP_PORT, RETRY_COUNT, LOG_LEVEL and ALLOWED_ORIGINS. This is the quickest option when you control the key names. If a name appears in both env and envFrom, the value from env wins.
Step 6 — Read the mounted configuration file
The volume mount projects the second ConfigMap as files inside the container:
Each key becomes one file, so the container sees /app/config/appsettings.Production.json. A ConfigMap with three keys would produce three files. Check it from a running Pod:
Expected output:
Print the file content:
ASP.NET Core loads appsettings.Production.json from the app folder (/app), not from /app/config. In your own app, add the mounted file to configuration in Program.cs:
/app would hide your app's DLLs and the container would fail to start. Mount into a subfolder such as /app/config. A subPath mount can place a single file instead, but that file never receives ConfigMap updates.Step 7 — Verify the environment variables
Confirm that the values were injected. The pipe runs on your machine, so only printenv runs inside the container:
Expected output (the order may differ):
Step 8 — Pass ConfigMap values as command-line arguments
The third method puts ConfigMap values into the container's startup command. Kubernetes replaces each $(VAR_NAME) in command or args with the value of that environment variable. Save this standalone Pod as config-cmd-demo.yaml:
Apply it and read the log:
Kubernetes expands the variables before the shell runs, so the command becomes sh -c "echo Host=mail.internal.local Port=587 && sleep 3600". Expected output:
This is handy for tools that read their configuration from command-line flags rather than from environment variables.
Step 9 — Update a ConfigMap
Change the log level from Information to Debug by editing the ConfigMap in place:
Or change LOG_LEVEL in notification-api-config.yaml and apply the file again:
What running Pods see after the update depends on how they consume the ConfigMap:
| Consumed as | Updated in running Pods? | What to do |
|---|---|---|
| Mounted files | Yes, automatically, usually within a minute or two (kubelet sync period plus cache delay) | Make sure your app re-reads the file. In .NET, reloadOnChange: true asks for that. |
Files mounted with subPath | No | Restart the Pods, or mount the whole folder instead. |
Environment variables (env, envFrom) | No, they are set once when the container starts | Restart the Pods. |
| Command-line arguments | No, they are expanded once at start | Restart the Pods. |
To restart a Deployment's Pods without downtime, roll them:
Step 10 — Clean up
Remove everything you created in this lesson:
If the manifests are in the same folder, you can also delete by file:
Common mistakes
| Symptom | Likely cause | Fix |
|---|---|---|
Pod stuck in CreateContainerConfigError | The ConfigMap or the key does not exist, or lives in another namespace | Check the names with kubectl get configmap -o yaml and create it in the Pod's namespace |
kubectl apply rejects the ConfigMap | An unquoted number or boolean under data | Quote every value: "587", "true" |
| App still uses the old value | The value comes from an environment variable | kubectl rollout restart deployment/<name> |
| Mounted file never changes | It is mounted with subPath | Mount the folder, or restart the Pods after each change |
| App files disappeared from the container | The volume was mounted over the app folder | Mount into a subfolder such as /app/config |
Lock settings with immutable ConfigMaps
If a ConfigMap should never change after you create it, add immutable: true at the top level, next to data. Kubernetes then rejects any edit, which protects you from accidental changes and lowers the load on the API server in large clusters. To change an immutable ConfigMap, delete and recreate it, then restart the Pods that use it.
Summary
ConfigMaps separate configuration from container images, so you can follow the build once, run anywhere principle. The same image runs everywhere; only the ConfigMap changes.
- A ConfigMap stores non-sensitive configuration as string key-value pairs inside a namespace, up to 1 MiB.
- Create one imperatively with
kubectl create configmap(from literals, files or folders) or declaratively from a YAML manifest. - Consume it as environment variables (
configMapKeyReffor single keys,envFromfor all keys), as files in a mounted volume, or as command-line arguments with$(VAR_NAME). - Mounted files update automatically after a short delay. Environment variables and arguments need a Pod restart:
kubectl rollout restart. - ConfigMaps are for non-sensitive data only. Passwords, API keys and certificates go into Secrets.
Next, you will meet Secrets, which use the same patterns for the data you must keep private.