If you publish a Docker port as 8080:80, Docker makes that service reachable on the host’s network interfaces by default. If you publish it as 127.0.0.1:8080:80, the service is bound to localhost on the host and is intended for local access or a reverse proxy on the same machine.
For a small VPS, this difference matters. 0.0.0.0 can mean “reachable from the internet if the network path allows it.” 127.0.0.1 means “reachable from the host itself.”
The short rule
Use a localhost bind unless the container really needs to accept network traffic directly.
docker run -p 127.0.0.1:8080:80 nginx
That pattern is safer for dashboards, admin tools, preview apps, and services that should sit behind Nginx, Caddy, Traefik, Cloudflare Tunnel, Tailscale, or an SSH tunnel.
Use an all-interface publish only when you are intentionally exposing the service:
docker run -p 8080:80 nginx
Do not treat that as a harmless local development default on a public server.
What 0.0.0.0 means in Docker port output
When Docker shows a published port like this:
0.0.0.0:8080->80/tcp
it means Docker has published host port 8080 for traffic arriving on host interfaces, not just the loopback interface. On a public VPS, that can make the application reachable from outside the server unless another layer blocks it.
Docker’s own publishing documentation says that when a port is published, it is published to all network interfaces by default. That is convenient, but it is not private-by-default.
What 127.0.0.1 changes
A localhost-bound publish looks like this:
docker run -p 127.0.0.1:8080:80 nginx
The intended result is that the service listens on the host loopback address. You can reach it from the server itself:
curl http://127.0.0.1:8080/
But remote clients should not be able to connect directly to SERVER_PUBLIC_IP:8080 just because the container exists.
This is a good fit when a reverse proxy or tunnel is the real public entry point.
Docker Compose example
In Compose, prefer the explicit host address for private local services:
services:
app:
image: nginx:latest
ports:
- "127.0.0.1:8080:80"
Avoid this unless the app is meant to be directly reachable from the network:
services:
app:
image: nginx:latest
ports:
- "8080:80"
The second form is shorter, but on a public server shorter can mean broader exposure.
How to check what Docker published
Use Docker’s own view first:
docker ps --format 'table {{.Names}}\t{{.Ports}}'
Look for the host-side address:
0.0.0.0:8080->80/tcp
127.0.0.1:8080->80/tcp
Then check local listening sockets:
ss -ltnp
If you see a service bound to 0.0.0.0:PORT, treat it as potentially reachable from outside. If you see 127.0.0.1:PORT, it is local to the host.
For an outside-in check, scan only systems you own or have permission to test:
nmap -Pn -p 8080 SERVER_PUBLIC_IP
Do not scan third-party systems.
Where this fits with reverse proxies
A common safe pattern is:
- bind the container to localhost;
- put a reverse proxy on the same host;
- expose only the reverse proxy ports that need to be public;
- add authentication, TLS, and logging at the proxy or access layer.
For example:
internet -> Caddy/Nginx/Traefik :443 -> 127.0.0.1:8080 -> container:80
That is easier to reason about than every container opening its own public port.
Common mistakes
Assuming EXPOSE publishes a port
EXPOSE documents the container port. It does not publish it to the host by itself. Publishing happens with -p, --publish, Compose ports:, or --publish-all.
Publishing admin tools directly
Dashboards, database UIs, internal APIs, and temporary preview apps should usually bind to localhost or stay on a private network.
Trusting the cloud firewall without checking Docker output
Cloud firewalls help, but they are not a substitute for knowing what Docker is publishing on the host. Check both.
Safer default checklist
Before leaving a container running on a public Linux server, confirm:
docker psdoes not show accidental0.0.0.0publishes;- admin tools are bound to
127.0.0.1or private interfaces; - public services are intentional and documented;
- a reverse proxy or tunnel is the only public entry point where possible;
ss -ltnpand an outside-in check agree with your expectation.
Related TheLinuxForum guides
- Linux Server Security
- How to Check What Ports Are Listening on Linux
- How to Decide What Should Be Public on a Linux Server
- Docker and UFW: Why Published Ports Can Surprise Linux Server Operators
- Nmap for Your Own Server
Sources
- Docker Docs: Publishing and exposing ports — https://docs.docker.com/get-started/docker-concepts/running-containers/publishing-ports/
- Docker Docs: Published ports — https://docs.docker.com/engine/network/port-publishing/
- Docker CLI reference:
docker container run— https://docs.docker.com/reference/cli/docker/container/run/ - Linux
ssmanual — https://man7.org/linux/man-pages/man8/ss.8.html
Verification note
This page is a source-checked operational guide. It uses read-only inspection commands and Docker’s documented port-publishing behavior. It does not claim a production firewall test on your server; confirm exposure from your own network path before relying on it.
Bottom line
On a public Linux server, write the host bind address explicitly. 127.0.0.1:8080:80 is a private local publish. 8080:80 is a public-exposure candidate until proven otherwise.