Kubernetes allows applications to run across multiple pods, services, and namespaces. By default, pods can often communicate with other pods across the cluster, provided the network environment permits it.

This is convenient when you are getting started, but it can create security risks in production. For example, a frontend application may need to access a backend API, while the database should accept connections only from that backend.

Kubernetes NetworkPolicies help control this communication. They let you define which network traffic is allowed into selected pods and which traffic those pods can send out.

In this article, we will create a practical NetworkPolicy setup, restrict access between frontend, backend, and database pods, and explain how to verify that the rules work as expected.

What Is a Kubernetes NetworkPolicy?

A NetworkPolicy is a Kubernetes resource that defines network traffic rules for a selected group of pods.

You can use it to control:

  • Ingress traffic, which enters selected pods.
  • Egress traffic, which leaves selected pods.
  • Traffic between pods in different namespaces.
  • Traffic to or from selected IP address ranges, where supported by the policy rules and network implementation.

NetworkPolicies work with pod labels, namespace labels, ports, and IP address ranges. They are not traditional firewall rules attached directly to a node.

An important detail is that creating a NetworkPolicy does not guarantee that traffic will be restricted. Your cluster's network plugin must support and enforce NetworkPolicies. If it does not, the policy resource may exist without providing the expected isolation.

How NetworkPolicy Rules Work

Kubernetes NetworkPolicies use a combination of pod selection and traffic rules.

SettingPurpose
podSelectorSelects the pods to which the policy applies
policyTypesSpecifies whether the policy controls ingress, egress, or both
ingressDefines allowed incoming traffic
egressDefines allowed outgoing traffic
namespaceSelectorSelects namespaces using their labels
portRestricts traffic to a particular destination port

NetworkPolicies are additive. If multiple policies select a pod, the allowed traffic is the combined set of traffic permitted by those policies for the relevant direction.

Once a pod is isolated for ingress or egress, traffic in that direction is denied unless an applicable policy allows it. Traffic in a direction for which the pod is not isolated remains subject to the cluster's existing networking behavior.

Step 1: Create Separate Namespaces

For this example, we will separate the application into three namespaces:

  • frontend: contains the user-facing application.
  • backend: contains the API.
  • database: contains the database workload.

Create the namespaces:

YAML
apiVersion: v1
kind: Namespace
metadata:
  name: frontend
---
apiVersion: v1
kind: Namespace
metadata:
  name: backend
---
apiVersion: v1
kind: Namespace
metadata:
  name: database

Save this as namespaces.yaml and apply it:

Bash
kubectl apply -f namespaces.yaml

Namespaces help organize workloads, but they do not automatically block network communication between them. NetworkPolicies are needed when you want to restrict pod traffic.

Step 2: Deploy Sample Pods

Next, create a simple pod in each namespace. These pods are for demonstrating network rules, not for running a production application.

YAML
apiVersion: v1
kind: Pod
metadata:
  name: frontend
  namespace: frontend
  labels:
    app: frontend
spec:
  containers:
    - name: web
      image: nginx:stable
      ports:
        - containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
  name: backend
  namespace: backend
  labels:
    app: backend
spec:
  containers:
    - name: api
      image: nginx:stable
      ports:
        - containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
  name: database
  namespace: database
  labels:
    app: database
spec:
  containers:
    - name: db
      image: nginx:stable
      ports:
        - containerPort: 5432

Save the file as sample-pods.yaml and apply it:

Bash
kubectl apply -f sample-pods.yaml

The database pod uses NGINX only to provide a simple test endpoint. It is not a real database and does not listen on port 5432 just because that port is declared in the pod specification.

In a real deployment, replace these demonstration pods with the appropriate application workloads and actual listening ports.

Step 3: Allow Frontend Traffic to the Backend

Suppose the frontend needs to call the backend API on TCP port 8080. The backend should accept traffic from pods labeled app=frontend in the frontend namespace.

Create this policy in the backend namespace:

YAML
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: backend
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: frontend
          podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 8080

Save it as allow-frontend-to-backend.yaml and apply it:

Bash
kubectl apply -f allow-frontend-to-backend.yaml

This policy selects backend pods labeled app=backend and allows incoming TCP traffic on port 8080 from frontend pods labeled app=frontend.

The namespaceSelector and podSelector are in the same item under from. That means both conditions must match: the source pod must have the required label and belong to the selected namespace.

The policy does not create a listener on port 8080. The backend application must actually listen on that port for connections to succeed.

Step 4: Restrict Access to the Database

Now suppose only backend pods should connect to the database on TCP port 5432.

Create a policy in the database namespace:

YAML
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-backend-to-database
  namespace: database
spec:
  podSelector:
    matchLabels:
      app: database
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: backend
          podSelector:
            matchLabels:
              app: backend
      ports:
        - protocol: TCP
          port: 5432

Apply the policy:

Bash
kubectl apply -f allow-backend-to-database.yaml

The policy allows matching backend pods to connect to the selected database pods on TCP port 5432.

Other incoming connections to those database pods are denied by this policy only if the pods are isolated for ingress and no other applicable NetworkPolicy permits the traffic.

For a secure design, review all policies that select the database pods. Since policies are additive, another policy that allows broader traffic could also permit connections.

Step 5: Deny Unwanted Incoming Traffic

A common security requirement is to deny incoming traffic by default and then allow only the connections that are needed.

You can create a default-deny ingress policy in a namespace:

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

This policy selects all pods in the database namespace and isolates them for ingress. No incoming connections are permitted unless another applicable NetworkPolicy allows them.

Because policies are additive, the allow-backend-to-database policy can still permit the intended backend traffic.

Be careful when applying default-deny policies to existing namespaces. They can break application traffic, health checks, monitoring, or other required connections if the necessary allow rules are missing.

Also, default-deny ingress does not restrict outgoing connections. Egress needs its own policy.

Step 6: Control Outgoing Traffic with Egress Policies

Ingress policies control traffic entering selected pods. Egress policies control traffic leaving selected pods.

For example, a backend application might need to connect to the database but should not be allowed to send traffic to arbitrary destinations.

An egress policy can allow the backend pods to connect to the database namespace on port 5432:

YAML
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-egress-to-database
  namespace: backend
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: database
          podSelector:
            matchLabels:
              app: database
      ports:
        - protocol: TCP
          port: 5432

This isolates selected backend pods for egress and allows the specified database traffic.

Once egress isolation is enabled, other outgoing traffic is denied unless another applicable policy allows it. The backend may also need access to DNS, authentication services, external APIs, or telemetry endpoints.

If those dependencies are required, add explicit rules for them. Otherwise, the application may be unable to resolve service names or contact external systems.

Step 7: Verify That the Policies Work

After applying the policies, check that the resources exist:

Bash
kubectl get networkpolicy -A

Inspect a specific policy:

Bash
kubectl describe networkpolicy allow-backend-to-database \
  -n database

Check the pod labels and namespace assignments:

Bash
kubectl get pods -n backend --show-labels
kubectl get pods -n database --show-labels

You can also inspect the standard namespace labels:

Bash
kubectl get namespace frontend --show-labels
kubectl get namespace backend --show-labels
kubectl get namespace database --show-labels

For an actual connectivity test, use pods that have a suitable network diagnostic tool and an application listening on the target port.

For example, if the backend contains a shell and curl, you might test an HTTP endpoint from the backend namespace. The destination should be the database service or pod endpoint appropriate to your environment, and the test protocol must match the application.

Do not rely on the sample NGINX database pod to prove connectivity on port 5432. It does not run a PostgreSQL server.

If a connection fails, check the NetworkPolicy rules, pod and namespace labels, actual listening ports, routing, service configuration, and network plugin behavior.

Common Mistakes to Avoid

Assuming namespaces block traffic automatically

Namespaces provide organization, not automatic network isolation. Use NetworkPolicies and verify that your network plugin enforces them.

Selecting the wrong pods

A policy affects only the pods matched by its podSelector. If labels do not match, the intended policy may not protect the workload.

Misunderstanding selector logic

A namespaceSelector and podSelector in the same item under from or to must both match. Separate items in the list represent alternative allowed sources or destinations.

Forgetting that policies are additive

A restrictive policy does not cancel an allow rule from another applicable policy. Review all policies that select the same pods.

Blocking DNS or required services

Default-deny egress policies can prevent DNS lookups and outbound API calls. Add rules for the required destinations and ports.

Assuming the port declaration starts a service

A NetworkPolicy permits or denies traffic; it does not configure the application to listen on a port. The container and service must use the correct ports.

Ignoring network plugin support

The existence of a NetworkPolicy object does not prove that traffic is being enforced. Confirm that the installed networking implementation supports NetworkPolicies.

Best Practices for Production Clusters

  • Separate workloads by namespace where it helps define clear security boundaries.
  • Use stable, meaningful pod labels in NetworkPolicies.
  • Start with default-deny policies where appropriate, then allow only required traffic.
  • Review both ingress and egress requirements.
  • Permit necessary DNS, monitoring, health-check, and external service traffic.
  • Keep policies under version control and review them with application changes.
  • Test allowed and denied connections before production deployment.
  • Audit all policies that select sensitive workloads.
  • Confirm network plugin support and enforcement.
  • Combine network isolation with authentication, authorization, and application-level security.

Summary

Kubernetes NetworkPolicies let you control communication between selected pods using labels, namespaces, ports, and traffic direction.

By allowing frontend traffic to the backend and restricting database access to backend workloads, you can reduce unnecessary communication inside a cluster.

A reliable implementation requires more than applying YAML. Check selector logic, account for additive policies, allow required dependencies, and verify actual network behavior. When used carefully, NetworkPolicies provide an important layer of network isolation for Kubernetes applications.