Restrict Django Pod Traffic With Kubernetes NetworkPolicies

Lock down network access for Django on Kubernetes: default-deny ingress and egress, allow the ingress controller to reach web pods, let web and Celery reach PostgreSQL, Redis, and DNS, permit required external APIs, and test policies without breaking the app.

Problem statement

By default, every pod in a Kubernetes cluster can talk to every other pod and to the internet. If an attacker gets code execution in your Django pod through a vulnerable dependency, they can reach the database directly, scan other namespaces, and send data anywhere. NetworkPolicies let you declare which traffic each pod may send and receive, so a compromised pod can reach only what Django legitimately needs.

NetworkPolicies for DjangoDefault denyingress + egressAllow ingressfrom gateway onlyAllow egressDB, Redis, DNSAllow externalspecific APIsStart from deny-all and open only the paths Django actually uses.
  1. Confirm your CNI enforces NetworkPolicies (Calico, Cilium, and most managed clusters' policy options do).
  2. Apply default-deny ingress and egress policies to the app namespace.
  3. Allow ingress to web pods only from the ingress controller or Gateway namespace, on the app port.
  4. Allow egress from web and Celery pods to PostgreSQL, Redis, and DNS (port 53 to kube-dns).
  5. Allow egress to required external services (email API, payment provider, Sentry) by CIDR or with FQDN policies if your CNI supports them.

Step-by-step solution

1. Default deny

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

With this alone, nothing in the namespace can send or receive traffic, including DNS. Apply it together with the allow rules below, or in a staging namespace first.

2. Allow DNS

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: allow-dns, namespace: shop }
spec:
  podSelector: {}
  policyTypes: ["Egress"]
  egress:
    - to:
        - namespaceSelector: { matchLabels: { kubernetes.io/metadata.name: kube-system } }
          podSelector: { matchLabels: { k8s-app: kube-dns } }
      ports:
        - { protocol: UDP, port: 53 }
        - { protocol: TCP, port: 53 }

Forgetting DNS is the most common reason a new policy set breaks everything with "Temporary failure in name resolution".

source, destination, portPortSelectorGateway to web8000gateway namespaceweb, celery to DNS53 UDP/TCPkube-dns podsweb, celery to Postgres5432db pods or CIDRweb, celery to Redis6379redis pods or CIDRweb to external APIs443CIDR or FQDN policyEverything not listed is denieddjango-deployment.com
Managed databases outside the cluster are allowed by CIDR.

3. Allow ingress from the gateway

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: web-ingress, namespace: shop }
spec:
  podSelector: { matchLabels: { app: web } }
  policyTypes: ["Ingress"]
  ingress:
    - from:
        - namespaceSelector: { matchLabels: { kubernetes.io/metadata.name: gateway } }
      ports: [{ protocol: TCP, port: 8000 }]

Kubelet health probes come from the node and are generally not subject to NetworkPolicies in most CNIs, but verify in your cluster.

4. Allow egress to data stores

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: app-egress-data, namespace: shop }
spec:
  podSelector: { matchExpressions: [{ key: app, operator: In, values: [web, celery] }] }
  policyTypes: ["Egress"]
  egress:
    - to: [{ ipBlock: { cidr: 10.20.0.0/24 } }]       # managed PostgreSQL subnet
      ports: [{ protocol: TCP, port: 5432 }]
    - to: [{ podSelector: { matchLabels: { app: redis } } }]
      ports: [{ protocol: TCP, port: 6379 }]

5. External APIs and testing

Standard NetworkPolicies only match IPs and CIDRs, which is awkward for SaaS APIs behind changing addresses. Options: route outbound traffic through an egress proxy and allow only the proxy, or use CNI features like Cilium's toFQDNs. Test in staging with each policy added in turn: run the test suite, trigger emails and payments, and watch for timeouts. Tools such as kubectl exec ... -- python -c "import socket; socket.create_connection(('db', 5432), 3)" check specific paths quickly.

safe rollout order1Map real trafficflow logs or Hubble2Write allow rulesDNS, DB, Redis, APIs3Apply in stagingwith default deny4Run full test passemails, payments, jobs5Apply to productionmonitor dropsAllow rules first, default deny last, in staging before productiondjango-deployment.com
Cilium Hubble or Calico flow logs show exactly which connections are denied.

Explanation

NetworkPolicies are additive allow lists evaluated by the cluster's CNI plugin. Once any policy selects a pod for a direction (ingress or egress), only traffic explicitly allowed by some policy is permitted in that direction. A default-deny policy that selects all pods turns the namespace into a closed system, and specific policies open the paths Django needs. This limits lateral movement after a compromise and documents dependencies as code.

Edge cases / notes

  • CNI support. Some basic network plugins ignore NetworkPolicies entirely; policies then appear to apply but do nothing.
  • Migrations and jobs. Job pods need the same egress as web pods; label them so the policies select them.
  • Metrics and logs. Allow ingress from Prometheus for metrics endpoints and egress to log collectors if they are network-based.

Next steps

FAQ

Are NetworkPolicies enabled by default?

Pods are open by default. Policies only take effect when your CNI supports them and you create them.

Why did everything break after adding default deny?

DNS egress was not allowed. Add a policy allowing port 53 to kube-dns.

How do I allow access to external APIs?

By CIDR with standard policies, through an egress proxy, or with FQDN rules if your CNI supports them.

Do health probes need a NetworkPolicy rule?

Usually not, since kubelet probes come from the node, but confirm with your CNI.

Learning path · step 11 of 16

Django on Kubernetes

Keep going