Namespaces: The Boundary Everything Else Is Scoped To🔗
Part of Essentials: Core Primitives
This article is part of Essentials: Core Primitives. It assumes you're comfortable with Pods, Services, and ConfigMaps and Secrets.
Almost everything you've created so far — Pods, Services, ConfigMaps, Secrets — lives inside a namespace. It's the partition that lets dozens of teams share one cluster without their object names colliding, lets one cluster hold dev and staging side by side, and gives the platform team a unit to attach quotas and access rules to.
The trap is assuming a namespace is more of a wall than it is. It cleanly separates names and API objects. It does not, by default, separate network traffic or grant any security isolation. Knowing exactly where that boundary stops — and what you bolt on to make it real — is what separates someone who uses namespaces from someone who can be trusted to carve up a cluster.
What You'll Learn
By the end of this article, you'll understand:
- What a namespace actually scopes — and the handful of things it doesn't
- Why namespaces exist — name collisions, multi-tenancy, environment separation
- Creating and targeting namespaces declaratively and from the CLI
requestsvs.limits— the floor-and-ceiling model people get backwards- ResourceQuotas and LimitRanges — keeping one tenant from starving the others
- Cross-namespace DNS — reaching a Service in another namespace
- The soft-boundary reality — why RBAC and Network Policies are the real walls
- Deletion blast radius — what a namespace delete cascades to, and why there's no undo
graph TD
Cluster["Cluster"]
NS1["Namespace: team-a"]
NS2["Namespace: team-b"]
KS["Namespace: kube-system<br/>(cluster system — hands off)"]
A1["Pods / Services / Secrets"]
B1["Pods / Services / Secrets"]
Cluster --> NS1 --> A1
Cluster --> NS2 --> B1
Cluster --> KS
style Cluster fill:#1a202c,stroke:#cbd5e0,stroke-width:2px,color:#fff
style NS1 fill:#2d3748,stroke:#cbd5e0,stroke-width:2px,color:#fff
style NS2 fill:#2d3748,stroke:#cbd5e0,stroke-width:2px,color:#fff
style KS fill:#c53030,stroke:#cbd5e0,stroke-width:2px,color:#fff
style A1 fill:#4a5568,stroke:#cbd5e0,stroke-width:2px,color:#fff
style B1 fill:#4a5568,stroke:#cbd5e0,stroke-width:2px,color:#fff
What a Namespace Actually Scopes🔗
A namespace is a scope for the names of namespaced API objects. Two teams can both have a Deployment named web-app as long as they're in different namespaces — the fully qualified identity is namespace/name, not just name.
-
Inside a namespace
Scoped by name — two namespaces can each have their own.
- Pods, Services, Deployments, ReplicaSets, StatefulSets, Jobs
- ConfigMaps, Secrets, ServiceAccounts
- PersistentVolumeClaims, Ingresses, NetworkPolicies, ResourceQuotas, LimitRanges
-
Cluster-scoped
Live outside any namespace — one global instance, shared by everyone.
- Nodes, PersistentVolumes, StorageClasses
- Namespaces themselves
- ClusterRoles & ClusterRoleBindings, CustomResourceDefinitions
A quick way to see which is which:
kubectl api-resources --namespaced=true # (1)!
kubectl api-resources --namespaced=false # (2)!
- Everything scoped to a namespace.
- Cluster-scoped resources.
That api-resources split is worth internalizing — it tells you, for any object type, whether a -n flag even applies.
Why Namespaces Exist🔗
Name collisions🔗
Without namespaces, object names would have to be globally unique across the cluster — unworkable the moment more than one team shares it. Namespaces make team-a/web-app and team-b/web-app distinct objects that never interfere.
Multi-tenancy on a shared cluster🔗
This is the real reason they matter operationally. One cluster, many tenants — teams, environments, or both. Each tenant gets a namespace, and the platform team hangs the controls off it: access rules, resource quotas, and network policy all attach to the namespace. That's why "give team-c their own namespace" is shorthand for an entire onboarding workflow.
Environment separation🔗
dev, staging, and prod as namespaces in one cluster is common for smaller setups — same manifests, different namespace. Be aware of the tradeoff: it's cheaper than separate clusters, but a namespace is a soft boundary (see below), so true prod isolation usually means a separate cluster, not just a separate namespace.
The Default Namespaces🔗
Every cluster ships with these:
| Namespace | Purpose |
|---|---|
default |
Where objects land if you don't specify one — avoid using it for real work |
kube-system |
The cluster's own system and add-on components (CoreDNS, kube-proxy, CNI) |
kube-public |
World-readable; rarely used directly |
kube-node-lease |
Node heartbeat objects; ignore |
kube-system is the cluster's life support
kube-system runs the components that make the cluster a cluster. Deleting or disrupting objects here can take down DNS, networking, or the cluster itself for everyone — the blast radius is the whole cluster, not your tenant. Read it to understand your cluster; don't modify it unless you own cluster operations and know exactly what you're doing.
Creating Namespaces🔗
Declaratively (the default)🔗
A namespace is a versioned object like anything else, and putting it in Git lets you attach labels that drive policy (cost allocation, NetworkPolicy targeting, environment tagging):
| namespace.yaml | |
|---|---|
- Labels on the namespace are how NetworkPolicies and tooling target it — not decoration.
- Kubernetes injects this immutable label on every namespace automatically; you can select on it.
The imperative shortcut
kubectl create namespace team-a-dev is fine for a throwaway namespace on a dev cluster. For anything that policy or other teams depend on, use the manifest — the labels are the whole point.
Targeting the Right Namespace🔗
Every command runs against some namespace. Getting this wrong is the classic "my resources vanished" confusion — they're fine, you're just looking in the wrong place.
kubectl get pods -n team-a-dev # (1)!
kubectl get pods -A # (2)!
kubectl config view --minify | grep namespace: # (3)!
- A specific namespace.
- Every namespace at once.
- Which namespace am I defaulting to right now?
Typing -n team-a-dev on every command gets old fast. Pin it to your current context once:
kubectl config set-context --current --namespace=team-a-dev # (1)!
- Now bare commands target
team-a-dev.
kubens
On a multi-namespace cluster, kubens (from the kubectx project) switches your default namespace interactively — much faster than the set-context incantation. Worth installing.
Prefer not hardcoding namespace: in manifests — leaving it out keeps them portable, with the namespace supplied at apply time (-n, your context, or Kustomize). Hardcode it only when an object must always live in one place.
Requests vs. Limits: Floor and Ceiling🔗
Both the quota and LimitRange below hinge on two words — the two most-confused settings in Kubernetes. requests is the floor: the absolute minimum your container needs to start, which the scheduler reserves. limits is the ceiling: the most you ever expect it to grow to. Get that straight and most "why won't this schedule?" and "why did it get killed?" mysteries disappear:
| What it is | Set it to | Get it wrong → | |
|---|---|---|---|
requests |
Minimum reserved to start (the floor) | The smallest your app needs to run | Too high → won't schedule (wasted capacity); too low → starved or evicted under pressure |
limits |
Maximum it may use (the ceiling) | The biggest you realistically expect it to reach | Too low → throttled or OOMKilled; too high → one Pod can hog the whole node |
The model to unlearn
The wrong mental model — "requests is how much I want, limits is how much I really want" — is everywhere, and it quietly wastes clusters. Say it the right way until it sticks: requests is the minimum to start; limits is the maximum before Kubernetes steps in. Padding requests "to be safe" reserves capacity nobody uses and makes the scheduler treat a half-empty node as full.
Resource Quotas: Stopping One Tenant from Eating the Cluster🔗
A namespace with no quota can schedule Pods until the cluster runs out of capacity — and on a shared cluster that's everyone's problem. A ResourceQuota puts a hard ceiling on aggregate consumption in the namespace:
| resource-quota.yaml | |
|---|---|
- Sum of all container CPU requests in the namespace can't exceed 10 cores.
- Object-count ceilings matter too — runaway controllers create Pods, not just consume CPU.
- Shows
UsedvsHardfor each resource.
A quota changes how Pods must be specified
Once a namespace has a requests/limits quota, every Pod created in it must declare the corresponding requests and limits, or the API server rejects it. This is a frequent "why won't my Pod schedule?" surprise. A LimitRange (next) supplies sensible defaults so developers don't have to annotate every Pod by hand.
LimitRanges: Defaults and Guardrails per Container🔗
Where a ResourceQuota caps the namespace total, a LimitRange sets per-container defaults — so Pods that don't declare resources still satisfy the quota:
| limit-range.yaml | |
|---|---|
- Applied as the container's limit if it doesn't set one.
- Applied as the container's request if it doesn't set one — this is what auto-satisfies a quota.
A LimitRange can also set max/min bounds that reject any container asking for too much or too little. Together, a ResourceQuota plus a LimitRange are the standard "fair-share" setup a platform team drops on every tenant namespace.
Cross-Namespace Communication🔗
A Service is reachable by short name within its own namespace. From another namespace, you need the fully qualified DNS name:
<service>.<namespace>.svc.cluster.local
This is exactly the cluster-DNS machinery from the Services article, with the namespace segment made explicit. CoreDNS resolves it; there's no special permission needed — which leads directly to the next point.
The Soft-Boundary Reality🔗
Here's the part people get wrong: by default, a Pod in one namespace can reach a Service in any other namespace. The namespace scopes names and gives you a handle for policy — it does not isolate network traffic, and it grants no security on its own.
Real isolation is something you add:
-
RBAC = who can touch the objects
Restricts which users/ServiceAccounts can read or modify resources in a namespace. Without it, namespace membership says nothing about permissions. (Mastery covers RBAC in depth.)
-
NetworkPolicies = who can talk to whom
Until you apply one, the cluster network is wide open across namespaces. NetworkPolicies are how you actually stop
team-afrom reachingteam-b's Pods. (Efficiency: Networking covers these.)
Why this matters when you're carving up a cluster
Treating a namespace as a security boundary is how multi-tenant clusters get breached. If two tenants genuinely must not reach or affect each other, "different namespace" is the start — you need RBAC, NetworkPolicies, and quotas on top, and for hostile tenants, often separate clusters entirely. Namespace = organization and policy anchor; not a firewall.
Deletion Blast Radius🔗
Deleting a namespace deletes everything in it
kubectl delete namespace team-a-dev cascades to every namespaced object inside — Pods, Services, Deployments, ConfigMaps, Secrets, PVCs. There is no undo. Double-check the name; deleting the wrong namespace on a shared cluster is one of the fastest ways to ruin a day for another team.
If the namespace then hangs in Terminating instead of disappearing, that's a finalizer waiting on cleanup that isn't happening — a troubleshooting topic in its own right, covered in the Troubleshooting section.
Practice Exercises🔗
Exercise 1: Prove the namespace boundary is soft
Create two namespaces, run a Service in one, and reach it from a Pod in the other — with no NetworkPolicy in place.
Solution
kubectl create namespace svc-ns
kubectl create namespace client-ns
kubectl run web --image=nginx:1.21 -n svc-ns --labels=app=web # (1)!
kubectl expose pod web --port=80 --name=web-svc -n svc-ns
kubectl run client --image=busybox:1.35 -n client-ns -it --rm -- \
wget -qO- http://web-svc.svc-ns.svc.cluster.local # (2)!
- A Service + backing Pod in
svc-ns. - Reach it from
client-nsusing the FQDN.
It works with zero extra configuration — which is exactly the point. Cross-namespace traffic is open by default; a NetworkPolicy is what would block it.
Exercise 2: A quota that forces resource specs
Apply a ResourceQuota to a namespace, then try to create a Pod with no resource requests. Explain what happens.
Solution
apiVersion: v1
kind: ResourceQuota
metadata:
name: demo-quota
namespace: quota-demo
spec:
hard:
requests.cpu: "2"
requests.memory: 2Gi
kubectl apply -f quota.yaml
kubectl run nginx --image=nginx:1.21 -n quota-demo
# Error: failed quota: must specify requests.cpu, requests.memory
The quota makes resource requests mandatory. In a real namespace you'd pair it with a LimitRange so Pods get default requests automatically instead of being rejected.
Quick Recap🔗
| Concept | What to Know |
|---|---|
| Namespace | Scope for the names of namespaced objects; multi-tenancy anchor |
| Namespaced vs cluster-scoped | kubectl api-resources --namespaced=true/false |
default |
Where objects land with no namespace set — don't use it for real work |
kube-system |
The cluster's own system components; cluster-wide blast radius, hands off |
| ResourceQuota | Hard ceiling on a namespace's total consumption |
| LimitRange | Per-container defaults and bounds |
| FQDN | service.namespace.svc.cluster.local for cross-namespace access |
| Soft boundary | Namespaces don't isolate network or grant security — RBAC + NetworkPolicies do |
| Deletion | Cascades to everything inside — no undo |
Further Reading🔗
Official Documentation🔗
- Namespaces - Concept reference
- Resource Quotas - All quota dimensions
- Limit Ranges - Defaults and constraints
Deep Dives🔗
- Kubernetes Multi-Tenancy - Where namespaces fit and where they don't
- Three Tenancy Models for Kubernetes - When a namespace is enough vs when you need separate clusters
Related Articles🔗
- Services: Stable Networking for Pods - The DNS that cross-namespace access builds on
- ConfigMaps and Secrets - Namespace-scoped, no cross-namespace references
- Labels and Selectors - How namespace labels drive policy
What's Next?🔗
You can scope, isolate, and rein in resources per namespace — and you know precisely where that boundary stops. The last primitive ties the whole system together: the key/value query layer that Services, Deployments, quotas, and NetworkPolicies all use to find the objects they act on.
Next: Labels and Selectors — the query language wired through every controller in Kubernetes.