by Guillermo Quiros
A complete C4 deployment diagram for a production Kubernetes cluster, built element by element so you can adapt it to your own infrastructure.
This article is part of the C4 model hub. If you are new to deployment diagrams, start with the C4 deployment diagram guide first.
What We Are Modelling
The diagram covers a typical production Kubernetes setup that most backend teams will recognise:
- A cloud load balancer terminating TLS at the edge
- An ingress controller (NGINX) routing traffic into the cluster
- Three namespaces inside the cluster:
app,data, andmonitoring - Stateful data workloads: PostgreSQL (primary + read replica) and Redis
- External cloud services: Container Registry, Secrets Manager, Object Storage
- An observability stack: Prometheus scraping metrics, Grafana for dashboards
This is not a toy example. It is the kind of cluster a team of ten to forty engineers would run in production for a SaaS product.
Why Start With Namespaces
In a Kubernetes cluster, namespaces are the primary way to express boundaries. In the C4 deployment diagram, they map directly to Groups — containers that hold other elements and make the hierarchy readable.
The diagram uses four groups inside the cluster:
| Namespace | Purpose |
|---|---|
ingress-nginx | Hosts the Ingress Controller. Separate so ingress RBAC stays isolated from app workloads. |
app | All application workloads: frontend, API, and background worker Deployments. |
data | Stateful workloads only: PostgreSQL StatefulSet and Redis StatefulSet. Separate so network policies can restrict data access to app namespace only. |
monitoring | Prometheus and Grafana. Separate so observability tooling cannot accidentally write to the data namespace. |
Keeping these four namespaces in the diagram is enough to explain the security and network policy boundaries to a new engineer on the first day. You do not need to show every Kubernetes object — that is what kubectl is for.
The Elements, Layer by Layer
Internet Zone
The diagram opens outside the cluster with two external elements:
End Users (Actor) — the people using the product via web or mobile. Placing them in the diagram makes it immediately clear that everything drawn between them and the database represents the full request path.
CDN (External System, CloudFlare) — sits between users and the load balancer. Handles DDoS protection, TLS offloading at the edge, and global caching for static assets. In many teams this is invisible in architecture diagrams because it is "just DNS", but it is a meaningful hop in the deployment topology that affects latency, error handling, and incident response.
Cloud Provider Zone
Cloud Load Balancer (AWS ALB) — the first thing inside the cloud provider boundary. This is where TLS is terminated if it is not already terminated at the CDN. It routes to the Ingress Controller inside the cluster.
Three external cloud services also live in this zone:
- Container Registry (AWS ECR) — every node in the cluster pulls images from here at pod startup. Including it makes image pull dependencies visible.
- Secrets Manager (AWS Secrets Manager) — the API pod fetches database credentials and API keys at startup. Not showing this hides a critical dependency that every operations engineer needs to know about.
- Object Storage (AWS S3) — the API writes user-uploaded files here. Again, a hidden dependency that shows up in incident postmortems if it goes down.
Ingress Namespace
NGINX Ingress Controller — the single element in the ingress-nginx namespace. Its job is to read Ingress resources and route traffic to Services:
/*routes to the frontend Service/api/*routes to the API Service
Separating the Ingress Controller into its own namespace is a common production pattern. It lets you apply different network policies and resource quotas to ingress traffic without affecting application workloads.
App Namespace
Three Deployments live here. Each is modelled as a Group (representing the Deployment) containing a single Application element (representing a pod, as a type representative):
frontend Deployment (3 replicas, React + Nginx) Serves the compiled React SPA via an Nginx sidecar. Stateless — can scale horizontally without coordination. Talks to the API over REST.
api Deployment (3 replicas, Node.js + Express) The main application server. Handles all business logic, talks to PostgreSQL for persistence, Redis for caching, S3 for file storage, and Secrets Manager at startup. This is the highest-fan-out element in the diagram — its relationships reveal most of the system's dependencies at a glance.
worker Deployment (2 replicas, Node.js + BullMQ) Processes background jobs from the Redis queue: email delivery, report exports, webhook dispatches. Writes results to PostgreSQL and dequeues from Redis. Keeping this separate from the API makes it clear that background processing can scale and fail independently of request handling.
Data Namespace
PostgreSQL StatefulSet — modelled as a Group containing two Store elements:
- Primary — handles all writes and strong-consistency reads
- Replica — receives streaming replication from the primary and serves read-only analytics queries. The replication relationship is drawn as an explicit line, which makes failover scenarios easier to reason about in incident postmortems.
Redis StatefulSet — a single Store element with AOF persistence. Serves two purposes: session cache for the API, and job queue for the worker. These two uses are documented in the element description rather than splitting Redis into two boxes — at this zoom level, one box is the right level of detail.
Monitoring Namespace
Prometheus scrapes /metrics endpoints on the API pods, worker pods, and frontend pods every 15 seconds. The scrape relationships are shown explicitly in the diagram because they cross namespace boundaries and require ServiceMonitor resources and appropriate network policies.
Grafana queries Prometheus over PromQL and renders the dashboards the on-call team checks during incidents. Showing the Grafana → Prometheus relationship makes it clear that Grafana has no direct access to the data namespace — it only sees aggregated metrics.
The Relationships That Matter Most
Not every relationship needs to be on the diagram, but some are load-bearing:
| Relationship | Why it matters |
|---|---|
| CDN → Load Balancer | Origin pull — if the CDN goes down, users hit the LB directly and may overwhelm it |
| Ingress → API (route /api/*) | Path-based routing — a misconfigured Ingress rule takes down the API while the frontend stays up |
| API → Secrets Manager | Startup dependency — if Secrets Manager is unavailable, pods cannot start after a rolling deploy |
| API → PostgreSQL Primary | Write path — single point of failure if the primary goes down before a replica is promoted |
| PostgreSQL Primary → Replica | Replication lag — under heavy write load, replicas fall behind and read queries return stale data |
| Registry → API, Frontend, Worker | Image pull — a registry outage blocks new pod scheduling, which blocks deploys and auto-scaling |
What This Diagram Does Not Show
A C4 deployment diagram operates at the infrastructure topology level. The following are intentionally left out:
- Individual replica pods — showing all three API replicas as separate boxes adds noise without adding architectural insight. The Deployment Group implies multiple replicas.
- Services and ConfigMaps — Kubernetes internal objects that implement wiring, not topology.
- HPA and PodDisruptionBudgets — operational constraints, not structural dependencies.
- CI/CD pipeline — belongs in a separate diagram (dynamic or context level) that shows how code moves from repository to cluster.
- Specific port numbers for every relationship — documented on the relationships where they matter (TCP 5432, TCP 6379) and omitted where they do not add information.
How to Adapt This to Your Stack
The structure generalises across cloud providers and technology choices. The mapping is:
| This diagram | Your stack |
|---|---|
| AWS ALB | GCP Cloud Load Balancing, Azure Application Gateway |
| AWS ECR | Docker Hub, GCR, GHCR |
| AWS Secrets Manager | HashiCorp Vault, GCP Secret Manager, Azure Key Vault |
| AWS S3 | GCS, Azure Blob Storage, MinIO |
| NGINX Ingress | Traefik, HAProxy Ingress, Istio Gateway |
| PostgreSQL | MySQL, CockroachDB, Cloud Spanner |
| Redis | Valkey, Memcached, DragonflyDB |
| Prometheus + Grafana | Datadog, New Relic, Victoria Metrics |
The namespace structure and relationship patterns transfer directly. Swap the technology labels and the diagram describes your cluster just as accurately.
Frequently Asked Questions
Should I show one pod or all replicas in a Deployment?
Show one — labelled to indicate the replica count in the description or element name. Three identical API Pod boxes add no information and make the diagram harder to read. The Deployment Group signals that multiple instances exist.
Where does the CI/CD pipeline go?
Not on a deployment diagram. The deployment diagram shows the running topology, not how code gets there. Draw a separate dynamic diagram for the deploy flow if your team needs to reason about it explicitly.
Should the data namespace be inside or outside the cluster group?
Inside. The data namespace is part of the cluster. External managed databases (RDS, Cloud SQL) would be drawn outside the cluster group as external systems, because they are not Kubernetes workloads.
How do I show network policies in the diagram?
Group boundaries imply the policy boundaries. If you want to make policies explicit, add a short note to each namespace Group description (e.g., "Network policy: only accepts traffic from app namespace"). Full network policy documentation belongs in infrastructure-as-code, not in the C4 diagram.
When should I add a component diagram below this?
For most Kubernetes deployments, the deployment diagram is the deepest level you need. A component diagram inside the API container would only be worth maintaining if the internal structure of the API pod is a frequent source of confusion or if the codebase is large enough that different teams own different internal components.