What this check reads
It takes the running container ids from docker ps -q and then, for each one, asks docker inspect for the container name and for every host binding under .NetworkSettings.Ports, formatted as HostIp:HostPort. A binding whose host IP is 0.0.0.0, :: or empty counts as "all interfaces", and the first such binding on a container is kept as the evidence. One container with a binding like that is enough for the WARN, which prints each offender as container(ip:port); if no container has one, the check PASSes.
When it applies
Needs a reachable daemon and at least one running container; otherwise SKIP. It reads .NetworkSettings.Ports, so it sees only published ports - a container reachable solely on a user-defined Docker network, or one using --net=host (which publishes nothing but exposes everything), produces no finding. A host IP of 0.0.0.0, :: or empty counts as "all interfaces"; a binding to a specific address, including a private LAN address, passes. It reports the configured binding, not whether anything upstream filters the traffic.
What each result means
| Result | When | What it means |
|---|---|---|
| SKIP | No running containers | Nothing was inspected, so no conclusion was drawn about this host's exposure. A stopped container that would publish on all interfaces is not examined either. |
| WARN | Any container publishes on all interfaces | The named containers accept connections on every host interface, and Docker's rules run ahead of UFW, so those ports are reachable from outside even where the firewall says deny. |
| PASS | All published ports are bound to a specific IP (or none are published) | Every published port names a specific host address, or nothing is published at all. Traffic reaching a container through --net=host is invisible to this test. |
Why it matters
-p 8080:80 is -p 0.0.0.0:8080:80. Docker programs nat and FORWARD rules (DOCKER chain) that are evaluated before UFW/firewalld INPUT rules, so a published port is reachable from the internet even when ufw status says it is denied. Docker's own documentation describes this and recommends binding to a specific address, using the DOCKER-USER chain, or setting "ip": "127.0.0.1" in daemon.json. CIS Docker 5.13 "Ensure that incoming container traffic is bound to a specific host interface"; OWASP Docker Cheat Sheet Rule #5 (network).
Why it fails, and when it is wrong
- A reverse proxy (Traefik/nginx) intentionally publishes 80/443 on all interfaces; that is the correct design, but it still WARNs here. Everything else (databases, admin UIs, metrics) should be on
127.0.0.1or unpublished. - A cloud security group that blocks the port mitigates exposure; the check cannot see it. Confirm with an external scan.
- IPv6: Docker 27+ publishes on
::too when IPv6 is enabled on the bridge;ip6tablesrules are required for the same bypass behaviour. - Rootless Docker publishes through RootlessKit and the host firewall does apply in most configurations.
How to fix it
Find what publishes on all interfaces
docker ps --format '{{.Names}}\t{{.Ports}}' | grep -E '0\.0\.0\.0|:::'Anything listed here answers from outside, whatever ufw status says.
Bind to loopback, or stop publishing
ports:
- "127.0.0.1:5432:5432" # local onlyOr remove the mapping entirely and let the app reach the service over a user-defined network. A reverse proxy is the one service that should keep 80 and 443 on all interfaces.
Make loopback the daemon default
Add this to /etc/docker/daemon.json and restart Docker, so a bare -p 8080:80 added later binds to loopback rather than every interface.
{ "ip": "127.0.0.1" }Filter in DOCKER-USER for ports that stay public
Docker evaluates the DOCKER-USER chain before its own rules, so this is where host-level filtering for published ports belongs.
sudo iptables -I DOCKER-USER -i eth0 ! -s 203.0.113.0/24 -p tcp --dport 5432 -j DROPOr install ufw-docker, which wires UFW into that chain.
Recreate and test from outside
Port bindings are fixed when a container is created.
docker compose up -d --force-recreate
nmap -Pn -p 5432 <server-public-ip> # run from another machine; expected: filtered or closedVerify the fix
docker ps --format '{{.Names}}\t{{.Ports}}'
# expected: 127.0.0.1:8080->8080/tcp, not 0.0.0.0:8080->8080/tcp
docker inspect -f '{{.Name}}{{range $p, $c := .NetworkSettings.Ports}}{{range $c}} {{.HostIp}}:{{.HostPort}}{{end}}{{end}}' $(docker ps -q)
# What the host is really listening on, and who owns it:
sudo ss -tlnp | grep -i docker
# The question that actually matters - reachability from outside:
nmap -Pn -p 8080 <server-public-ip> # expected: filtered/closedDebugging
This one message covers two different cases: the audit could not reach the Docker daemon, or it reached it and nothing was running. To tell them apart, check systemctl is-active docker, then docker ps as the audit user and sudo docker ps, and run the audit as root or with sudo if only the second works. If both work and list nothing, there is genuinely nothing to evaluate, and a stopped container that would publish widely is not reported. The Docker Inventory check reports the unreachable case under its own message and walks through it in more detail.
This is the point of the check, not a false positive. Docker inserts its own rules in the DOCKER chain of nat/filter, which are evaluated before UFW's rules, so ufw deny 8080 does not stop a published container port. Confirm with sudo iptables -t nat -L DOCKER -n and test from outside with nmap.
Publish to loopback (-p 127.0.0.1:8080:8080, Compose ports: ["127.0.0.1:8080:8080"]) and put a reverse proxy in front, or install ufw-docker so UFW rules apply to Docker traffic. Recreate the container afterwards - port bindings are fixed at creation.
--net=host bypasses port publishing entirely and shows no ports here. Check with docker inspect -f '{{.HostConfig.NetworkMode}}' $(docker ps -q).
Then the host firewall must be Docker-aware. Verify the deny actually works from an external address before accepting the finding; a rule that Docker bypasses is worse than no rule, because it looks protective.
Sources
- Docker: Packet filtering and firewalls (Docker and ufw/firewalld, DOCKER-USER)
- Docker: Published ports (binding to a specific IP)
- Docker: dockerd
--ipdefault binding - chaifeng/ufw-docker
- Baeldung: Docker published port ignoring UFW rules
How the script reads this
Next
Re-run the Network & Supply Chain audit after applying the fix and confirm this check moves to PASS. CtrlOps runs all 5 checks over your existing SSH connection and scores the result, so the change is visible without reading another config file.
All 5 Network & Supply Chain fixes