Network Policies
By default, every Pod in Kubernetes can communicate with every other Pod — no restrictions. This is convenient for development but a security risk in production. Network Policies act as firewall rules that control traffic flow between Pods.
Why Network Policies?
Consider a typical application:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Frontend │────►│ Backend │────►│ Database │
└──────────┘ └──────────┘ └──────────┘
Without Network Policies, the Frontend can directly access the Database — bypassing the Backend. Network Policies let you enforce that only the Backend can talk to the Database.
Prerequisites
Network Policies require a CNI plugin that supports them:
| CNI Plugin | Network Policy Support |
|---|---|
| Calico | ✅ Full support |
| Cilium | ✅ Full support + extensions |
| Weave Net | ✅ Supported |
| Flannel | ❌ No support |
| kubenet | ❌ No support |
Note: In Minikube, enable Calico: minikube start --cni=calico or use the Calico addon: minikube addons enable calico.
Default Behavior
Without any Network Policies:
- All ingress (incoming) traffic to a Pod is allowed
- All egress (outgoing) traffic from a Pod is allowed
Once you apply a Network Policy that selects a Pod, that Pod’s default changes:
- All traffic NOT explicitly allowed by a policy is denied
- This is a “default deny” model per-direction
Network Policy YAML
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
namespace: default
spec:
podSelector:
matchLabels:
app: backend # This policy applies to Pods with label app=backend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend # Allow traffic FROM frontend Pods
ports:
- protocol: TCP
port: 8080 # Only on port 8080
egress:
- to:
- podSelector:
matchLabels:
app: database # Allow traffic TO database Pods
ports:
- protocol: TCP
port: 5432 # Only on port 5432
- to: # Allow DNS resolution
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Key Fields
| Field | Description |
|---|---|
podSelector |
Which Pods this policy applies to |
policyTypes |
Ingress, Egress, or both |
ingress.from |
Who can send traffic to these Pods |
egress.to |
Where these Pods can send traffic |
ports |
Which ports are allowed |
Common Patterns
1. Default Deny All Ingress
Block all incoming traffic to Pods in a namespace:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {} # Applies to ALL Pods in the namespace
policyTypes:
- Ingress
# No ingress rules = deny all ingress
2. Default Deny All Egress
Block all outgoing traffic from Pods in a namespace:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
# No egress rules = deny all egress
3. Default Deny All (Both Directions)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
4. Allow Traffic from Specific Namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-monitoring
spec:
podSelector: {}
ingress:
- from:
- namespaceSelector:
matchLabels:
purpose: monitoring # Allow from namespaces labeled purpose=monitoring
policyTypes:
- Ingress
First, label the namespace:
kubectl label namespace monitoring purpose=monitoring
5. Allow Traffic from Specific IP Ranges
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/8
except:
- 10.0.1.0/24 # Exclude this subnet
6. Combine Multiple Selectors
ingress:
- from:
# Rule 1: OR between array elements
- podSelector:
matchLabels:
app: frontend
- namespaceSelector:
matchLabels:
env: staging
# These are two SEPARATE rules (OR logic)
# Either Pods with app=frontend OR any Pod from staging namespace
Important: AND vs OR logic:
# OR: Two separate items in the 'from' array
- from:
- podSelector: {matchLabels: {app: frontend}} # Rule 1
- namespaceSelector: {matchLabels: {env: staging}} # Rule 2
# Allows: frontend pods (any namespace) OR any pod from staging namespace
# AND: Both selectors in the SAME item
- from:
- podSelector: {matchLabels: {app: frontend}}
namespaceSelector: {matchLabels: {env: staging}}
# Allows: frontend pods ONLY from staging namespace
Hands-On: Securing a 3-Tier Application
Let’s create a frontend → backend → database setup with Network Policies:
# application.yaml
# Database
apiVersion: apps/v1
kind: Deployment
metadata:
name: database
labels:
app: database
spec:
replicas: 1
selector:
matchLabels:
app: database
template:
metadata:
labels:
app: database
tier: data
spec:
containers:
- name: db
image: hashicorp/http-echo
args: ["-text=Database Response", "-listen=:5432"]
ports:
- containerPort: 5432
---
apiVersion: v1
kind: Service
metadata:
name: database
spec:
selector:
app: database
ports:
- port: 5432
---
# Backend
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 2
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
tier: api
spec:
containers:
- name: api
image: hashicorp/http-echo
args: ["-text=Backend Response", "-listen=:8080"]
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
selector:
app: backend
ports:
- port: 8080
---
# Frontend
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
tier: web
spec:
containers:
- name: web
image: hashicorp/http-echo
args: ["-text=Frontend Response", "-listen=:80"]
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
selector:
app: frontend
ports:
- port: 80
Now apply Network Policies:
# network-policies.yaml
# 1. Default deny all traffic in the namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# 2. Frontend: allow ingress from anywhere, egress to backend + DNS
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-policy
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Ingress
- Egress
ingress:
- {} # Allow all ingress
egress:
- to:
- podSelector:
matchLabels:
app: backend
ports:
- port: 8080
- to:
- namespaceSelector: {}
ports:
- port: 53
protocol: UDP
---
# 3. Backend: allow ingress from frontend, egress to database + DNS
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- port: 5432
- to:
- namespaceSelector: {}
ports:
- port: 53
protocol: UDP
---
# 4. Database: allow ingress from backend only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database-policy
spec:
podSelector:
matchLabels:
app: database
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: backend
ports:
- port: 5432
kubectl apply -f application.yaml
kubectl apply -f network-policies.yaml
# Test: Frontend → Backend (should work)
kubectl exec deployment/frontend -- wget -qO- http://backend:8080
# Test: Frontend → Database (should FAIL/timeout)
kubectl exec deployment/frontend -- wget -qO- --timeout=3 http://database:5432
# Test: Backend → Database (should work)
kubectl exec deployment/backend -- wget -qO- http://database:5432
Debugging Network Policies
# List Network Policies
kubectl get networkpolicies
# or: kubectl get netpol
# Describe a policy
kubectl describe netpol backend-policy
# Test connectivity
kubectl exec -it <pod-name> -- wget -qO- --timeout=3 http://<service>:<port>
# Check if a CNI supports network policies
kubectl get pods -n kube-system | grep -i calico
Best Practices
- Start with default-deny in every namespace, then allow specific traffic
- Always allow DNS egress (port 53 UDP/TCP) — Pods need DNS to resolve Service names
- Use labels consistently — Network Policies rely on label selectors
- Test policies thoroughly — Misconfigured policies can break your application
- Document your network architecture — Track which Pods can communicate
Summary
- Network Policies are Kubernetes’ built-in firewall
- They require a CNI that supports them (Calico, Cilium)
- Default behavior: all traffic allowed (no policies)
- Once a policy selects a Pod, unmatched traffic is denied
- Use default-deny + allow-list pattern for security
- Always allow DNS egress (port 53) for Service discovery
What’s Next?
Network Policies control which Pods can communicate. But who can access the Kubernetes API itself? In the next chapter, we’ll learn about RBAC (Role-Based Access Control) — controlling who can do what in your cluster.