Docker Port Mapping and Access Control
Many "why can others access this / why can't others access this" questions boil down to this: which address and which port on the host did you actually publish the container port to.
Remember the -p Format First
The most common syntax:
docker run -p [host-IP:]host-port:container-port IMAGE
For example:
docker run -d --name web -p 8080:80 nginx:stable
This means:
- The service inside the container listens on
80 - The host provides access externally via
8080
4 Most Common Publishing Patterns
1. -p 8080:80
docker run -d --name web -p 8080:80 nginx:stable
This is the most common syntax and the one most likely to make people overlook the default exposure scope. It typically means the host listens on this port on all available network interfaces.
If the host itself is accessible from the LAN or public network, this service is usually exposed along with it.
2. -p 127.0.0.1:8080:80
docker run -d --name web -p 127.0.0.1:8080:80 nginx:stable
This binds only to the local loopback address.
Suitable for the following situations:
- Only want local programs to access it
- Planning to use Nginx, Caddy, or another reverse proxy to forward traffic
- Planning to access indirectly via SSH tunnel, VS Code Port Forward, frp, etc.
If this is a personal service or temporary debugging, I recommend starting with this pattern.
3. -p 0.0.0.0:8080:80
docker run -d --name web -p 0.0.0.0:8080:80 nginx:stable
The meaning is very straightforward: bind to all IPv4 addresses on the host.
If you want other machines to access it, this syntax is the clearest. But don't forget that opening to 0.0.0.0 doesn't guarantee public network access. You also need to check:
- Cloud server security groups
- Host firewall
- Upstream network policies
4. -p 192.168.1.10:8080:80
docker run -d --name web -p 192.168.1.10:8080:80 nginx:stable
Suitable for machines with multiple network interfaces or machines that have both a public IP and a private IP.
For example, a server with:
- Public IP:
203.0.113.10 - Private IP:
10.0.0.10
You can explicitly decide which address the service should be accessible from.
Don't Confuse EXPOSE with -p
EXPOSE 80 in a Dockerfile is just image metadata. It does not actually open a port on the host.
What actually makes the service accessible from the host is:
docker run -p ...
If you only wrote EXPOSE but didn't use -p at runtime, the host still can't access the service.
How to Confirm What Is Currently Mapped
First check the container itself:
docker ps
docker port web
Then check what the host is listening on:
ss -lntp | grep 8080
If the service is up in the container logs but external access fails, it's usually one of these three issues:
- The service only listens on
127.0.0.1inside the container - The port was not correctly published with
-p - The host network policy is blocking it
Common Misconceptions
Service Only Listens on localhost Inside the Container
Some applications default to listening only on 127.0.0.1 inside the container. In this case, even with -p 8080:8080, access from the host may fail.
A more reliable approach is to have the application listen on:
0.0.0.0:<container-port>
Assuming docker run -p Automatically Does Access Control
-p handles "where to map," not "who is allowed to access." The actual access boundary also depends on:
- Host firewall
- Cloud provider security groups
- Upstream reverse proxy
Host Port Already in Use
If 8080 is already in use, the container will usually report an error at startup.
Check first:
ss -lntp | grep 8080
docker ps --format "table {{.Names}}\t{{.Ports}}"
Then either change the host port or stop the conflicting service.
My Default Choices
- Temporary debugging or local-only use:
127.0.0.1:host:container - Explicitly need to provide external access:
0.0.0.0:host:container - Multi-NIC machine: explicitly bind to the target IP