Every Kubernetes operator has felt this moment: you're cleaning up a namespace, your cursor is hovering over kubectl delete secret legacy-db-credentials, and a quiet voice asks — is anything still using this?
The honest answer, with stock tooling, is "I don't know." This article works backward from that question: what would you need to answer it reliably, why kubectl alone can't, and how to get a real answer in minutes with an open-source dependency graph.
Why kubectl can't answer the question
Kubernetes stores resources as independent objects. The relationships between them — a Deployment mounting a ConfigMap, an Ingress routing to a Service, a RoleBinding granting a ServiceAccount access — exist only as references inside spec fields. There is no reverse index. You can ask "what does this Deployment mount?" by reading its spec, but you cannot ask the reverse: "which Deployments mount this ConfigMap?"
The usual workaround is a chain of greps:
kubectl get deployments -o yaml -A | grep -B 20 "legacy-db-credentials"
This misses entire categories of dependents:
- Indirect references — an Ingress with a TLS section naming the Secret, a ServiceAccount with an imagePullSecret, a CSI volume.
- CRD-mediated references — cert-manager Certificates, ExternalSecrets, Istio/Gateway API resources that point at Secrets in their own schemas.
- Transitive impact — the Deployment that mounts the Secret feeds a Service, which feeds an Ingress. Deleting the Secret eventually breaks the public endpoint, two hops away.
The hidden factor most people overlook: the dangerous dependencies are exactly the ones that don't look like dependencies. RBAC bindings and platform-identity links (a ServiceAccount bound to an AWS IAM role via IRSA, for example) almost never show up in a grep, but they're load-bearing.
Working backward: what a real answer looks like
The end goal is a single query: given resource X, show me everything downstream of it, with the paths. That requires three things:
- A complete inventory of resources, including CRDs — not just the built-in kinds.
- Typed edges between them: mounts, routes-to, binds, scales, owns. The edge type matters because "breaks immediately" (a mounted Secret) and "breaks on next rollout" (an imagePullSecret) are different risks.
- A graph you can traverse in both directions, so "what depends on X" is as cheap as "what does X depend on".
This is precisely what KubeAtlas builds: a directed dependency graph of every resource in the cluster — Deployments, ConfigMaps, Services, Ingresses, Gateways, HTTPRoutes, PVCs, RBAC, and CRDs — that you can query through a web UI, an API, or straight from kubectl.
Step 1 — Install KubeAtlas
The fastest path is the in-memory Helm install (no persistence, one binary):
helm install kubeatlas oci://ghcr.io/lithastra/charts/kubeatlas \
--version 1.3.1 \
--namespace kubeatlas --create-namespace
kubectl -n kubeatlas rollout status deploy/kubeatlas
kubectl -n kubeatlas port-forward svc/kubeatlas 8080:80
Then open http://localhost:8080. If you'd rather not install anything in the cluster at all, skip to the kubectl atlas option below — it builds the graph client-side.
Security note: KubeAtlas reads every namespace, ConfigMap, and RBAC binding in your cluster, and the default install is deliberately ClusterIP-only with no built-in authentication. Put an auth layer (oauth2-proxy, Pomerium, Cloudflare Access) in front before exposing it. See the security warning in the docs.
Step 2 — Run a blast-radius query
In the web UI, find the Secret (full-text search is ranked across names, kinds, namespaces, and labels), select it, and switch on blast-radius mode. The canvas highlights everything downstream, with two controls that matter:
- Depth — direct dependents only, or the full transitive closure. Depth 1 answers "what mounts this?"; full depth answers "what user-facing thing eventually breaks?"
- Direction — downstream (what depends on me) for deletions; upstream (what I depend on) for debugging an already-broken workload.
Edge types are filterable (RBAC / Network / Config / Storage), so you can ask narrower questions like "what does this Secret affect through configuration mounts only?"
Step 3 — Or do it from the terminal
The kubectl atlas plugin renders the same graph without any in-cluster server. It talks to the Kubernetes API directly:
# one resource and its neighborhood
kubectl atlas deployment api -n petclinic
# a whole namespace
kubectl atlas namespace petclinic
# interactive UI, served from an in-process server
kubectl atlas namespace petclinic --local-ui
The default mode writes a static SVG (it needs the graphviz dot binary); --local-ui opens the full interactive UI with no graphviz and no in-cluster components. Install:
curl -L https://github.com/lithastra/kubeatlas/releases/latest/download/kubectl-atlas_$(uname -s)_$(uname -m).tar.gz \
| tar -xz kubectl-atlas && sudo install kubectl-atlas /usr/local/bin/
Step 4 — Make it a habit, not a heroic effort
Blast-radius checks are most valuable when they happen before every risky change, not just during postmortems. Two ways to systematize:
- In CI — the kubeatlas-action GitHub Action renders the dependency graph as a CI artifact on every PR, so reviewers see the topology a change lands in.
- Over time — with the persistent (PostgreSQL) tier, KubeAtlas records every resource change. The time-axis diff answers "what changed in the last hour?" — the first question of every incident call.
The owl's-eye summary
| Question | Stock kubectl | Dependency graph |
|---|---|---|
| What does X reference? | Read the spec, manually | One query, typed edges |
| What references X? | Grep and hope | One query, reverse edges |
| What breaks transitively? | Not answerable | Blast-radius mode, any depth |
| What changed recently? | Not answerable | Time-axis diff (1h/4h/24h/7d) |
The pre-deletion pause shouldn't be a moment of dread. With the graph in place, "if I delete this Secret, what breaks?" becomes a question with an answer — usually in under a minute.