Docker

Docker Port Exposed But Not Accessible From Host

A port that shows up in docker ps but doesn't respond when you try to connect from the host almost always means the application inside the container is bound to the wrong network interface, or the port mapping itself was specified incorrectly — not that Docker's networking is broken.

The Problem

The container appears to be running with the expected port mapping:

docker ps
CONTAINER ID   PORTS
a1b2c3d4e5f6   0.0.0.0:3000->3000/tcp

But trying to connect from the host fails:

curl http://localhost:3000
curl: (7) Failed to connect to localhost port 3000: Connection refused
Binding matters inside the container App binds to 127.0.0.1:3000 unreachable from outside App binds to 0.0.0.0:3000 reachable via port mapping

Why It Happens

Docker's port mapping (the -p flag or ports: in Compose) forwards traffic to the container's network interface, but this only works if the application inside is actually listening on an interface that traffic can reach. Common causes:

  • The application inside the container binds specifically to 127.0.0.1 (localhost) rather than 0.0.0.0127.0.0.1 inside a container refers only to the container's own internal loopback, which Docker's port forwarding can't reach from outside, even with a correct port mapping in place
  • The port mapping direction was specified backwards, or the container's internal port doesn't actually match what's in the mapping (a common typo: -p 3000:8080 when the app inside actually listens on 3000, not 8080)
  • The application crashed shortly after starting, so nothing is actually listening on the port anymore, even though the container itself still shows as running
  • A host-level firewall is blocking the mapped port, independent of anything Docker itself is doing correctly

The Fix

First, confirm the application inside the container is actually listening on 0.0.0.0, not 127.0.0.1. Check directly from inside the container:

docker exec -it your-container netstat -tlnp

If the output shows something like 127.0.0.1:3000 rather than 0.0.0.0:3000, that's the root cause — the fix is in your application's own configuration, telling it to bind to all interfaces rather than just loopback:

# Example: Node.js Express app
app.listen(3000, '0.0.0.0'); // not app.listen(3000) which often defaults to localhost only

Different frameworks and languages have their own specific way of setting this — the key requirement is the same everywhere: the process must bind to 0.0.0.0 (or an equivalent "all interfaces" setting) for Docker's port forwarding to have anything reachable to forward traffic to.

Confirm the port mapping itself is correct, matching container-internal-port to host-port in the right order:

docker run -p 3000:3000 your-image
# format is: -p HOST_PORT:CONTAINER_PORT

Confirm the application is actually still running and hasn't crashed silently after container startup, by checking logs directly:

docker logs your-container

A container can remain in "running" status even if the main process inside crashed and something else (a shell, a supervisor process) keeps it technically alive — checking logs reveals whether the actual application is genuinely still up and listening.

Rule out a host-level firewall blocking the port, particularly relevant on cloud VM instances with their own separate security group or firewall configuration layered on top of the OS firewall:

sudo ufw status
sudo iptables -L -n

Still Not Working?

If binding, mapping, and firewall all check out but the connection still fails, test connectivity directly from inside the container first, bypassing the host-level port mapping entirely, to isolate whether the application itself is the problem or the mapping layer:

docker exec -it your-container curl http://localhost:3000

If this succeeds from inside the container but the same request fails from the host, the issue is specifically in the port mapping or host-level network path, not the application itself — pointing you back toward re-checking the exact -p flag syntax and any intermediate firewall rules, rather than further investigating the application's own configuration.

It's also worth checking whether another process on the host is already bound to the same host-side port, silently preventing Docker's own port forwarding from working as expected. Confirm nothing else on the host is already listening on the port you're trying to map to:

sudo lsof -i :3000

If this shows a non-Docker process already bound to that port, Docker's port mapping can conflict with it in ways that produce inconsistent or confusing connection behavior, rather than a clear, immediate error at container startup.

Finally, if you're testing from a different machine on the same network rather than the host itself, confirm the host's own network interface is actually reachable and that you're not being blocked by a router-level or cloud provider's separate network security configuration, which exists independently of both Docker's port mapping and the host's own local firewall rules covered earlier.