Post

Kubernetes Learning Path: Understanding Namespaces

Kubernetes Learning Path: Understanding Namespaces

Kubernetes Learning Path: Understanding Namespaces

In the previous posts, we’ve deployed apps and managed configuration. But what happens when you want to run multiple environments on the same cluster? Maybe you’re running dev, staging, and prod all together, or different teams need to share the cluster without accidentally deleting each other’s stuff.

That’s where namespaces come in. They’re basically Kubernetes’s way of keeping things organized.

What Are Namespaces?

Think of namespaces like separate folders on your computer. You could put all your files in one big folder, but that gets messy fast. Instead, you organize them into different folders—work stuff here, personal stuff there, projects over there.

Namespaces do the same thing for your Kubernetes resources. They give you:

  • Isolation - Resources in one namespace don’t interfere with resources in another
  • Organization - Group related resources together (dev, staging, prod)
  • Access Control - Different teams can have permissions to different namespaces
  • Resource Limits - You can set quotas per namespace

But here’s the thing—namespaces aren’t completely isolated. Pods in different namespaces can still talk to each other (unless you set up network policies). Think of namespaces more like organizational boundaries than security walls.

See Your Current Namespaces

Kubernetes creates a few namespaces by default:

1
kubectl get namespaces

Output:

1
2
3
4
5
NAME              STATUS   AGE
default           Active   2d
kube-node-lease   Active   2d
kube-public       Active   2d
kube-system       Active   2d

Here’s what they’re for:

  • default - Where your resources go if you don’t specify a namespace
  • kube-system - System components like DNS and dashboard live here
  • kube-public - Public resources readable by everyone (rarely used)
  • kube-node-lease - Heartbeat information from nodes (you won’t need to touch this)

When you’ve been running kubectl get pods in the previous tutorials, you’ve actually been looking at the default namespace this whole time. You just didn’t know it.

Creating Namespaces

Let’s create two namespaces for development and production environments:

1
2
kubectl create namespace dev
kubectl create namespace prod

Output:

1
2
namespace/dev created
namespace/prod created

Check your namespaces again:

1
kubectl get namespaces

Output:

1
2
3
4
5
6
7
NAME              STATUS   AGE
default           Active   2d
dev               Active   5s
kube-node-lease   Active   2d
kube-public       Active   2d
kube-system       Active   2d
prod              Active   5s

Your new namespaces are ready to use.

Real Example: Deploy App to Multiple Namespaces

Let’s deploy a simple app to both dev and prod namespaces. We’ll use different ConfigMaps for each environment to show how namespaces keep things separate.

Step 1: Create ConfigMaps for Each Environment

First, create a ConfigMap for dev with development-specific settings:

1
2
3
4
5
kubectl create configmap app-config \
  --from-literal=ENVIRONMENT=development \
  --from-literal=DEBUG_MODE=true \
  --from-literal=API_URL=http://dev-api.example.com \
  --namespace=dev

Output:

1
configmap/app-config created

Now create a different ConfigMap for prod with production settings:

1
2
3
4
5
kubectl create configmap app-config \
  --from-literal=ENVIRONMENT=production \
  --from-literal=DEBUG_MODE=false \
  --from-literal=API_URL=http://api.example.com \
  --namespace=prod

Output:

1
configmap/app-config created

Notice we used the same name app-config in both namespaces. You might think this would cause a conflict, but it doesn’t—resources with the same name can exist in different namespaces without any issues. They’re completely separate.

View the dev ConfigMap:

1
kubectl get configmap app-config -n dev -o yaml

Output:

1
2
3
4
5
6
7
8
9
apiVersion: v1
data:
  API_URL: http://dev-api.example.com
  DEBUG_MODE: "true"
  ENVIRONMENT: development
kind: ConfigMap
metadata:
  name: app-config
  namespace: dev

The -n flag is shorthand for --namespace. You’ll use it a lot.

Step 2: Create the Application Deployment

Create a file called app-deployment.yaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
        env:
        # Environment variables pulled from ConfigMap
        # Each env variable references a key from the ConfigMap in the SAME namespace
        - name: ENVIRONMENT              # Name of the environment variable inside the container
          valueFrom:
            configMapKeyRef:
              name: app-config           # ConfigMap name (must exist in the same namespace)
              key: ENVIRONMENT           # Key from the ConfigMap to get the value from
        - name: DEBUG_MODE               # Another environment variable in the container
          valueFrom:
            configMapKeyRef:
              name: app-config           # Same ConfigMap as above
              key: DEBUG_MODE            # Different key from the same ConfigMap
        - name: API_URL                  # Third environment variable
          valueFrom:
            configMapKeyRef:
              name: app-config           # Still the same ConfigMap
              key: API_URL               # Another key from the ConfigMap

This is a simple nginx deployment that pulls configuration from a ConfigMap. The cool part? We can use this exact same YAML file in both namespaces, and each deployment will automatically grab its own namespace-specific ConfigMap. Same file, different configs.

Deploy to dev:

1
kubectl apply -f app-deployment.yaml --namespace=dev

Output:

1
deployment.apps/webapp created

Deploy to prod (using the same file):

1
kubectl apply -f app-deployment.yaml --namespace=prod

Output:

1
deployment.apps/webapp created

Step 3: Verify the Deployments

Check pods in the dev namespace:

1
kubectl get pods -n dev

Output:

1
2
3
NAME                      READY   STATUS    RESTARTS   AGE
webapp-7d8c9f5b4-2x9k7    1/1     Running   0          15s
webapp-7d8c9f5b4-5m2p9    1/1     Running   0          15s

Check pods in the prod namespace:

1
kubectl get pods -n prod

Output:

1
2
3
NAME                      READY   STATUS    RESTARTS   AGE
webapp-7d8c9f5b4-6n3q8    1/1     Running   0          10s
webapp-7d8c9f5b4-8p4r2    1/1     Running   0          10s

Same deployment name, same pod names—but they’re in different namespaces, so there’s no conflict.

Step 4: Verify Different Configurations

Let’s check that each deployment is actually using its own ConfigMap. Check the dev environment variables:

1
kubectl exec -n dev deployment/webapp -- sh -c 'echo "ENV: $ENVIRONMENT" && echo "DEBUG: $DEBUG_MODE" && echo "API: $API_URL"'

Output:

1
2
3
ENV: development
DEBUG: true
API: http://dev-api.example.com

Now check prod:

1
kubectl exec -n prod deployment/webapp -- sh -c 'echo "ENV: $ENVIRONMENT" && echo "DEBUG: $DEBUG_MODE" && echo "API: $API_URL"'

Output:

1
2
3
ENV: production
DEBUG: false
API: http://api.example.com

There we go! Each deployment is using its own ConfigMap values. The dev environment has debug mode enabled and points to a dev API, while prod has debug mode disabled and points to the production API. Same deployment file, totally different behavior.

Working with Namespaces

View Resources in a Specific Namespace

1
2
3
4
5
# Get all pods in dev namespace
kubectl get pods -n dev

# Get all resources in prod namespace
kubectl get all -n prod

Output:

1
2
3
4
5
6
NAME                          READY   STATUS    RESTARTS   AGE
pod/webapp-7d8c9f5b4-6n3q8    1/1     Running   0          5m
pod/webapp-7d8c9f5b4-8p4r2    1/1     Running   0          5m

NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/webapp   2/2     2            2           5m

View Resources Across All Namespaces

1
kubectl get pods --all-namespaces

Or use the shorthand:

1
kubectl get pods -A

Output:

1
2
3
4
5
6
NAMESPACE     NAME                                      READY   STATUS    RESTARTS   AGE
dev           webapp-7d8c9f5b4-2x9k7                    1/1     Running   0          10m
dev           webapp-7d8c9f5b4-5m2p9                    1/1     Running   0          10m
prod          webapp-7d8c9f5b4-6n3q8                    1/1     Running   0          8m
prod          webapp-7d8c9f5b4-8p4r2                    1/1     Running   0          8m
kube-system   coredns-59b4f5bbd5-8xk2p                  1/1     Running   0          2d

Set a Default Namespace

Typing -n dev or -n prod every single time gets annoying real quick. You can set a default namespace for your current context so you don’t have to:

1
kubectl config set-context --current --namespace=dev

Output:

1
Context "k3d-learning" modified.

Now all your commands will default to the dev namespace:

1
kubectl get pods

Output (without needing -n dev):

1
2
3
NAME                      READY   STATUS    RESTARTS   AGE
webapp-7d8c9f5b4-2x9k7    1/1     Running   0          15m
webapp-7d8c9f5b4-5m2p9    1/1     Running   0          15m

Switch back to default when you’re done:

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

Describe a Namespace

1
kubectl describe namespace dev

Output:

1
2
3
4
5
6
7
8
Name:         dev
Labels:       kubernetes.io/metadata.name=dev
Annotations:  <none>
Status:       Active

No resource quota.

No LimitRange resource.

This shows you any resource quotas or limits applied to the namespace (none in our case).

Namespace Communication

Here’s something that caught me by surprise—pods in different namespaces can still talk to each other by default. Namespaces don’t block network traffic, they just change how DNS works.

Let’s say you have a service called api-service in the prod namespace. Pods can reach it using:

  • api-service - Only works from within the same namespace
  • api-service.prod - Works from any namespace
  • api-service.prod.svc.cluster.local - Full DNS name (also works from anywhere)

So namespaces aren’t security walls—they’re more like organizational folders. If you need actual network isolation, you’d have to set up Network Policies, but that’s beyond the scope of this post.

Practical Tips

Specify Namespace in YAML Files

Instead of using -n flags all the time, you can specify the namespace in your YAML files:

1
2
3
4
5
6
7
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: dev    # <-- Specify namespace here
data:
  ENVIRONMENT: development

This is clearer and less error-prone, especially when managing multiple resources.

Use Namespaces for Environments

A common pattern is to use namespaces for different environments on the same cluster:

  • dev namespace for development
  • staging namespace for staging
  • prod namespace for production

But remember—if you have the resources, separate clusters for production are often better. Namespaces provide logical separation, not physical isolation.

Don’t Overdo It

I’ve seen people create a namespace for every little thing, and honestly, it just makes life harder. Start simple:

  • One or two namespaces for different environments
  • Maybe separate namespaces for completely different applications
  • Don’t create namespaces just because you can

If you find yourself constantly switching between 10+ namespaces, you’ve probably gone too far. Keep it simple.

Clean Up

Delete the deployments from both namespaces:

1
2
kubectl delete deployment webapp -n dev
kubectl delete deployment webapp -n prod

Output:

1
2
deployment.apps "webapp" deleted
deployment.apps "webapp" deleted

Delete the ConfigMaps:

1
2
kubectl delete configmap app-config -n dev
kubectl delete configmap app-config -n prod

Output:

1
2
configmap "app-config" deleted
configmap "app-config" deleted

Delete the namespaces themselves (this also deletes everything inside them):

1
2
kubectl delete namespace dev
kubectl delete namespace prod

Output:

1
2
namespace "dev" deleted
namespace "prod" deleted

When you delete a namespace, Kubernetes automatically deletes everything inside it—all pods, deployments, services, everything. Super handy for cleanup, but also scary if you accidentally delete the wrong namespace. Double-check before you hit enter on that delete command.

What You Learned

✅ What namespaces are and why they’re useful
✅ How to create and manage namespaces
✅ How to deploy the same application to different namespaces
✅ How to use namespace-specific ConfigMaps
✅ How to work with resources across namespaces

That’s pretty much it for namespaces. They’re not complicated—just think of them as folders for organizing your cluster. Once you start using them, you’ll wonder how you managed without them.

What’s Next?

In Part 4 of this series, we’ll explore Persistent Storage in Kubernetes. You’ll learn how to:

  • Use volumes to persist data beyond pod lifecycles
  • Work with Persistent Volumes and Persistent Volume Claims
  • Deploy stateful applications like databases

Stay tuned!


Series Navigation

Part 1: Deploy Your First App
Part 2: ConfigMaps and Secrets
Part 3: Understanding Namespaces ← You just finished this!
Part 4: Persistent Storage (Coming soon)
Part 5: Ingress and Load Balancing (Coming soon)


Found a mistake or have questions? Feel free to open an issue here.

Thanks for reading! Feel free to share this post with others.