Most teams either use namespaces too little — everything in default — or too much — a namespace for every microservice. Here’s the right mental model and the patterns that actually work in production.
Kubernetes Zero to Hero · Bonus Episode ·
If you’ve been following this series, you’ve seen namespaces mentioned constantly — kube-system, production, default. We've used them in NetworkPolicy, RBAC, and Services. But we've never stopped to give them the full treatment they deserve.
That changes now.
Namespaces are deceptively simple on the surface. They look like folders. They feel like folders. But they’re really a multi-dimensional isolation mechanism that affects networking, access control, resource quotas, naming, and operational boundaries simultaneously. Understanding them properly changes how you design clusters.
What Is a Namespace?
A namespace is a virtual partition inside a single Kubernetes cluster. Resources inside a namespace are isolated from resources in other namespaces — they have separate names, separate RBAC policies, separate resource quotas, and separate NetworkPolicy scopes.
# List all namespaces in a cluster
$ kubectl get namespaces
NAME STATUS AGE
default Active 5d ← where resources land without a namespace specified
kube-system Active 5d ← Kubernetes system components (etcd, API server, CoreDNS...)
kube-public Active 5d ← publicly readable, rarely used
kube-node-lease Active 5d ← node heartbeat objects, internal use only
Every Kubernetes cluster starts with these four. You should almost never put your own workloads in kube-system or kube-public. The default namespace is where resources land when you don't specify one — which is fine for learning, but not for production.
What Namespaces Isolate — And What They Don’t
This is where most engineers get namespaces wrong. Namespaces are not a security boundary in the way a VM or a separate cluster is. Let’s be precise about what they do and don’t isolate.
What namespaces DO isolate:
Names — Two resources of the same type can share a name if they’re in different namespaces. You can have a postgres Deployment in both staging and production without conflict.
RBAC scope — Roles and RoleBindings are namespace-scoped. A developer with edit access in staging has no permissions in production.
Resource Quotas — You can limit how much CPU, memory, and object count each namespace can consume. The staging namespace can be capped at 4 CPU cores while production gets 32.
NetworkPolicy scope — NetworkPolicy rules apply within and across namespaces as you define. Without policies, pods across namespaces can communicate freely.
DNS names — Services are reachable by short name within their namespace (backend-service) and by full name from others (backend-service.production.svc.cluster.local).
What namespaces do NOT isolate:
Node access — All pods across all namespaces share the same underlying nodes. A privileged pod in namespace A can affect the host and therefore affect namespace B.
Cluster-scoped resources — Nodes, PersistentVolumes, StorageClasses, ClusterRoles, and IngressClasses exist outside any namespace. All namespaces share them.
Network traffic — Without NetworkPolicy, pods in namespace A can freely reach pods in namespace B. Namespaces alone provide zero network isolation.
Kernel resources — Processes across namespaces share the same kernel. Container escapes affect the whole node, not just the namespace.
💡 The key insight: Namespaces provide organisational isolation and access control boundaries. They do not provide security isolation equivalent to separate clusters. For true hard multi-tenancy — where tenants must be cryptographically isolated from each other — use separate clusters.
The Four Namespace Patterns
Over years of running Kubernetes in production, four namespace patterns emerge repeatedly. Pick the one that matches your team’s situation.
Pattern 1: Environment-Based (Most Common)
One namespace per environment. Simple, clean, easy to reason about.
cluster
├── development
├── staging
└── production
kubectl create namespace development
kubectl create namespace staging
kubectl create namespace production
Best for: Small to medium teams with a single application or tightly coupled microservices. The entire stack lives in one namespace per environment. Developers have full access to development, limited access to staging, and read-only (or no direct) access to production.
Trade-off: As you add microservices, the namespace becomes a mixing bowl. Resource quota enforcement is coarse-grained — you can’t easily limit how much the payments service consumes vs the notifications service.
Pattern 2: Team-Based
One namespace per team, per environment. Teams own their slice of the cluster.
cluster
├── payments-dev
├── payments-staging
├── payments-prod
├── notifications-dev
├── notifications-staging
└── notifications-prod
Best for: Platform teams managing infrastructure for multiple product teams. Each team has full autonomy within their namespaces, zero blast radius overlap with other teams.
Trade-off: Namespace count grows fast. Cross-team service discovery requires full DNS names. More RBAC objects to manage.
Pattern 3: Application-Based
One namespace per application or microservice, regardless of environment (environments are differentiated by cluster or labels).
cluster-production
├── frontend
├── api-gateway
├── payments-service
├── notifications-service
└── data-pipeline
Best for: Mature organisations using GitOps where each application’s manifests live in their own repository and namespace. Good for fine-grained resource quota enforcement per service.
Trade-off: Cross-service communication always requires full DNS names. Can feel over-engineered for smaller teams.
Pattern 4: Hybrid (Most Practical at Scale)
System and infrastructure concerns in dedicated namespaces. Product workloads grouped by team or domain.
cluster
├── kube-system (Kubernetes internals)
├── monitoring (Prometheus, Grafana, AlertManager)
├── ingress-nginx (Ingress Controller)
├── cert-manager (TLS automation)
├── payments-prod (Payments team, production)
├── payments-staging (Payments team, staging)
├── platform-prod (Platform team, production)
└── platform-staging (Platform team, staging)
Best for: Most production clusters. Infrastructure tooling is cleanly separated from application workloads. RBAC is easy to reason about. Cross-team isolation is enforced.
Creating and Configuring Namespaces
# namespace.yaml — a namespace with labels for policy targeting
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
environment: production # used by NetworkPolicy namespaceSelector
team: platform # used by RBAC and tooling
kubernetes.io/metadata.name: production # automatically set, useful for policies
annotations:
owner: platform-team@company.com
documentation: https://wiki.internal/k8s/production
kubectl apply -f namespace.yaml
# Or create it quickly
kubectl create namespace staging
# Set a default namespace for your kubectl session
kubectl config set-context --current --namespace=production
# Verify your current namespace context
kubectl config view --minify | grep namespace
💡 Set your default namespace context. Running kubectl get pods and getting results from the wrong namespace is a rite of passage in Kubernetes. Setting the context saves you from accidental cross-environment operations.
Resource Quotas: Preventing Noisy Neighbours
Without quotas, one namespace can consume all the CPU and memory in the cluster, starving every other namespace. ResourceQuota sets hard limits on what a namespace can consume.
# production-quota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: production-quota
namespace: production
spec:
hard:
# Compute resources
requests.cpu: "16" # total CPU requests across all pods
requests.memory: 32Gi # total memory requests
limits.cpu: "32" # total CPU limits
limits.memory: 64Gi # total memory limits
# Object count limits
pods: "100" # max pods in namespace
services: "20" # max Services
persistentvolumeclaims: "30" # max PVCs
secrets: "50" # max Secrets
configmaps: "50" # max ConfigMaps
# LoadBalancer limit (cloud cost control)
services.loadbalancers: "2"
services.nodeports: "5"
kubectl apply -f production-quota.yaml
# View current quota usage
kubectl describe resourcequota production-quota -n production
Name: production-quota
Namespace: production
Resource Used Hard
-------- ---- ----
limits.cpu 4 32
limits.memory 8Gi 64Gi
pods 12 100
requests.cpu 2 16
requests.memory 4Gi 32Gi
secrets 8 50
LimitRange: Default Requests and Limits
ResourceQuota enforces namespace totals. LimitRange enforces per-container minimums, maximums, and defaults. Without a LimitRange, pods with no resource requests bypass quota accounting.
# limit-range.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: production
spec:
limits:
- type: Container
default: # applied when container has no limits set
cpu: "500m"
memory: "256Mi"
defaultRequest: # applied when container has no requests set
cpu: "100m"
memory: "128Mi"
max: # hard ceiling per container
cpu: "4"
memory: "8Gi"
min: # hard floor per container
cpu: "50m"
memory: "64Mi"
- type: PersistentVolumeClaim
max:
storage: 50Gi # no single PVC can request more than this
min:
storage: 1Gi
kubectl apply -f limit-range.yaml
# Now any pod deployed without resource specs gets the defaults automatically
# Verify
kubectl describe limitrange default-limits -n production
⚠️ Always deploy a LimitRange alongside a ResourceQuota. Without a LimitRange, containers without resource requests don’t count against the quota — making the quota meaningless. The two objects work together: LimitRange sets per-container floors and defaults, ResourceQuota sets namespace-wide totals.
Namespace-Scoped DNS: How Services Find Each Other
Every Service gets a DNS entry that encodes its namespace:
<service-name>.<namespace>.svc.cluster.local
This has practical implications for cross-namespace communication:
# Within the same namespace — short name works
curl http://backend-service:8080
# Across namespaces — must use the full qualified name
curl http://backend-service.payments.svc.cluster.local:8080
# Or the shorter cross-namespace form (omits cluster.local)
curl http://backend-service.payments.svc:8080
# Cross-namespace Service reference in a ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: frontend
data:
PAYMENTS_API_URL: "http://payments-api.payments.svc.cluster.local:8080"
AUTH_SERVICE_URL: "http://auth-service.auth.svc.cluster.local:8081"
Namespace-Aware RBAC in Practice
One of the biggest practical benefits of namespaces is how they scope RBAC. A complete access control model becomes straightforward:
# developers can edit in staging, view in production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dev-edit-staging
namespace: staging
subjects:
- kind: Group
name: developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: edit
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dev-view-production
namespace: production
subjects:
- kind: Group
name: developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: view
apiGroup: rbac.authorization.k8s.io
With this model, a developer who accidentally runs kubectl delete deployment my-app while their context is set to staging causes no damage to production. The namespace boundary enforces the RBAC boundary, and the RBAC boundary enforces the permission boundary. Three layers of protection from a typo.
Namespace Labels: The Key to Policy Automation
Namespace labels are what make NetworkPolicy, Pod Security Admission, and tooling work at scale. Label your namespaces consistently and policies can target them programmatically.
# Label namespaces for NetworkPolicy targeting
kubectl label namespace production environment=production
kubectl label namespace staging environment=staging
kubectl label namespace monitoring purpose=monitoring
# Apply Pod Security profiles via label
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/warn=restricted
# Verify labels
kubectl get namespaces --show-labels
With labels in place, a NetworkPolicy can reference an entire environment rather than individual namespaces:
# Allow all production namespaces to receive monitoring scrapes
ingress:
- from:
- namespaceSelector:
matchLabels:
purpose: monitoring # targets the monitoring namespace by label
ports:
- protocol: TCP
port: 9090
Cleaning Up: Deleting a Namespace
Namespace deletion is cascade — it deletes every resource inside it. This is both a superpower and a footgun.
# Delete a namespace and EVERYTHING inside it
kubectl delete namespace staging
# This deletes: all pods, deployments, services, configmaps, secrets,
# PVCs, ingresses, network policies, roles, rolebindings...
# in that namespace simultaneously.
# Before deleting, audit what's inside
kubectl get all -n staging
kubectl get pvc -n staging # PVCs may have important data
kubectl get secrets -n staging # any secrets worth backing up?
# Safe pattern: drain workloads first, then delete
kubectl scale deployment --all --replicas=0 -n staging
kubectl delete namespace staging
⚠️ PVCs in a deleted namespace follow their StorageClass reclaimPolicy. If the StorageClass uses Delete (common on cloud providers), deleting the namespace deletes the PVCs which deletes the underlying storage. If that storage had data you needed — it's gone. Always check PVC reclaim policies before deleting namespaces containing stateful workloads.
The Namespace Anti-Patterns to Avoid
Anti-pattern 1: Everything in default
The default namespace has no ResourceQuota, no LimitRange, no NetworkPolicy, and broad RBAC defaults. Running production workloads in default means no guardrails, no isolation, and no clear ownership. Create named namespaces with proper configuration.
Anti-pattern 2: A namespace per microservice (in most cases)
For most teams, one namespace per microservice creates more complexity than value. You end up managing hundreds of RBAC objects, cross-namespace DNS in every ConfigMap, and NetworkPolicies with dozens of namespaceSelector rules. Unless you have a genuine need for per-service isolation (compliance requirements, different security profiles), team or environment-based namespaces are almost always the right call.
Anti-pattern 3: Treating namespaces as a hard security boundary
Namespaces are not VMs. They do not provide kernel-level isolation. A privileged container can escape to the host regardless of namespace. If you need hard multi-tenancy, use separate clusters — or at minimum combine namespaces with strict Pod Security Admission, NetworkPolicy, and no privileged containers.
Anti-pattern 4: Unlabelled namespaces
Namespaces without consistent labels can’t be targeted by NetworkPolicy namespaceSelector, can't be selected by Pod Security Admission profiles, and are invisible to many observability tools. Label every namespace at creation — it's much harder to add labels retroactively when policies are already in place.
Hands-On Lab
# 1. Create three namespaces with labels
for env in development staging production; do
kubectl create namespace $env
kubectl label namespace $env environment=$env
done
# 2. Apply a ResourceQuota to staging
kubectl apply -f - <<EOF
apiVersion: v1
kind: ResourceQuota
metadata:
name: staging-quota
namespace: staging
spec:
hard:
pods: "10"
requests.cpu: "2"
requests.memory: 4Gi
limits.cpu: "4"
limits.memory: 8Gi
EOF
# 3. Apply a LimitRange to staging
kubectl apply -f - <<EOF
apiVersion: v1
kind: LimitRange
metadata:
name: staging-limits
namespace: staging
spec:
limits:
- type: Container
default:
cpu: 200m
memory: 128Mi
defaultRequest:
cpu: 100m
memory: 64Mi
EOF
# 4. Deploy something to staging
kubectl create deployment nginx --image=nginx:1.25 \
--replicas=3 -n staging
# 5. Check quota consumption
kubectl describe resourcequota staging-quota -n staging
# 6. Try to exceed the quota — deploy 10 more pods
kubectl scale deployment nginx --replicas=12 -n staging
# ReplicaSet will fail to create pods beyond the quota
kubectl get events -n staging | grep quota
# 7. Switch contexts between namespaces and feel the difference
kubectl config set-context --current --namespace=staging
kubectl get pods # staging pods
kubectl config set-context --current --namespace=production
kubectl get pods # production pods (none yet)
# 8. Verify DNS namespacing
kubectl run dnstest --image=busybox -n staging -- sleep 3600
kubectl exec -n staging dnstest -- nslookup nginx.staging.svc.cluster.local
kubectl exec -n staging dnstest -- nslookup nginx.production.svc.cluster.local
# Second one fails — no nginx service in production
# 9. Clean up
kubectl delete namespace development staging production
Step 6 is the one to pay attention to — watching quota enforcement reject pod creation in real time makes the ResourceQuota object click in a way that reading YAML never does.
Namespace Quick Reference
# Create
kubectl create namespace <name>
kubectl apply -f namespace.yaml
# List
kubectl get namespaces
kubectl get ns # short alias
# Set default context
kubectl config set-context --current --namespace=<name>
# View current namespace
kubectl config view --minify | grep namespace
# Get all resources in a namespace
kubectl get all -n <name>
# Get resources across ALL namespaces
kubectl get pods --all-namespaces
kubectl get pods -A # short form
# Delete (cascade — deletes everything inside)
kubectl delete namespace <name>
# ResourceQuota
kubectl describe resourcequota -n <name>
# LimitRange
kubectl describe limitrange -n <name>
# Labels
kubectl label namespace <name> key=value
kubectl get namespaces --show-labels
What’s Next
We return to the main series with Episode 09: Monitoring with Prometheus and Grafana — building the observability stack that gives you eyes inside your cluster. Pod health, node resource usage, application metrics, and alerting — all covered.
Episode Topic
EP 01 Introduction to Kubernetes
EP 02 Kubernetes Architecture & Components
EP 03 Pods, Deployments & ReplicaSets
EP 04 Services & Networking
EP 05 ConfigMaps, Secrets & Environment Config
EP 06 Storage: PersistentVolumes & StatefulSets
Bonus Setting Up a Cluster with kubeadm on EC2
Bonus Network Policy Deep Dive
EP 07 Ingress Controllers & Load Balancing
EP 08 RBAC & Security Fundamentals
Bonus ← You are here Namespaces Deep Dive
EP 09 Monitoring with Prometheus & Grafana
EP 10 Production Hardening & Helm Charts
Before the Next Article
Three questions to test your understanding:
- A team asks you whether they should use one namespace per microservice or one namespace per environment. What questions do you ask before recommending either?
- You apply a ResourceQuota to a namespace but pods with no resource requests are still being scheduled. What did you forget?
- A developer’s kubectl exec works fine in staging but gets Forbidden in production. No RBAC changes were made today. What's the most likely explanation?
Namespaces done wrong are invisible problems — everything works until a rogue workload eats all your cluster resources, a developer accidentally deletes the wrong environment, or an auditor asks how you enforce team isolation. Namespaces done right make those problems structurally impossible. See you in Episode 09.