Kubernetes Namespaces

By Vishvesh Patel · DevOps EngineerUpdated September 12, 2026

How namespaces partition a cluster. What is and is not namespaced, cross-namespace DNS, quotas and RBAC, and why a namespace can get stuck Terminating.

A namespace is a name-scoping boundary inside a cluster. Two Deployments can both be called api as long as they live in different namespaces, and RBAC, quotas and network policy can all be applied per namespace.

What a namespace is not is a security boundary on its own. Pods in different namespaces can reach each other over the network by default, and they share the same nodes and kernel.

What is namespaced and what is not

kubectl api-resources --namespaced=true | head
kubectl api-resources --namespaced=false
NamespacedCluster-scoped
Pods, Deployments, StatefulSets, JobsNodes
Services, Ingresses, EndpointSlicesPersistentVolumes
ConfigMaps, Secrets, ServiceAccountsStorageClasses
PVCs, Roles, RoleBindingsClusterRoles, ClusterRoleBindings
ResourceQuotas, LimitRanges, NetworkPoliciesCustomResourceDefinitions, Namespaces

The PV/PVC split is the one that catches people: a PersistentVolumeClaim is namespaced but the PersistentVolume it binds is not. So a claim can only be used by Pods in its own namespace, while the underlying volume is a cluster resource.

Secrets being namespaced is the other frequent surprise. A registry credential created in default does nothing for a Pod in payments.

The built-in namespaces

NamespaceContains
defaultWhere objects go when you do not say otherwise
kube-systemControl plane and cluster components. Do not deploy here
kube-publicWorld-readable, rarely used
kube-node-leaseNode heartbeat objects

Avoid default for anything real. It has no quota, no network policy and no meaningful RBAC, and things accumulate there until nobody knows what is safe to delete.

Working with them

kubectl create namespace payments

# or declaratively, which is what you want in Git
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Namespace
metadata:
  name: payments
  labels:
    team: payments
    environment: production
EOF
kubectl get pods -n payments
kubectl get pods -A                    # every namespace
kubectl get namespaces

Set a default so you stop typing -n:

kubectl config set-context --current --namespace=payments

Forgetting the namespace is the most common reason kubectl get pods reports "No resources found" when something is definitely running.

Label your namespaces. They are the handle for network policy selectors, quota reporting and cost allocation, and adding them later means reconstructing who owns what.

Cross-namespace DNS

A Service's short name only resolves within its own namespace. Across namespaces you need the namespace segment:

payments-api                                    # same namespace
payments-api.payments                           # from another namespace
payments-api.payments.svc.cluster.local         # fully qualified

A call that works from one namespace and fails from another is nearly always this.

Where you genuinely want to alias a Service from elsewhere, an ExternalName Service does it without hardcoding the FQDN in application config:

apiVersion: v1
kind: Service
metadata:
  name: payments-api
  namespace: checkout
spec:
  type: ExternalName
  externalName: payments-api.payments.svc.cluster.local

What namespaces are actually good for

RBAC. A Role and RoleBinding grant permissions within one namespace, which is how you give a team full control of its own space and nothing else:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: payments-team
  namespace: payments
subjects:
  - kind: Group
    name: payments-engineers
roleRef:
  kind: ClusterRole
  name: edit           # built-in: read/write most things, no RBAC changes
  apiGroup: rbac.authorization.k8s.io

Note the roleRef points at a ClusterRole while the binding itself is namespaced, that is the normal pattern for reusing the built-in view, edit and admin roles per namespace.

Resource quotas. Without one, any namespace can consume the whole cluster:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: payments-quota
  namespace: payments
spec:
  hard:
    requests.cpu: "20"
    requests.memory: 40Gi
    pods: "50"

Pair it with a LimitRange, because once a quota constrains a resource every Pod must specify it or be rejected.

Network policy. This is what turns a namespace into an actual boundary. By default all Pods can reach all Pods; a default-deny policy plus explicit allows changes that:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: payments
spec:
  podSelector: {}
  policyTypes: [Ingress]

Network policies require a CNI that implements them. Calico, Cilium, and similar. On a CNI that does not, the policy is accepted and silently does nothing, which is a dangerous way to believe you are protected.

How to divide a cluster

Common patterns, with their trade-offs:

  • Per team or service (payments, checkout). The usual choice. Clean RBAC and quota boundaries.
  • Per environment in one cluster (dev, staging). Cheap, but a noisy dev workload shares nodes with staging and a cluster-wide incident hits both. Fine for non-production; separate clusters are better for production isolation.
  • Per developer (dev-alice). Good for ephemeral preview environments, needs automated cleanup.

What does not work is one namespace for everything. You lose quotas, per-team RBAC and any meaningful network policy, and everything ends up in default.

Deleting, and the Terminating trap

kubectl delete namespace payments

This deletes everything inside it. Deployments, Services, Secrets, ConfigMaps, and PVCs. A PVC whose StorageClass has reclaimPolicy: Delete takes its data with it. Deleting a namespace is one of the most destructive single commands in Kubernetes.

Sometimes it hangs in Terminating indefinitely. The cause is virtually always a finalizer on a resource whose controller is gone, commonly a custom resource left behind after its operator was uninstalled:

kubectl get namespace payments -o json | jq '.status.conditions'
"message": "Some resources are remaining: mycrds.example.com has 2 resource instances"

The correct fix is to remove the blocking resources or reinstall the operator so its finalizers can run:

kubectl get mycrds.example.com -n payments
kubectl patch mycrd my-instance -n payments -p '{"metadata":{"finalizers":null}}' --type=merge

Force-removing the namespace's own finalizer through the API is the widely-circulated trick, and it does work, but it orphans any resources that had not been cleaned up, including cloud load balancers and volumes that will keep billing. Treat it as a last resort and check for leftovers afterwards.

Quick reference

TaskCommand
Createkubectl create namespace <name>
Listkubectl get namespaces
Work in onekubectl get pods -n <name>
Across allkubectl get pods -A
Set the defaultkubectl config set-context --current --namespace=<name>
What is namespacedkubectl api-resources --namespaced=true
Everything in onekubectl get all -n <name>
Quota usagekubectl describe resourcequota -n <name>
Why it will not deletekubectl get ns <name> -o json | jq '.status.conditions'

Key takeaways

  • Namespaces scope names and policy, not the network. Without NetworkPolicy, everything can still reach everything.
  • PVCs are namespaced; PersistentVolumes are not. So are Secrets. A registry credential must exist in the Pod's own namespace.
  • Cross-namespace DNS needs the namespace segment in the name.
  • Avoid default. No quota, no policy, no ownership.
  • Label namespaces from the start: network policy, quotas and cost reporting all key on them.
  • Pair a ResourceQuota with a LimitRange, or Pods without requests are rejected.
  • Deleting a namespace deletes its PVCs, and a Delete reclaim policy destroys the data.
  • A stuck Terminating namespace is a finalizer, usually from an uninstalled operator.

Check your understanding

Exercise

How isolated is a namespace?

A team puts an internal admin service in its own namespace, assuming other teams cannot reach it. Is that assumption correct on a default cluster?

Exercise

A namespace that will not delete

A namespace has been Terminating for an hour. Its conditions report "Some resources are remaining: widgets.example.com has 2 resource instances". What is the correct first action?


Frequently Asked Questions

What is a Kubernetes namespace used for?

It partitions a cluster into named scopes so resource names only need to be unique within one, and it gives RBAC, resource quotas, limit ranges and network policies something to attach to. That combination is what lets several teams share a cluster: each gets its own space, its own permissions, and a cap on how much it can consume. It is an organisational and policy boundary rather than a runtime one. Workloads still share the same nodes.

Are namespaces a security boundary?

Only partly, and assuming otherwise is a common mistake. Kubernetes networking is flat by default, so a Pod in any namespace can reach a Service in any other. Namespaces do provide an RBAC boundary, so you can stop people managing resources elsewhere, but they do not stop workloads talking to each other. Real isolation needs NetworkPolicy resources enforced by a capable CNI such as Calico or Cilium, plus Pod Security Standards. For strong isolation between untrusted tenants, separate clusters remain the safer answer.

How do I call a Service in another namespace?

Include the namespace in the DNS name: service-name.namespace, or the fully qualified service-name.namespace.svc.cluster.local. The bare short name only resolves within the caller's own namespace because the cluster DNS search path appends that namespace first. This is why a request that works between two services in the same namespace fails the moment one of them moves.

Which resources are not namespaced?

Nodes, PersistentVolumes, StorageClasses, ClusterRoles, ClusterRoleBindings, CustomResourceDefinitions and namespaces themselves are all cluster-scoped. kubectl api-resources --namespaced=false lists them for your cluster. The distinction that surprises people most is that PersistentVolumeClaims are namespaced while the PersistentVolumes they bind are not, and that Secrets are namespaced, so a registry pull secret must exist in the same namespace as the Pod that needs it.

Why is my namespace stuck in Terminating?

Something inside it has a finalizer that is not completing, almost always a custom resource whose operator has been uninstalled so nothing is left to run the cleanup. Check with kubectl get namespace <name> -o json | jq '.status.conditions', which names the remaining resource types. Fix it by deleting those resources or reinstalling the operator. Force-removing the namespace's finalizer is widely suggested and does work, but it orphans whatever the finalizers were protecting, including cloud load balancers and disks that carry on costing money.

Should I use one namespace per environment or separate clusters?

Namespaces per environment are cheap and fine for development and staging, where sharing nodes and a control plane is acceptable. For production, separate clusters are safer: namespaces share nodes, a kernel and a control plane, so a resource-hungry workload or a cluster-wide upgrade affects everything at once. A common arrangement is one non-production cluster split by namespace and a dedicated production cluster, itself split by team or service.

Continue Learning

Explore Related Topics

Try the Tool

Practise this in the browser

Kubernetes Playground

Practise kubectl against a simulated cluster in your browser.

When it breaks

Related Resources