Docker Compose network diagram, the short version: draw one zone per network, put each service inside the networks it joins, then add published ports as entry points from the host. Arrows come from real connections like env vars and app config, not from depends_on. That one swap is the difference between a startup-order chart and a topology you can review.
Every Compose stack starts cute. Two services, one database, life is good. Eighteen months later there are nine services, three override files, and a compose.yaml that only one person dares to touch.
So someone generates a diagram, it looks great, and it's quietly wrong. The arrows show who starts first, while the actual risks sit in lines nobody drew.
Here's how to map what's really there, using a stack we validated with Docker Compose v5.7.0.
What a Docker Compose network diagram should show
A Docker Compose network diagram answers one question: what can reach what? Not who boots first, and not which images you pulled.
Compose has several keys that look like they answer that. Only some of them do:
| Compose key | What it really tells you | How to draw it |
|---|---|---|
networks | Who can connect to whom | A zone around services |
ports | What's published on the host | An entry arrow from outside |
expose | A declared container port, not published | A label on the service, never an entry |
depends_on | Start and stop order | A dashed note, or leave it out |
environment | Real connection targets, like db:5432 | A solid arrow, once confirmed |
volumes | Shared or persistent state | A storage node |
profiles | Services that only run sometimes | A dotted box |
Solid arrows should mean "this connection happens". If an arrow means something else, label it or lose it.
→ Turn your compose file into a dependency map with the free Docker Compose Diagrammer
Docker Compose networks, explained in 90 seconds
If your file declares no networks, you already have one. Compose sets up a single network for your app, named <project-name>_default, and every service joins it. Each service registers its name with an internal DNS server, so web can reach postgres://db:5432 by name.
That default is convenient and flat. Every service can try to connect to every other service, so your diagram gets exactly one zone.
Declare networks and the picture changes. The Compose networks reference spells out the rule with a classic example: a proxy on frontend, a database on backend, and an app on both. The proxy and database are isolated because they share no network.
A few special cases deserve their own markings:
internal: truecreates an externally isolated network. Draw it with a thick border.external: truemeans the network's lifecycle is managed outside this application, and Compose errors if it doesn't exist. Draw it crossing the edge of your stack, because something else is on it.network_mode: hostshares the host's network stack. Port mapping doesn't apply and service-name DNS doesn't work, so the service sits outside your zones.
Those last two are where "isolated" stops being true without anybody noticing. Hunt for them first.
→ Try a shared network layout in the Docker Compose Diagrammer
Docker Compose networks example: a stack with two surprises
Here's a realistic stack. Read it and spot the problems before scrolling on:
services:
proxy:
image: nginx:1.27
ports:
- "80:80"
networks: [frontend]
depends_on: [api]
api:
build: ./api
expose:
- "8000"
environment:
DATABASE_URL: postgres://app:${DB_PASSWORD:?set DB_PASSWORD}@db:5432/app
REDIS_URL: redis://cache:6379
networks: [frontend, backend]
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
worker:
build: ./worker
environment:
DATABASE_URL: postgres://app:${DB_PASSWORD:?set DB_PASSWORD}@db:5432/app
REDIS_URL: redis://cache:6379
networks: [backend]
depends_on: [cache]
cache:
image: redis:7
networks: [backend]
adminer:
image: adminer:5
profiles: [debug]
ports:
- "8080:8080"
networks: [backend]
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_DB: app
POSTGRES_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 3s
retries: 5
networks: [backend]
networks:
frontend:
backend:
volumes:
pgdata:
The two-network split looks tidy. Now write the real topology as an edge list, which is the fastest draft of any diagram:
host --:80----> proxy published, intended entry point
host --:5432--> db published, REVIEW THIS
proxy --:8000--> api frontend (confirm in nginx.conf)
api --:5432--> db backend
api --:6379--> cache backend
worker --:5432--> db backend, not in depends_on
worker --:6379--> cache backend
Surprise one: worker uses the database, but depends_on never mentions it. A diagram built from depends_on draws no line there.
Surprise two: db lives on the "private" backend network and is also published on the host. That single ports entry walks around your careful segmentation.
| From | To | Shared network | Connects? | In depends_on? |
|---|---|---|---|---|
| proxy | api | frontend | Yes | Yes |
| api | db | backend | Yes | Yes |
| worker | cache | backend | Yes | Yes |
| worker | db | backend | Yes | No |
| proxy | db | None | No | No |
| host | db | Published port | Yes | No |
→ Threat-model this kind of exposure with the free STRIDE checklist
Docker Compose depends_on vs networks
This mix-up causes most bad Compose diagrams, so here it is plainly: depends_on is about time, and networks are about space.
Per Docker's startup order docs, Compose starts and stops containers in dependency order, and on startup it waits until a container is running, not until it's ready. If you need readiness, use condition: service_healthy with a healthcheck, as api does above. service_completed_successfully is for one-shot jobs such as migrations.
None of this creates or restricts a connection. Which leads to two failure modes:
- Missing arrows: services talk with no
depends_on, likeworkeranddb. - Phantom arrows:
depends_onexists for ordering, but there's no meaningful traffic, as with a migration job that finishes before the app starts.
Direction can trip you up too. Here proxy depends on api, and traffic happens to flow the same way. Don't count on that for every edge, because a dependency only tells you what must start first.
→ Show request order properly with a sequence diagram in Flow IQ
Docker Compose ports vs expose
ports publishes. expose declares. Only one of them puts your service on the host.
Docker's docs on publishing ports are blunt about it: a published port goes to all network interfaces by default, so any traffic reaching your machine can reach the app. The same page says to be mindful of publishing databases. Fair warning, and it applies directly to our db.
On a laptop, that port is handy for a local SQL client. On a shared VM or cloud host, it's a very different conversation. Two fixes:
# Option 1: keep it on this machine only
ports:
- "127.0.0.1:5432:5432"
# Option 2: remove ports entirely, services still connect via db:5432
Option 2 works because service-to-service traffic uses the container port. Your host port mapping never enters into it, which also means 8001:5432 on the host still means db:5432 inside the network. Label the container port on internal edges and the host port on entry arrows.
→ Run your design through Threat Analyzer before it reaches production
How to visualize Docker Compose services: 3 ways
Pick based on what you need next. Each option gives you a starting point, and the review layer stays yours.
| Option | Best for | Output | Watch out for |
|---|---|---|---|
docker compose config | Checking the effective model | YAML or JSON | Text only, and it resolves variables |
docker compose alpha viz | A quick graph in a terminal | Graphviz DOT | Experimental, arrows show dependencies |
| Docker Compose Diagrammer | Sharing a map in docs | Mermaid, Markdown, JSON | Manual layer needed for ports and app edges |
Start with config. It merges your files, resolves variables and expands shorthand into canonical form, so overrides stop hiding from you:
docker compose config --services
docker compose config --networks
docker compose --profile debug config --services
docker compose config --no-interpolate > effective.yaml
Profiles matter here. In our stack, adminer only shows up in that third command, so a default run makes it invisible. If it runs in staging, it belongs on the diagram.
Use --no-interpolate for anything you'll share. Plain config writes resolved values into the output, including passwords in connection strings.
Then generate a quick graph. Docker ships an experimental Graphviz generator:
docker compose alpha viz --networks --ports --image | dot -Tsvg > compose.svg
You'll need Graphviz installed and your required variables set. It lists networks and ports inside each node, which is great for inventory, while the arrows still come from dependencies. On our stack, worker to db doesn't appear. Experimental also means it can change or vanish between releases, so don't wire it into anything important.
Or skip the terminal. Paste your source compose.yaml into the Docker Compose Diagrammer and you get a Mermaid map of services, depends_on links, networks and volumes, plus exports for your docs. Use the source file with short-syntax volumes, not expanded config output. It parses what you paste, so it won't apply override files, and published ports and env-var connections still need adding by hand.
→ Generate your Mermaid topology with the Docker Compose Diagrammer
A 6-step checklist for your Compose diagram
Use this on any stack, from a side project to a pre-migration inventory:
- Validate the model. Run
docker compose config -qso you're drawing a file that parses. - List every profile and override. Note which combination you're drawing, in the diagram title.
- Draw network zones first. One box per network, and service boxes go inside. Services on two networks straddle the line.
- Add entry points. One arrow from the host for every entry in
ports, labelled with its bind address. - Add real connections. Search
environment, env files and app config for hostnames and ports. Confirm each one. - Mark the weird stuff.
external,internal,network_mode, shared volumes and anything else that breaks the simple story.
Then ask a reviewer one question: "Which of these arrows surprises you?" If one does, the diagram has already paid for itself.
Compose stacks are often the most honest record of an older system, so this pairs well with reverse engineering architecture from code when the docs have gone missing.
→ Document the request flows around this map in Flow IQ
From Compose to a production architecture diagram
A Compose file usually describes a stack on one Docker host. Your production design usually has more to say, so treat the Compose map as your current-state input, not your target.
| In Compose | Rough production equivalent | Question to answer |
|---|---|---|
| Network zones | Subnets, security groups or network policies | Which boundaries must be enforced? |
Published ports | Load balancer, ingress or gateway | What's public, and who terminates TLS? |
| Named volumes | Managed storage or persistent volumes | Who owns backups and recovery? |
healthcheck | Readiness and health probes | What should traffic wait for? |
depends_on | Application retries | What happens when a dependency disappears? |
If Kubernetes is next, the network policy explainer is a good bridge from Compose-style zones, and the Kubernetes YAML Visualizer gives you a system view of the manifests that follow.
For a cloud target, describe the stack and your constraints to Architect AI. It generates a structured topology that you can refine on a canvas and pass into cost, threat and documentation workflows.
→ Generate your target architecture from a prompt with Architect AI
Key takeaways
networksdecide reachability.depends_ondecides order, and it's not a topology.- Real connections live in config. Env vars and app config reveal edges Compose can't see.
portscan undo your segmentation. A published database is reachable from the host, whatever its network says.- Profiles and overrides hide services. Check them before you trust any generated map.
- Generated diagrams are a first draft. Add entry points, real connections and a human review.
→ Map your stack now with the free Docker Compose Diagrammer
FAQ
How do I create a network diagram from a Docker Compose file?
Run docker compose config to validate the file and see the model Compose will really use. Draw one zone per network, place each service inside the networks it joins, add published ports as entry points from the host, and add the real connections from environment variables and app config. Finally, flag anything published on the host and review the result.
Does depends_on mean two Docker Compose services can talk to each other?
No. depends_on controls start and stop order. Connectivity comes from sharing a network. A service can connect to another service with no depends_on entry at all if they share a network, and depends_on doesn't create a network path between services that don't.
Can Docker Compose services on different networks communicate?
Not directly. Docker's Compose reference describes a proxy being isolated from a database because the two have no network in common. A service attached to both networks can talk to each side, which is why it's the natural place for a gateway or API tier.
What is the difference between ports and expose in Docker Compose?
ports publishes a container port on the host, so traffic reaching the machine can reach the service. expose declares a container port without publishing it to the host. Traffic between services on a shared network uses the container port, so it doesn't need ports at all.
Is there a built-in Docker Compose visualizer?
There is an experimental one. docker compose alpha viz generates a Graphviz graph from your Compose file, with flags for networks, ports and images. It's marked experimental, so it may change or disappear between releases, and its arrows show dependencies rather than traffic.
→ Compare Architecto with Lucidchart for technical diagrams
Stop trusting the pretty version
Your Compose file already knows a lot about your system. The job is separating what it proves from what it merely implies, then drawing the rest on purpose.
Start with the free tool, spend ten minutes on the checklist, and take the result into your next review. When it's time to move from a single-host stack to a real cloud design, bring that context with you.
→ Start free and design your next architecture in Architecto
→ See how Architect AI turns a prompt into a reviewable topology