<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: kaibuild</title>
    <description>The latest articles on DEV Community by kaibuild (@_75d19caa71fa22c06b8).</description>
    <link>https://dev.to/_75d19caa71fa22c06b8</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3963079%2F7b040520-3bf4-45e5-9b4f-4764c37a648e.png</url>
      <title>DEV Community: kaibuild</title>
      <link>https://dev.to/_75d19caa71fa22c06b8</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_75d19caa71fa22c06b8"/>
    <language>en</language>
    <item>
      <title>How to see what your self-hosted Docker Compose stack exposes</title>
      <dc:creator>kaibuild</dc:creator>
      <pubDate>Mon, 01 Jun 2026 16:49:17 +0000</pubDate>
      <link>https://dev.to/_75d19caa71fa22c06b8/how-to-see-what-your-self-hosted-docker-compose-stack-exposes-36g9</link>
      <guid>https://dev.to/_75d19caa71fa22c06b8/how-to-see-what-your-self-hosted-docker-compose-stack-exposes-36g9</guid>
      <description>&lt;p&gt;GitHub link: &lt;a href="https://github.com/kaibuild/exposemap" rel="noopener noreferrer"&gt;https://github.com/kaibuild/exposemap&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why exposure is hard to reason about in self-hosted stacks
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;At some point, it becomes hard to answer a basic question: which services are reachable, and how?&lt;/p&gt;

&lt;p&gt;Docker Compose makes this especially easy to lose track of because exposure can be implied by small details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;5432:5432&lt;/code&gt; publishes PostgreSQL broadly unless host-level controls say otherwise.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;127.0.0.1:5432:5432&lt;/code&gt; is very different.&lt;/li&gt;
&lt;li&gt;A service with no &lt;code&gt;ports&lt;/code&gt; entry may still be routed by a reverse proxy.&lt;/li&gt;
&lt;li&gt;A reverse proxy may expose only intended apps, or it may hide complexity in labels, mounted config, or external state.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Mistake 1: Directly exposing databases
&lt;/h2&gt;

&lt;p&gt;A common self-hosting mistake is leaving a database port mapped to the host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432:5432"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;For many setups, the safer default is to remove the host port mapping or bind it to localhost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:5432:5432"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Mistake 2: Confusing localhost-only with public bindings
&lt;/h2&gt;

&lt;p&gt;These two Compose snippets look similar, but they have very different intent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:8080:8080"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first publishes a host port broadly. The second binds to localhost. That distinction is easy to miss during reviews, especially in larger files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake 3: Assuming reverse proxy means everything is safe
&lt;/h2&gt;

&lt;p&gt;Reverse proxies are useful, but the phrase "behind a reverse proxy" can hide a lot of assumptions.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Compose alone cannot prove the real exposure path, but it can show useful hints and contradictions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake 4: Losing track of admin panels
&lt;/h2&gt;

&lt;p&gt;Admin tools often use ports such as &lt;code&gt;8080&lt;/code&gt;, &lt;code&gt;9090&lt;/code&gt;, or &lt;code&gt;3000&lt;/code&gt;. Those ports are not always dangerous, but they are worth checking when they are broadly published.&lt;/p&gt;

&lt;p&gt;Examples include database UIs, monitoring dashboards, container dashboards, internal tools, and temporary debugging interfaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake 5: Not documenting exposure paths
&lt;/h2&gt;

&lt;p&gt;Even when the setup is correct, it helps to document why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which service is intentionally public?&lt;/li&gt;
&lt;li&gt;Which service is only reachable through localhost?&lt;/li&gt;
&lt;li&gt;Which service is reachable through a VPN?&lt;/li&gt;
&lt;li&gt;Which service is routed through a reverse proxy?&lt;/li&gt;
&lt;li&gt;Which services should never be directly exposed?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small exposure maps reduce future confusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built ExposeMap
&lt;/h2&gt;

&lt;p&gt;I built ExposeMap as a small open-source CLI for this first-pass review.&lt;/p&gt;

&lt;p&gt;It scans a &lt;code&gt;docker-compose.yml&lt;/code&gt; file and classifies services as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;internal&lt;/li&gt;
&lt;li&gt;localhost-only&lt;/li&gt;
&lt;li&gt;directly exposed&lt;/li&gt;
&lt;li&gt;reverse-proxy exposed&lt;/li&gt;
&lt;li&gt;unknown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It generates a Markdown report with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a summary table&lt;/li&gt;
&lt;li&gt;high-risk findings&lt;/li&gt;
&lt;li&gt;service details&lt;/li&gt;
&lt;li&gt;a Mermaid diagram&lt;/li&gt;
&lt;li&gt;limitations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful feedback
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;ExposeMap remains local, read-only, and Compose-based. It is separate from real external exposure testing.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/kaibuild/exposemap" rel="noopener noreferrer"&gt;https://github.com/kaibuild/exposemap&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>opensource</category>
      <category>cli</category>
    </item>
  </channel>
</rss>
