When a web app works inside a Docker container but cannot be opened from your browser, the problem is often not Docker itself. It is the address the app listens on. In container work, localhost, 127.0.0.1, and 0.0.0.0 describe different network boundaries, and choosing the wrong one can make a published port look broken.
The short rule is simple: if a service inside a container must be reachable from outside that same container, make the app listen on 0.0.0.0, then publish the container port with Docker.
localhost inside a container
localhost usually points to 127.0.0.1, the loopback address of the current machine. Inside Docker, "current machine" means the container's own network namespace, not your Mac, Windows PC, or Linux host.
If your app listens on this address:
127.0.0.1:3000
then only processes inside the same container can connect to it. The host may still publish a port:
docker run -p 3000:3000 my-app
but the app is listening only on the container loopback interface, so Docker cannot forward outside traffic to the service in the way you expect.
This is why development servers often need a host flag:
npm run dev -- --host 0.0.0.0
or a framework-specific setting that makes the server listen on every interface.
What 0.0.0.0 means
0.0.0.0 is not a destination address you normally type into a browser. As a listen address, it means "bind to all available network interfaces."
Inside a container, that means the app accepts connections on the container's network interface, not only on loopback. Docker can then forward traffic from a published host port to the container port.
docker run --name web -p 8080:3000 my-app
If the app inside the container listens on 0.0.0.0:3000, you can usually open:
http://localhost:8080
from the host machine.
Port publishing is a second decision
Binding the application to 0.0.0.0 does not automatically expose it to the entire internet. Docker port publishing decides which host address and port are exposed.
This publishes the container to all host interfaces:
docker run -p 8080:3000 my-app
This publishes it only on the host loopback address:
docker run -p 127.0.0.1:8080:3000 my-app
That second form is often safer for local development. The app inside the container can still listen on 0.0.0.0, while Docker restricts host-side access to your own machine.
Quick comparison
| Address | Where it listens | Typical Docker result |
|---|---|---|
127.0.0.1 / localhost inside the container |
Container loopback only | Other containers and the host usually cannot reach the service |
0.0.0.0 inside the container |
All container interfaces | Docker can forward published host ports to the service |
127.0.0.1:8080:3000 in -p |
Host loopback only | Reachable from the host, not the external network |
8080:3000 in -p |
All host interfaces | Potentially reachable from other machines, depending on firewall and network |
Common examples
Node servers often use a host parameter:
server.listen(3000, "0.0.0.0")
Vite development servers can be started with:
vite --host 0.0.0.0
Next.js development servers can be bound similarly:
next dev -H 0.0.0.0
The exact command depends on the framework, but the network idea is the same.
A practical debugging checklist
Start from the container:
docker ps
docker logs web
docker exec -it web sh
Inside the container, check whether the app responds locally. Then check which address it listens on, using tools available in the image:
netstat -tulpn
or:
ss -tulpn
If you see only 127.0.0.1:3000, change the app's host setting. If you see 0.0.0.0:3000, inspect Docker port publishing:
docker port web
docker inspect web
Security note
Do not blindly publish every container with -p 8080:3000 on a public server. For local-only tools, prefer host loopback publishing:
docker run -p 127.0.0.1:8080:3000 my-app
For production, place the service behind a reverse proxy, firewall, load balancer, or orchestrator rule that matches the deployment model. Docker networking is only one layer of the exposure decision.
Related FreeMac guides
- For the everyday commands used in this article, read Docker CLI Commands: Containers, Images, Logs, and Cleanup.
- For image builds and
.dockerignore, continue with Dockerfile and .dockerignore for Node.js Apps. - If you are deploying behind a web server, the Chinese version currently has an Nginx reverse proxy guide.
Continue reading
Docker CLI Commands: Containers, Images, Logs, and Cleanup
A practical Docker command guide for checking containers, reading logs, inspecting configuration, entering running containers, managing volumes, and cleaning disk safely.
11 min readDockerfile and .dockerignore for Node.js Apps
Build cleaner Node.js Docker images by controlling the build context, using cache-friendly COPY order, applying multi-stage builds, and keeping secrets out of image layers.
9 min readLinux Commands for Files, Processes, Network, and Logs
A practical Linux command guide for developers: files, directories, logs, processes, network checks, SSH, curl, environment variables, and safer cleanup habits.
Subscribe to FreeMac
Weekly picks: free Mac software reviews, trusted source updates, alternatives, and low-friction guides.