kaibuildGitHub link: https://github.com/kaibuild/exposemap Why exposure is hard to reason about in...
GitHub link: https://github.com/kaibuild/exposemap
Self-hosted Docker Compose setups usually start simple. One app, one database, maybe a reverse proxy. Over time, the stack grows: admin panels, dashboards, caches, search backends, VPNs, tunnels, and old experiments that still have port mappings.
At some point, it becomes hard to answer a basic question: which services are reachable, and how?
Docker Compose makes this especially easy to lose track of because exposure can be implied by small details:
5432:5432 publishes PostgreSQL broadly unless host-level controls say otherwise.127.0.0.1:5432:5432 is very different.ports entry may still be routed by a reverse proxy.A common self-hosting mistake is leaving a database port mapped to the host:
services:
postgres:
image: postgres:16
ports:
- "5432:5432"
This does not automatically mean the database is reachable from the internet. Firewalls, cloud security groups, VPNs, and host configuration matter. But it is still a strong signal that the setup deserves review.
For many setups, the safer default is to remove the host port mapping or bind it to localhost:
ports:
- "127.0.0.1:5432:5432"
These two Compose snippets look similar, but they have very different intent:
ports:
- "8080:8080"
ports:
- "127.0.0.1:8080:8080"
The first publishes a host port broadly. The second binds to localhost. That distinction is easy to miss during reviews, especially in larger files.
Reverse proxies are useful, but the phrase "behind a reverse proxy" can hide a lot of assumptions.
Some services are routed through Traefik labels. Some are configured through Caddyfiles or Nginx files mounted into a container. Some use Nginx Proxy Manager state that is not visible in Compose. Some are exposed directly and proxied at the same time.
Compose alone cannot prove the real exposure path, but it can show useful hints and contradictions.
Admin tools often use ports such as 8080, 9090, or 3000. Those ports are not always dangerous, but they are worth checking when they are broadly published.
Examples include database UIs, monitoring dashboards, container dashboards, internal tools, and temporary debugging interfaces.
Even when the setup is correct, it helps to document why:
Small exposure maps reduce future confusion.
I built ExposeMap as a small open-source CLI for this first-pass review.
It scans a docker-compose.yml file and classifies services as:
It generates a Markdown report with:
It runs locally, does not modify Compose files, does not connect to containers, does not send reports anywhere, and does not perform real network scans.
That last point matters: ExposeMap is not a full security audit and does not prove internet exposure. It is a lightweight configuration review tool based on Compose heuristics.
The CLI is free and open source. The most useful feedback right now is about Compose patterns that should be classified more clearly, false positives, false negatives, and sanitized examples that show common self-hosting setups.
ExposeMap remains local, read-only, and Compose-based. It is separate from real external exposure testing.