Inject ConfigMaps into Python Pods
Inject ConfigMaps into Python pods as environment — Kubernetes for Python Developers. Learn how to expose configuration data to your Python apps via ConfigMaps, with hands-on steps, troubleshooting, and what's next.
Focus: inject configmaps into python pods as environment
Every Python developer knows the pain: your app works perfectly in local development, but the moment you deploy to Kubernetes, settings like database URLs, API keys, and feature flags are either hardcoded, scattered across multiple YAML files, or worse — baked into the container image at build time. Changing a single environment variable means rebuilding an image, pushing it to a registry, and rolling out a new deployment. That workflow is slow, error-prone, and a security nightmare. This lesson shows you how to inject ConfigMaps into Python pods as environment variables — the Kubernetes-native way to manage configuration separately from your code, so you can change settings on the fly without touching your image or redeploying from scratch.
The problem this lesson solves
When you run a Python app in Kubernetes, the container needs configuration: a DATABASE_URL, an API_KEY, a LOG_LEVEL, maybe a FEATURE_FLAG. Where does that come from? The naive answers all fail in production:
- Hardcoding the value in your Python source means a config change requires a code change, a commit, a CI build, a new image, and a rollout. Slow and risky.
- Storing config in the container image via environment variables defined in the
Dockerfile(e.g.,ENV DATABASE_URL=...) bakes the value into every image. You can't change it without rebuilding, and you'll accidentally leak secrets into your image registry if you're not careful. - Mounting a config file works, but requires the file to exist on disk and complicates how your app reads it.
Kubernetes gives you a dedicated resource for non-sensitive configuration: the ConfigMap. It's a simple key-value store designed exactly for this. When you inject a ConfigMap into a Python pod as environment variables, you decouple configuration from your code and image. You update the ConfigMap, your app picks up the new value on the next pod restart, and you never rebuild an image again.
Core concept / mental model
Think of a ConfigMap as a post-it note board on your cluster. Your Python pod is a task that needs to read those notes. Instead of writing the notes on the task itself (hardcoding) or gluing them to the task's container (baking into the image), you pin them to the board and let the task read them.
Concretely, a ConfigMap is a Kubernetes API object that holds key-value pairs. A pod can consume it in two ways:
- As environment variables — each key becomes an env var in your container, accessible via
os.environ. - As files in a volume — each key becomes a file in a mounted directory, useful for config files like
app.iniorlogging.json.
For this lesson, focus on environment variables, the most common pattern for Python apps. The flow looks like this:
- You create a ConfigMap with your configuration data.
- You reference the ConfigMap in your pod's spec under
envFromorvalueFrom. - Kubernetes injects the values as environment variables into your Python container.
- Your Python code reads them with
os.environ.get("KEY").
This works exactly like the env vars you set locally, so your app code stays clean and portable.
How it works step by step
Let's break down the mechanism:
-
Create the ConfigMap — You define a YAML manifest with
apiVersion: v1,kind: ConfigMap, and adatamap. Keys are strings that become environment variable names; values are the configuration strings. (Values are always strings — numbers and booleans must be quoted.) -
Reference the ConfigMap in the pod spec — There are two ways: -
envFrom: Injects every key in the ConfigMap as an environment variable. Fastest, but you can't control which keys arrive (all of them do). -envwithvalueFrom: Injects a specific key under a chosen env var name. More explicit, gives you renaming and selective injection. -
Deploy the pod — Kubernetes API server stores the ConfigMap, and the kubelet on the node reads it when starting your container.
-
Your Python app reads the env var — import
osand useos.getenv("MY_KEY", "default")in your code. -
Update behavior — If you change the ConfigMap data, Kubernetes does NOT update env vars in running pods. You must restart the pod (e.g.,
kubectl rollout restart deployment/my-app) for the new values to take effect. This is a critical detail — env vars are injected at container start, not dynamically.
The key insight: ConfigMaps are not secrets. They are for non-sensitive data like public endpoints, feature flags, log levels. For credentials, you'll use a Secret — the next lesson in this track.
Hands-on walkthrough
Let's put this into action. You'll need a running Kubernetes cluster (minikube, kind, or Docker Desktop with Kubernetes) and kubectl configured.
Step 1: Create your Python app
Start with a minimal Flask app that reads a config value from the environment:
# app.py
from flask import Flask
import os
app = Flask(__name__)
APP_NAME = os.getenv("APP_NAME", "unknown")
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
FEATURE_FLAG = os.getenv("FEATURE_FLAG", "false").lower() == "true"
@app.route("/")
def index():
return f"App: {APP_NAME} | Log: {LOG_LEVEL} | Feature flag: {FEATURE_FLAG}"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Step 2: Build and push the image
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
requirements.txt: flask==3.0.0
Build and push to a registry (here we use a local minikube registry, but you can use Docker Hub or any registry):
eval $(minikube docker-env) # if using minikube
docker build -t my-python-app:latest .
docker tag my-python-app:latest myregistry/my-python-app:latest
docker push myregistry/my-python-app:latest # skip if using minikube docker-env
Step 3: Create the ConfigMap
Create configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_NAME: "My Python Service"
LOG_LEVEL: "DEBUG"
FEATURE_FLAG: "true"
Apply it:
kubectl apply -f configmap.yaml
Step 4: Deploy the pod that uses the ConfigMap
Create deployment.yaml using envFrom:
apiVersion: apps/v1
kind: Deployment
metadata:
name: python-app
spec:
replicas: 1
selector:
matchLabels:
app: python-app
template:
metadata:
labels:
app: python-app
spec:
containers:
- name: python-app
image: myregistry/my-python-app:latest
ports:
- containerPort: 5000
envFrom:
- configMapRef:
name: app-config
Apply it:
kubectl apply -f deployment.yaml
Step 5: Verify the injection
Check the pod's environment variables:
kubectl exec -it deploy/python-app -- env | grep -E 'APP_NAME|LOG_LEVEL|FEATURE_FLAG'
Expected output:
APP_NAME=My Python Service
LOG_LEVEL=DEBUG
FEATURE_FLAG=true
Now expose the app and test:
kubectl expose deployment python-app --type=NodePort --port=5000
minikube service python-app # or get the node port and curl
Expected output:
App: My Python Service | Log: DEBUG | Feature flag: True
Optional: Selective injection with valueFrom
If you want to rename a key or inject only some keys, use env with valueFrom:
env:
- name: SERVICE_NAME
valueFrom:
configMapKeyRef:
name: app-config
key: APP_NAME
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
Now the pod has SERVICE_NAME and LOG_LEVEL, but not FEATURE_FLAG.
Update flow
Edit the ConfigMap to change a value:
kubectl edit configmap app-config
Change LOG_LEVEL to INFO, save. Then restart the deployment to apply:
kubectl rollout restart deployment/python-app
Now if you exec and check, the new value is there.
Pro tip: Always use
os.getenv("KEY", "default")in your Python code. This makes your app resilient to missing config, which helps during local development and testing when the injected env vars aren't present.
Compare options / when to choose what
When injecting ConfigMaps into Python pods, you have several approaches. Here's a quick comparison:
| Approach | Pros | Cons | Best for |
|---|---|---|---|
envFrom (all keys) |
Fast, no duplication, auto-sync with ConfigMap | Injects everything — you might get unexpected vars; can't rename | Simple apps, configs with many keys |
env + valueFrom (specific keys) |
Explicit, allows renaming, selective | Verbose; you must list every var | Apps that need a curated set of env vars |
| Mount ConfigMap as volume | Works for file-based config (init files, .env), can reload |
Requires file I/O in your app; more complex | Apps that read config from files (e.g., configparser) |
| Use a Secret instead | Handles sensitive data, base64-encoded | Still encoded, not encrypted; more complexity | Credentials, passwords (see next lesson) |
| Helm / Kustomize templating | Dynamic values per environment | Adds tooling overhead | Multi-environment deployments |
For most Python microservices, start with envFrom — it's the least code and aligns with 12-factor app principles. If you need to rename keys or avoid injecting unrelated keys, switch to valueFrom.
Note:
envFromandenvcan be combined. You can useenvFromfor bulk andenvto override a specific key. Pod-level env vars take precedence if names clash.
Troubleshooting & edge cases
Here are the common issues you'll hit:
- Env var not showing up — Check that your pod spec references the ConfigMap correctly. Run
kubectl describe pod <pod-name>and look for theEnvironment:section. If you see an error likeconfigmap "app-config" not found, you forgot to create it or the name is wrong. - Variable value contains special characters — If your value has
$, backticks, or spaces, YAML may parse it incorrectly. Quote the value:APP_NAME: "My $ervice". - Numeric or boolean values — In a ConfigMap, all values are strings. So
count: 3becomes"3". In Python,os.getenv("COUNT")returns"3"— you must convert toint()if needed. Same for booleans: compare with.lower() == "true". - ConfigMap not found in pod — Make sure the ConfigMap is in the same namespace as the pod. Use
kubectl get configmap -n <namespace>. - Changes not reflected — Env vars are injected at container start. After updating the ConfigMap, you must restart the pod/deployment.
kubectl rollout restart deployment/your-appis your friend. envFromconflicts with pod's existing env — The pod-levelenvoverridesenvFrom. If you have both and see unexpected values, check which one is winning.- Deployment fails with
MountVolume.SetUp failed— This is for volume mounts, but if you're usingenvFromand see a mount error, you might have accidentally mixed syntax. Double-check indentation in your YAML.
Pro tip: Use
kubectl get configmap app-config -o yamlto inspect the actual data stored. Andkubectl exec -it <pod> -- env | sortto see all env vars in the pod.
What you learned & what's next
You've learned how to inject ConfigMaps into Python pods as environment variables — the Kubernetes-native way to manage configuration. You now know how to create a ConfigMap, reference it in a deployment using envFrom or valueFrom, and read the values in your Python app with os.getenv. You also understand the update flow and the critical difference between ConfigMaps and Secrets: ConfigMaps are for non-sensitive data, Secrets for credentials.
With configuration handled, the natural next step in this track is Secrets — how to inject sensitive data like API keys and database passwords securely, using the same injection patterns but with proper encryption and access controls. That's already waiting for you in the next lesson.
Keep practicing: create a ConfigMap with a few keys, inject them into a simple Python service, change a value, and restart — you'll be amazed at how much smoother your deployment workflow becomes.
Practice recap
Try this: create a new ConfigMap called my-settings with keys DEBUG_LVL and MAX_RETRIES. Deploy a simple Python pod that references it with envFrom and prints those values. Then update the ConfigMap, restart the deployment, and verify the new values appear. This reinforces the injection and update flow hands-on.
Common mistakes
- Forgetting to quote string values in ConfigMap YAML — numbers and booleans are treated as strings, causing type errors in Python if you don't convert them.
- Updating a ConfigMap but not restarting the pod — env vars are only injected at container start, so changes don't take effect until you restart.
- Using a ConfigMap for secrets — ConfigMaps are not encrypted and are visible to anyone with RBAC access; use Secrets instead.
- Mixing
envFromandenvand expectingenvto override —envtakes precedence, but you might accidentally shadow a value you didn't intend.
Variations
- Mount the ConfigMap as a volume (e.g., at
/etc/config) and read files from disk — useful for file-based config formats like.envorconfigparser. - Use Helm or Kustomize to generate ConfigMap values per environment (dev, staging, prod) from templates.
- Use
envwithconfigMapKeyReffor renaming keys or picking specific entries when you don't want to inject all keys.
Real-world use cases
- A Flask or FastAPI microservice that reads its database connection string and log level from a ConfigMap injected as env vars, so you can change them per environment without image rebuilds.
- A Python worker (like Celery or a Kafka consumer) that reads feature flags from a ConfigMap to toggle behaviors dynamically after a rollout restart.
- A Django app that needs a
DJANGO_SETTINGS_MODULEandALLOWED_HOSTSinjected from ConfigMaps, keeping the same Docker image across staging and production.
Key takeaways
- ConfigMaps store non-sensitive key-value configuration and can be injected into Python pods as environment variables.
- Use
envFromto inject all keys, orenvwithvalueFromfor selective, renamed variables. - Your Python code reads injected values via
os.getenv()— always provide a sensible default. - Env vars are set at container start; after updating a ConfigMap, restart the pod to apply changes.
- ConfigMaps are not secrets — never store passwords or API keys in them; use Secrets instead.
- Keep configuration out of your image to make it portable across environments and avoid rebuild cycles.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.