Kubernetes Namespaces, ConfigMaps & Secrets Explained: Configuration and Security
Assume you have deployed Pods and Services on Kubernetes but now your cluster is getting bigger.
Different teams are working on different apps.
Passwords and API keys are sitting dangerously inside your code.
Three powerful Kubernetes tools:
Namespaces (organise your cluster into isolated zones),
ConfigMaps (store app settings cleanly outside your code),
and Secrets (protect sensitive data like passwords and API keys). Let's go! ☸️
Think of your Kubernetes cluster as a huge school building 🏫.
Without proper organisation, it becomes chaos — students from different classes mixing up,
homework getting lost, teachers unable to find anything.
These three tools fix all of that:
- Namespaces → Like separate classrooms. Team A cannot see Team B's stuff.
- ConfigMaps → Like a settings notice board. Stores app config outside your code.
- Secrets → Like a locked safe. Stores passwords and API keys securely.
💡 Quick rule of thumb: Non-sensitive settings go in ConfigMaps. Sensitive data goes in Secrets. Everything lives inside a Namespace.
Here is a quick comparison before we dive deep:
- Namespaces → Logical boundary · Isolates all resources · Cluster-wide scope
- ConfigMaps → Plain text key-value pairs · Not sensitive · Lives inside a namespace
- Secrets → Base64 encoded · Sensitive data only · Lives inside a namespace
Part 1 — Namespaces (Your Cluster's Room Dividers)
Imagine a giant open office 🏢 where 500 engineers all work in one massive room.
Total chaos — nobody knows which laptop belongs to whom!
Now add partition walls to create separate rooms:
Room A for the ML team, Room B for the backend team, Room C for testing.
That is exactly what Namespaces do in Kubernetes.
A Namespace is a virtual partition inside a single Kubernetes cluster.
Resources inside one namespace are completely invisible to other namespaces by default.
One physical cluster can host dozens of completely separate environments — all at the same time!
💡 Think of it like this: Your dev namespace has a test version of your AI model. Your production namespace has the real model serving millions of users. A mistake in dev cannot accidentally affect production — they are in separate rooms!
The 4 Built-in Namespaces
Kubernetes automatically creates these four namespaces when you install it:
- default → Where your resources go when you don't specify a namespace. Fine for learning, avoid in production.
- kube-system → Kubernetes' own internal components live here — DNS, scheduler, API server. Never touch this!
- kube-public → Publicly readable data. Rarely used by regular developers.
- kube-node-lease → Heartbeat tracking for Nodes. Kubernetes manages this automatically.
How Namespaces Work — Step by Step
- Decide what logical zones you need — dev, staging, prod, or team names.
- Create each namespace using a YAML file or a single kubectl command.
- Deploy all resources into their correct namespace using
-n <namespace>. - Set ResourceQuotas so one namespace can't hog all the cluster's memory and CPU.
- Optionally set a default namespace so you don't have to type
-nevery time.
Quick Command — Create a Namespace
# Fastest way — one line
kubectl create namespace ml-team
# Or create multiple at once
kubectl create namespace dev
kubectl create namespace staging
kubectl create namespace production
# View all namespaces
kubectl get namespaces
Expected output:
NAME STATUS AGE
default Active 5d
dev Active 2m
kube-node-lease Active 5d
kube-public Active 5d
kube-system Active 5d
ml-team Active 1m
production Active 2m
staging Active 2m
All your namespaces are listed with their status. Notice kube-system is already there — never delete that! 🎯
YAML Example — Namespace with ResourceQuota
# namespace-ml-team.yaml
apiVersion: v1
kind: Namespace
metadata:
name: ml-team
labels:
environment: development
team: machine-learning
annotations:
description: "ML team workspace for model training"
owner: "mlteam@company.com"
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: ml-team-quota
namespace: ml-team
spec:
hard:
requests.cpu: "20" # max 20 CPU cores
requests.memory: "40Gi" # max 40 GB RAM
count/pods: "50" # max 50 pods
count/secrets: "20" # max 20 secrets
kubectl Commands — Namespaces
# Deploy to a specific namespace
kubectl apply -f deployment.yaml -n ml-team
# List pods in a specific namespace
kubectl get pods -n ml-team
# List ALL pods across ALL namespaces
kubectl get pods --all-namespaces
# Set a default namespace so you don't type -n every time
kubectl config set-context --current --namespace=ml-team
# Check resource quota usage
kubectl describe resourcequota ml-team-quota -n ml-team
# Delete a namespace — WARNING: deletes EVERYTHING inside it!
kubectl delete namespace ml-team
✅ DO: Use one namespace per team per environment — for example team-ml-dev and team-ml-prod. Always add ResourceQuotas to prevent one team from using all cluster resources. Add labels and annotations to every namespace so you know who owns it and how to contact them.
❌ DON'T: Deploy everything to default — it becomes impossible to manage at scale. Never touch kube-system — you can break your entire cluster! Don't delete a namespace without checking what's inside — it permanently deletes all resources in it.
⚠️ Latest Trend — User Namespaces: Kubernetes 1.33 made User Namespaces stable and on-by-default. This maps container users to unprivileged host users. Even if a Pod is compromised, it cannot escape to the host machine. Enable this on all production Pods by adding hostUsers: false to your Pod spec.
Part 2 — ConfigMaps (Your App's Settings Notice Board)
Imagine you run a cafe ☕ and you write your menu on a notice board outside.
When you change the price of a coffee, you just update the board — you don't rebuild the whole cafe!
ConfigMaps work exactly like that notice board for your Kubernetes apps.
A ConfigMap stores non-sensitive configuration data as key-value pairs.
Your app reads this data at runtime — without being rebuilt or redeployed.
Change the ConfigMap and your app picks up the new settings automatically!
💡 Think of it like this: Your model version, database host, log level, and port numbers all go in a ConfigMap. Your app reads them as environment variables or as files — no more hardcoded values buried inside your Docker image!
How ConfigMaps Work — Step by Step
- Identify all the settings your app needs that are not sensitive — database host, port, log level, feature flags.
- Create a ConfigMap YAML file with those key-value pairs.
- Deploy the ConfigMap to the correct namespace.
- Reference the ConfigMap in your Pod — either as environment variables or as a mounted file.
- Your Pod starts and reads the settings automatically at runtime. ✅
YAML Example — ConfigMap (Weather AI App Settings)
# weather-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: weather-app-config
namespace: ml-team
data:
# Simple key-value settings
APP_PORT: "8080"
LOG_LEVEL: "info"
DATABASE_HOST: "postgres.ml-team.svc.cluster.local"
DATABASE_PORT: "5432"
MODEL_VERSION: "v3.1"
PREDICTION_BATCH_SIZE: "32"
# A whole config file stored as a value
app.properties: |
server.port=8080
model.cache.enabled=true
max.prediction.requests=1000
Using a ConfigMap — Method 1: Environment Variables
Inject all keys from a ConfigMap as environment variables at once using envFrom:
spec:
containers:
- name: weather-model
image: weather-ai:v3.1
envFrom:
- configMapRef:
name: weather-app-config # every key becomes an env var
With envFrom, every key from the ConfigMap becomes an environment variable in your container. Your Python app then reads them with os.environ['LOG_LEVEL']. Simple and clean! ✅
Using a ConfigMap — Method 2: Mount as a File
Mount a config file from a ConfigMap directly into the container's filesystem:
spec:
containers:
- name: weather-model
image: weather-ai:v3.1
volumeMounts:
- name: config-volume
mountPath: "/app/config"
volumes:
- name: config-volume
configMap:
name: weather-app-config
With a volume mount, the app.properties entry from your ConfigMap appears as a real file at /app/config/app.properties inside the container. Your app reads it just like a local file! ✅
When to use which method?
- Use
envFrom→ For simple key-value settings like port numbers and log levels - Use volume mount → For full config files like
nginx.conformodel_config.json
kubectl Commands — ConfigMaps
# Apply your ConfigMap
kubectl apply -f weather-configmap.yaml
# Create quickly from the command line
kubectl create configmap my-config \
--from-literal=LOG_LEVEL=info \
--from-literal=PORT=8080
# Create ConfigMap from an existing file
kubectl create configmap my-config --from-file=app.properties
# View all ConfigMaps in a namespace
kubectl get configmaps -n ml-team
# See the actual content of a ConfigMap
kubectl describe configmap weather-app-config -n ml-team
# Edit a ConfigMap live — change takes effect immediately
kubectl edit configmap weather-app-config -n ml-team
# Delete a ConfigMap
kubectl delete configmap weather-app-config -n ml-team
✅ DO: Store all non-sensitive settings in ConfigMaps — database host, port, model version, log level, feature flags. Use separate ConfigMaps per environment — different values for dev, staging, and production. Add immutable: true to ConfigMaps that must never change after deployment — this also improves cluster performance.
❌ DON'T: Never store passwords, API keys, or tokens in a ConfigMap — use Secrets for those! Don't create one giant ConfigMap for everything — split logically into app settings, model settings, and database settings. Don't store more than 1 MB of data in a single ConfigMap — it can slow down the API server.
⚠️ Latest Trend — Immutable ConfigMaps: Adding immutable: true to a ConfigMap locks it permanently after creation. Kubernetes stops watching it for changes, which improves performance significantly at scale. Use this for any settings that are fixed per deployment — like model version or batch size.
Part 3 — Secrets (Your Cluster's Locked Safe)
Imagine you run a bank 🏦 and you keep customer PIN numbers on a public notice board.
That's a disaster! PINs go into a locked safe — only authorised staff can access it.
Kubernetes Secrets are that locked safe for your cluster.
A Secret stores sensitive data like passwords, API keys, database credentials, and tokens.
The data is stored as Base64 encoded strings — not plain text.
Only Pods and users that are specifically authorised can read a Secret.
⚠️ Critical Security Note — Read This First!
By default, Kubernetes Secrets are stored unencrypted in etcd (the cluster database).
Base64 is encoding, not encryption — anyone with cluster access can decode it instantly., always enable Encryption at Rest for etcd and use external secret managers for production.
Understanding Base64 Encoding
Before creating a Secret, you need to understand Base64 encoding.
It converts plain text into a scrambled-looking string — but it is not encryption.
It is just a way to represent binary data as text. Anyone can decode it instantly.
# Encode your password to Base64
echo -n "my-super-password" | base64
# Output: bXktc3VwZXItcGFzc3dvcmQ=
# Decode it back — anyone can do this!
echo "bXktc3VwZXItcGFzc3dvcmQ=" | base64 --decode
# Output: my-super-password
See? Base64 is not security. It is just a format. Real security comes from controlling who can access the Secret and encrypting etcd at rest.
How Secrets Work — Step by Step
- Identify all sensitive data your app needs — passwords, API keys, tokens.
- Use
stringDatain your YAML to write plain text — Kubernetes auto-encodes to Base64 for you. - Create a Secret YAML file — or use
kubectl create secretdirectly from the command line. - Deploy the Secret to the correct namespace.
- Reference the Secret in your Pod — either as environment variables or a mounted file.
- In production: enable etcd encryption and use an external secrets manager like Vault or AWS Secrets Manager.
YAML Example — Secret using data (Base64 values)
# weather-secrets.yaml — all values must be base64 encoded
apiVersion: v1
kind: Secret
metadata:
name: weather-app-secrets
namespace: ml-team
type: Opaque
data:
DATABASE_PASSWORD: c3VwZXItc2VjcmV0LXBhc3M= # super-secret-pass
OPENAI_API_KEY: c2stMTIzNDU2Nzg5MGFiY2RlZg== # sk-1234567890abcdef
JWT_SECRET: bXktand0LXNlY3JldC1rZXk= # my-jwt-secret-key
YAML Example — Secret using stringData (No Base64 Needed! ✨)
# weather-secrets-easy.yaml — write plain text, K8s auto-encodes it
apiVersion: v1
kind: Secret
metadata:
name: weather-app-secrets
namespace: ml-team
type: Opaque
stringData:
DATABASE_PASSWORD: "super-secret-pass"
OPENAI_API_KEY: "sk-1234567890abcdef"
JWT_SECRET: "my-jwt-secret-key"
💡 Always use stringData when writing YAML by hand! It skips the manual Base64 step entirely. Kubernetes handles the encoding automatically when saving to etcd. Much less error-prone and much easier to read.
Using a Secret — Method 1: Environment Variables
spec:
containers:
- name: weather-model
image: weather-ai:v3.1
env:
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: weather-app-secrets
key: DATABASE_PASSWORD
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: weather-app-secrets
key: OPENAI_API_KEY
Using a Secret — Method 2: Mount as a File (Most Secure!)
spec:
containers:
- name: weather-model
image: weather-ai:v3.1
volumeMounts:
- name: secret-volume
mountPath: "/app/secrets"
readOnly: true # always read-only for secrets!
volumes:
- name: secret-volume
secret:
secretName: weather-app-secrets
With this approach, each Secret key becomes a separate file inside /app/secrets/.
The file /app/secrets/DATABASE_PASSWORD contains the actual decoded password.
Volume-mounted secrets are automatically updated when the Secret changes! ✅
When to use which method?
- Use environment variables → Quick and works for most apps
- Use volume mount → More secure, values don't appear in process listings or crash dumps
kubectl Commands — Secrets
# Create a secret directly from the command line
kubectl create secret generic db-creds \
--from-literal=password=mypassword \
-n ml-team
# Apply from YAML file
kubectl apply -f weather-secrets.yaml
# List secrets — shows names only, never actual values
kubectl get secrets -n ml-team
# Decode and view a secret value (for debugging only!)
kubectl get secret weather-app-secrets -n ml-team \
-o jsonpath="{.data.DATABASE_PASSWORD}" | base64 --decode
# Delete a secret
kubectl delete secret weather-app-secrets -n ml-team
✅ DO: Enable Encryption at Rest in etcd — this is the number one security step for Kubernetes Secrets. Use RBAC to give Pods access only to the specific Secrets they need. Mount Secrets as read-only files rather than env vars wherever possible. Set immutable: true on Secrets that never change.
❌ DON'T: Never commit Secret YAML files to Git — even Base64 encoded values are trivially decodable. Don't use Secrets for non-sensitive data — that is what ConfigMaps are for. Don't log Secret values in your app code — they will appear in log storage forever. Never assume Base64 equals encryption — it absolutely does not!
⚠️ Gold Standard — External Secrets Operator: The industry standard is to not store secrets inside Kubernetes at all. Store them in HashiCorp Vault, AWS Secrets Manager, or GCP Secret Manager. Then use the External Secrets Operator (ESO) to automatically sync them into your cluster on a schedule. Your Git repo never contains any sensitive data and credentials rotate automatically.
YAML Example — External Secrets Operator (Advanced Production Pattern)
# external-secret.yaml — syncs from AWS Secrets Manager every 15 minutes
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: weather-external-secret
namespace: production
spec:
refreshInterval: 15m # auto-sync every 15 minutes
secretStoreRef:
kind: ClusterSecretStore
name: aws-secrets-manager
target:
name: weather-app-secrets # creates this K8s Secret automatically
data:
- secretKey: DATABASE_PASSWORD
remoteRef:
key: weather-app/db-password # path in AWS Secrets Manager
With ESO, your real passwords never touch your cluster directly. They live in AWS or Vault and ESO automatically creates and rotates the Kubernetes Secret on a schedule. This is production-grade secrets management ! 🏆
All Three Working Together — Real MLOps Example
Let's see how Namespaces, ConfigMaps, and Secrets all work together in a real ML weather prediction platform 🌦️.
Here is the full picture:
- Namespace: ml-team → Contains all ML team resources during development
- Namespace: production → Completely isolated — different resources, stricter rules
- ConfigMap in ml-team →
DB_HOST: postgres-dev,LOG_LEVEL: debug,MODEL_VERSION: v3.1 - ConfigMap in production →
DB_HOST: postgres-prod,LOG_LEVEL: warn,MODEL_VERSION: v3.0 - Secret in ml-team → Fake test passwords stored directly in the cluster
- Secret in production → Real passwords synced from AWS Secrets Manager every 15 minutes via ESO
The same ConfigMap and Secret names exist in both namespaces — but with completely different values. A Pod in ml-team cannot read a Secret from production. This is how real companies run Kubernetes.
Here is how a single Pod reads both its ConfigMap and Secret together:
# weather-pod.yaml — reads ConfigMap as env vars, Secret as files
apiVersion: v1
kind: Pod
metadata:
name: weather-model
namespace: ml-team
spec:
containers:
- name: weather-model
image: weather-ai:v3.1
envFrom:
- configMapRef:
name: weather-app-config # all settings become env vars
volumeMounts:
- name: secret-volume
mountPath: "/app/secrets"
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: weather-app-secrets # passwords appear as files
Clean, readable, and production-ready. Settings in ConfigMap, passwords in Secret, everything scoped to the ml-team namespace. 🎉
When Should I Use Which? — Decision Guide
Ask yourself these questions to pick the right tool every time:
- Need to separate teams or environments? → Use Namespaces. Always. ✅
- Need to store a database host, port, or log level? → Use a ConfigMap. ✅
- Need to store a password, token, or API key? → Use a Secret. ✅
- Running production on cloud with auto-rotating credentials? → Use External Secrets Operator + Vault or AWS Secrets Manager. ✅
- Still not sure? → Ask: is this sensitive? Yes → Secret. No → ConfigMap. ✅
Practice Challenges — From Beginner to Hero 🏆
You have learned all the theory — now get your hands dirty! 💪
🟢 Beginner Challenge — Namespaces
# Step 1: Start Minikube
minikube start
# Step 2: Create three namespaces
kubectl create namespace dev
kubectl create namespace staging
kubectl create namespace production
# Step 3: Deploy nginx to dev namespace only
kubectl run nginx --image=nginx -n dev
# Step 4: Confirm it only appears in dev
kubectl get pods --all-namespaces
# Step 5: Set dev as your default namespace
kubectl config set-context --current --namespace=dev
Confirm that nginx appears only under the dev row. Nothing in staging or production! 🎉
🟡 Intermediate Challenge — ConfigMaps
# Step 1: Create a ConfigMap
kubectl create configmap app-config \
--from-literal=APP_COLOR=blue \
--from-literal=LOG_LEVEL=debug \
--from-literal=MAX_USERS=100 \
-n dev
# Step 2: Describe it to see the values
kubectl describe configmap app-config -n dev
# Step 3: Run a Pod that reads the ConfigMap as env vars
kubectl run test-pod \
--image=busybox \
--restart=Never \
-n dev \
--env="APP_COLOR=$(kubectl get configmap app-config -n dev -o jsonpath='{.data.APP_COLOR}')" \
-- sleep 3600
# Step 4: Exec in and confirm the env var
kubectl exec -it test-pod -n dev -- env | grep APP_COLOR
See APP_COLOR=blue in the output? Your Pod is reading from the ConfigMap! ✅
🔵 Advanced Challenge — Secrets
# Step 1: Create a Secret using stringData
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: test-secret
namespace: dev
type: Opaque
stringData:
DATABASE_PASSWORD: "my-fake-password-123"
API_KEY: "fake-api-key-xyz"
EOF
# Step 2: List secrets — values are hidden
kubectl get secrets -n dev
# Step 3: Decode the password directly
kubectl get secret test-secret -n dev \
-o jsonpath="{.data.DATABASE_PASSWORD}" | base64 --decode
# Step 4: Mount the secret as a file and read it from inside a Pod
# (see the complete Pod YAML in the "All Three Working Together" section above)
# Then exec in:
kubectl exec -it weather-model -n dev -- cat /app/secrets/DATABASE_PASSWORD
See your password decoded correctly? And then appearing as a file inside the Pod? That is production-style secrets handling! 🔒
The Ultimate Cheat Sheet 📋
All essential commands in one place:
# ── NAMESPACES ─────────────────────────────────────────
kubectl create namespace <name>
kubectl get namespaces
kubectl get pods -n <namespace>
kubectl get pods --all-namespaces
kubectl config set-context --current --namespace=<ns>
kubectl delete namespace <name> # ⚠️ deletes everything!
kubectl describe resourcequota -n <ns>
# ── CONFIGMAPS ──────────────────────────────────────────
kubectl create configmap <name> --from-literal=KEY=VALUE
kubectl create configmap <name> --from-file=config.yaml
kubectl get configmaps -n <namespace>
kubectl describe configmap <name>
kubectl edit configmap <name> # live edit
kubectl delete configmap <name>
# ── SECRETS ─────────────────────────────────────────────
kubectl create secret generic <name> --from-literal=key=value
kubectl create secret generic <name> --from-file=ssh-key=~/.ssh/id_rsa
kubectl get secrets -n <namespace>
kubectl describe secret <name>
kubectl get secret <name> \
-o jsonpath="{.data.KEY}" | base64 --decode
kubectl delete secret <name>
# ── BASE64 ENCODE / DECODE ──────────────────────────────
echo -n "my-password" | base64 # encode
echo "bXktcGFzc3dvcmQ=" | base64 --decode # decode
Quick Summary 📝
What we learned:
- Why we need them → Clusters grow fast. Without organisation, config management becomes chaos.
- Namespaces → Virtual partitions in your cluster. One cluster, many isolated environments. Always use them in production.
- ConfigMaps → Store non-sensitive settings outside your code. Decouple config from Docker images. Change settings without a rebuild.
- Secrets → Store passwords and API keys. Base64 is not encryption. Enable etcd encryption and use external secret managers in production.
- They work together → Namespaces isolate both ConfigMaps and Secrets. A Pod can only read what's in its own namespace.
- Best practice → Use External Secrets Operator + Vault or AWS Secrets Manager to keep all production credentials completely outside the cluster.
Keep practising with your own cluster! Namespaces, ConfigMaps, and Secrets become second nature once you use them on a real project. Happy learning! ☸️✨
Comments
Post a Comment