Namespaces

Organize and isolate resources using Namespaces. Learn about resource quotas and namespace-based access control.

9 min read

Namespaces

In the previous tutorial, we learned how to set resource limits and quotas — basically putting guardrails on how much CPU and memory your containers can gobble up. Now let's tackle the next question.

As your cluster grows, things get... messy. Like, "everyone dumping files on the Desktop" messy. You've got dev stuff, staging stuff, production stuff, three different teams' experiments, and that one Pod someone created six months ago that nobody knows what it does.

You need folders. Virtual folders for your cluster. That's what Namespaces are.

What is a Namespace?

A Namespace is a logical partition of cluster resources. Think of it like floors in an office building — each team gets their own floor, with their own rooms, their own coffee machine, and their own mess. But they're all in the same building.

Objects in different namespaces can have the same name (two teams can both have a service called api), resources within a namespace find each other easily, and resources across namespaces need to use the full address — like sending mail between floors.

"So it's basically just a folder?"

Pretty much! But a folder with superpowers — resource quotas, access control, network isolation. Regular folders wish they were this cool.

Default Namespaces

Every cluster comes with some namespaces out of the box — like the pre-installed apps on your phone that you never asked for but are kinda useful:

kubectl get namespaces
NAME              STATUS   AGE
default           Active   1d
kube-system       Active   1d
kube-public       Active   1d
kube-node-lease   Active   1d

Let's break these down:

| Namespace | Purpose | | default | Where your resources go if you don't specify a namespace. It's the junk drawer of Kubernetes. | | kube-system | Kubernetes system components (API server, scheduler, etc.). Don't mess with this one. Seriously. | | kube-public | Readable by all users, used for cluster-wide visible resources. Like the lobby bulletin board. | | kube-node-lease | Node heartbeat data for node health. The "are you still alive?" namespace. |

Create a Namespace

Alright, let's make our own namespace! Two ways to do this:

Imperative (The Quick Way)

kubectl create namespace development

Boom. Done. One line.

Declarative (The Proper Way)

Create namespace.yaml:

apiVersion: v1
kind: Namespace
metadata:
  name: development
  labels:
    environment: dev
    team: backend

Apply it:

kubectl apply -f namespace.yaml

"When would I use declarative over imperative?"

If you want labels, annotations, or anything beyond a bare namespace — go declarative. Plus, you can check it into version control. Future-you will thank present-you.

Work with Namespaces

Here's the thing — once you create namespaces, you need to tell kubectl which one you're talking to. Otherwise it defaults to default (shocking, I know).

Specify Namespace in Commands

# List pods in a specific namespace
kubectl get pods -n development

# Create a pod in a specific namespace
kubectl run nginx --image=nginx -n development

# Delete from a specific namespace
kubectl delete pod nginx -n development

Specify Namespace in YAML

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: development  # Explicit namespace
spec:
  containers:
  - name: nginx
    image: nginx

Set Default Namespace

Tired of typing -n development on every. single. command? Same. Set a default:

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

Verify it took:

kubectl config view --minify | grep namespace

Want to go back to default?

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

How cool is that? No more -n flag on everything.

Create Complete Environment

Okay, this is where it gets fun. Let's set up a real development environment with its own namespace, resource quotas, and limits — like building a fully furnished apartment, not just an empty room.

1. Create the Namespace

# dev-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: dev
  labels:
    environment: development

2. Add Resource Quota

# dev-quota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: dev-quota
  namespace: dev
spec:
  hard:
    requests.cpu: "2"
    requests.memory: "4Gi"
    limits.cpu: "4"
    limits.memory: "8Gi"
    pods: "10"
    services: "5"
    persistentvolumeclaims: "5"

3. Add Limit Range

# dev-limits.yaml
apiVersion: v1
kind: LimitRange
metadata:
  name: dev-limits
  namespace: dev
spec:
  limits:
  - default:
      memory: "512Mi"
      cpu: "500m"
    defaultRequest:
      memory: "256Mi"
      cpu: "250m"
    type: Container

Apply the whole thing:

kubectl apply -f dev-namespace.yaml
kubectl apply -f dev-quota.yaml
kubectl apply -f dev-limits.yaml

Now check out what we built:

kubectl describe namespace dev
Name:         dev
Labels:       environment=development
Status:       Active

Resource Quotas
  Name:            dev-quota
  Resource         Used  Hard
  --------         ---   ---
  limits.cpu       0     4
  limits.memory    0     8Gi
  pods             0     10
  ...

Resource Limits
  Type       Resource  Min  Max  Default Request  Default Limit
  ----       --------  ---  ---  ---------------  -------------
  Container  cpu       -    -    250m             500m
  Container  memory    -    -    256Mi            512Mi

Look at that — a fully configured environment with quotas and default limits. Any container deployed here automatically gets sensible resource defaults. No more "oops I forgot to set limits" situations.

Cross-Namespace Communication

"Wait, if everything is separated, how do services in different namespaces talk to each other?"

Great question! They use fully qualified DNS names — basically the full mailing address:

<service-name>.<namespace>.svc.cluster.local

Let's Try It

Deploy nginx in the dev namespace:

kubectl create deployment nginx --image=nginx -n dev
kubectl expose deployment nginx --port=80 -n dev

From the default namespace, reach across and access it:

kubectl run test --image=busybox --rm -it --restart=Never -- wget -qO- nginx.dev.svc.cluster.local

Shorter form also works (Kubernetes is smart enough to figure it out):

kubectl run test --image=busybox --rm -it --restart=Never -- wget -qO- nginx.dev

It's like calling someone on another floor — you just need to know their extension. How cool is that?

Namespace Use Cases

Okay, so when should you actually create separate namespaces? Here are the big ones:

1. Environment Separation

├── dev/
│   └── (development workloads)
├── staging/
│   └── (staging workloads)
└── prod/
    └── (production workloads)
kubectl create namespace dev
kubectl create namespace staging
kubectl create namespace prod

2. Team Isolation

├── team-frontend/
├── team-backend/
├── team-data/
└── team-platform/

Each team gets their own namespace with:

  • Resource quotas (so the data team doesn't eat all the RAM... again)
  • RBAC roles (limit what they can touch)
  • Network policies (control cross-team traffic)

It's like giving each team their own office with their own budget and their own key card access. No more stepping on each other's toes.

3. Multi-Tenancy

For SaaS applications, give each tenant/customer their own namespace:

├── tenant-acme/
├── tenant-globex/
└── tenant-initech/

Namespace-Scoped vs Cluster-Scoped

"Does everything go in a namespace?"

Nope! Not everything lives in a namespace. Some things are building-wide (cluster-scoped), not floor-specific (namespace-scoped).

Namespace-scoped (exist within a namespace):

  • Pods, Deployments, Services
  • ConfigMaps, Secrets
  • PersistentVolumeClaims
  • Roles, RoleBindings

Cluster-scoped (exist cluster-wide):

  • Nodes
  • PersistentVolumes
  • StorageClasses
  • Namespaces themselves
  • ClusterRoles, ClusterRoleBindings

Want to see the full lists? Check what's namespace-scoped:

kubectl api-resources --namespaced=true

And what's cluster-scoped:

kubectl api-resources --namespaced=false

View Resources Across Namespaces

Sometimes you need to see what's going on everywhere. Because chaos doesn't respect namespace boundaries.

All namespaces

kubectl get pods --all-namespaces
# or shorter
kubectl get pods -A

Multiple specific namespaces

Kubernetes doesn't support querying multiple specific namespaces in one command (rude, honestly). But you can use a loop:

for ns in dev staging prod; do
  echo "=== $ns ==="
  kubectl get pods -n $ns
done

Delete a Namespace

⚠️ Big warning here. Deleting a namespace deletes everything inside it:

kubectl delete namespace development

Pods, Services, ConfigMaps, Secrets — all gone. Poof. No "are you sure?" prompt. No recycle bin. Just gone.

Don't run this on production at 4pm on a Friday. Or ever, unless you mean it.

Protect Against Accidental Deletion

Add a finalizer to prevent accidental deletion:

apiVersion: v1
kind: Namespace
metadata:
  name: production
  finalizers:
  - kubernetes
  labels:
    protected: "true"

Or use RBAC to restrict who can delete namespaces. Because not everyone should have the nuclear launch codes.

Namespace Best Practices

Alright, here's the wisdom section. Learn from the mistakes of those who came before you.

1. Don't Use Default for Production

The default namespace is fine for learning and messing around. But in production? Create explicit namespaces. Using default in production is like storing important documents in your Downloads folder. You can do it, but you shouldn't.

2. Use Descriptive Names

Future-you (and your teammates) will thank you:

# Good
namespace: payment-service-prod
namespace: user-api-staging

# Bad
namespace: ns1
namespace: my-namespace

3. Apply Labels Consistently

metadata:
  name: backend-prod
  labels:
    environment: production
    team: backend
    cost-center: engineering

Labels are how you'll query, filter, and apply policies later. Think of them as sticky notes on your folders. We'll dive deep into these in the next tutorial.

4. Set Resource Quotas

Always set quotas for shared clusters. Otherwise it's a free-for-all buffet and someone will deploy a memory-hungry monster that takes everyone else down:

spec:
  hard:
    pods: "50"
    requests.cpu: "10"
    requests.memory: "20Gi"

5. Use Network Policies

By default, all namespaces can communicate with each other. Which is... not great for production. Use network policies to restrict who can talk to whom:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-from-other-namespaces
  namespace: production
spec:
  podSelector: {}
  ingress:
  - from:
    - podSelector: {}  # Only from same namespace

Quick Commands Cheat Sheet

Here's your pocket reference for namespace commands:

# Create namespace
kubectl create namespace <name>

# List namespaces
kubectl get namespaces

# Describe namespace (shows quotas, limits)
kubectl describe namespace <name>

# Set default namespace
kubectl config set-context --current --namespace=<name>

# Get resources in namespace
kubectl get all -n <name>

# Delete namespace (and everything in it)
kubectl delete namespace <name>

Clean Up

kubectl delete namespace dev development 2>/dev/null

What's Next?

Nice work! You now know how to organize your cluster like a pro — separate environments, team isolation, resource quotas, the works. No more "everything in the default namespace" chaos.

But here's the thing — how do you select and query specific resources within those namespaces (or across them)? That's where Labels and Selectors come in. They're like the search and filter system for your entire cluster. Let's go!