Docs & Code Guide

Docker Compose diagram: resolve the effective config, then keep the service map current

A compose stack is a graph pretending to be a list. Services, networks, volumes, and depends_on describe exactly the map a new teammate asks for on day one, and the file changes often enough that a hand-drawn version is stale within a sprint. The loop that holds: resolve the effective config with docker compose config, generate the diagram from that output, and rerun the generation whenever a compose file changes.

7 min readFor teams whose dev and small-production stacks are defined in compose files

See it as a diagram

Everything below, as a diagram you can edit. Describe yours and see it in seconds.

204/20003 credits left
Try:

No account needed · Editable canvas, not a picture

Diagram what Compose runs, not the file you opened

The compose.yaml at the repo root is rarely the whole story. Compose auto-merges a compose.override.yaml sitting next to it, applies every -f file in the order given, interpolates variables from the shell and the .env file, and silently drops any service whose profiles list does not match an active profile. Two engineers running what they both call the same file, with different COMPOSE_PROFILES set, are running different graphs.

docker compose config collapses all of that into one canonical document: merges applied, variables interpolated, profile-gated services filtered in or out, defaults made explicit. That output is the thing worth diagramming, and it is also the best input for generation, because nothing in it depends on who ran it or what happened to be in their shell.

# Resolve merges, profiles, and env into one effective file
docker compose -f compose.yaml -f compose.override.yaml \
  --profile jobs config > compose.effective.yaml

# Quick sanity checks on what the diagram will contain
docker compose config --services
docker compose config --format json | jq '.services | keys'

What becomes a node, a zone, an edge

Services are the nodes, with one annotation worth keeping: whether the service is built from this repo (build:) or pulled as an image (image:). That single distinction separates your code from your dependencies at a glance.

Networks are the zones. A service attached to both frontend and backend networks sits on the boundary between them, which is usually the interesting fact about it: compose networks are the closest thing the file has to trust boundaries. Named volumes become storage nodes, and a volume mounted by two services is a real coupling edge that most hand-drawn diagrams forget.

depends_on gives you the startup-order edges, and the condition: service_healthy form documents an actual runtime contract rather than a hope. Published ports mark the entry points from the outside world. Together those four constructs carry almost everything a reader wants from a stack map.

Override files and profiles multiply the graphs

Most real projects run a chain: compose.yaml as the base, compose.override.yaml for dev conveniences, a compose.prod.yaml swapping bind mounts for named volumes and adding replicas. Each -f chain is its own effective graph, so decide which environments deserve a diagram. A workable default is to diagram the production chain and annotate dev-only services, and split into one diagram per environment only when the graphs genuinely diverge.

Profiles cut the other way: a jobs or debug profile gates one-off containers that exist in the file but not in a normal boot. Resolving with and without --profile shows you exactly which nodes are conditional, and the diagram can mark them as such instead of pretending they are always running.

The loop that keeps the map current

For the first diagram, paste the resolved output into the Docker Compose diagram generator and edit from there. This page is about every diagram after that one.

Datadef ships an MCP server, registry name io.datadef/mcp, so an agent like Claude Code or Cursor connected to it can rerun docker compose config, read the effective file, and update the existing diagram in place. To be precise about what this is not: Datadef does not watch your repository and nothing detects the change by itself. The redraw is one agent call or one CI step, triggered by you. In CI the honest trigger is a paths filter on the compose files themselves, so the job runs exactly when the stack definition changes and never otherwise. Connecting an agent needs an API key, available on paid plans.

Trigger paths worth listing

compose.yaml, compose.*.yaml, docker-compose*.yml, and .env.example if your interpolation defaults live there. A change to any of these is a change to the graph.

Where the Compose view stops

Compose only knows about containers. The managed Postgres your api actually talks to in production appears in the file as a DATABASE_URL variable, not as a service, so a diagram generated from compose config alone will miss it. An agent reading the repo can add those externals from env vars and client code, but the compose file cannot be the sole source for a production map.

If the diagram is embedded in a README or wiki through the live image URL, the project has to be shared public: flip it private and the embed returns a 404. And the scope is the diagram, not the prose. Datadef keeps the picture current; the paragraphs around it are still yours to edit.

FAQ

How do I generate a diagram from a docker-compose file?

Resolve the effective configuration first with docker compose config, which applies file merges, profiles, and env interpolation, then generate from that output rather than the raw YAML. Datadef accepts the resolved file as a paste and returns an editable service map with services as nodes, networks as zones, and depends_on as edges.

Why diagram docker compose config output instead of compose.yaml?

Because compose.yaml is only one input. Compose auto-merges compose.override.yaml, applies every -f file in order, fills in variables from .env and the shell, and drops services gated behind inactive profiles. The config subcommand prints the single effective file that actually runs, which is the only version two engineers will agree on.

Does the diagram update automatically when compose.yaml changes?

Not by itself: Datadef does not watch repositories. The update is one command away instead: a CI job with a paths filter on the compose files, or an MCP-connected agent asked to redraw after a merge, regenerates the diagram. Anywhere the diagram is embedded then shows the new version within minutes.

How should depends_on and networks appear in a service diagram?

depends_on becomes directed startup-order edges, with condition: service_healthy worth annotating because it is a real runtime contract. Networks work better as zones than as nodes: draw each network as a boundary and place services inside it, so a service on two networks visibly sits on the boundary.

Can I keep separate diagrams for dev and production compose files?

Yes, and the clean way is to resolve each -f chain separately: docker compose -f compose.yaml -f compose.prod.yaml config gives the production graph, the default chain gives dev. If the two only differ in mounts and replicas, one diagram with annotations is less maintenance than two.