Linux Server Security is the current field-guide cluster.Linux • Security • Self-hosting • Practical tools
Linux Server Security

Docker Ports: 0.0.0.0 vs 127.0.0.1 on Linux

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:

  1. bind the container to localhost;
  2. put a reverse proxy on the same host;
  3. expose only the reverse proxy ports that need to be public;
  4. 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:

Sources

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.