Port Mapping with -p
Port mapping is how you make a network service inside a Docker container reachable from your host machine or from other machines on the network. A web server may listen on port 80 inside its container, but nothing on the host can reach it until Docker publishes that container port with -p. Understanding this distinction prevents one of the most common Docker networking mistakes: confusing a process listening inside a container with a port that is open on the host.
Overview: How Port Mapping Works
Every Docker container gets its own network namespace. On the default bridge network, the container has its own private IP address, its own loopback interface, and its own set of listening ports. If Nginx listens on 0.0.0.0:80 inside the container, that means port 80 is open inside the container namespace, not automatically on your laptop’s port 80.
The -p option on docker run asks Docker to publish a container port on the host. The common form -p 8080:80 means: listen on host port 8080 and forward traffic to container port 80. After that, a browser request to http://localhost:8080 reaches the process listening on port 80 inside the container.
On native Linux Docker Engine, Docker programs networking rules for the bridge network and may use a userland proxy in some cases. On Docker Desktop for macOS and Windows, the Docker daemon runs inside a lightweight Linux VM, so Docker Desktop also forwards traffic between the host operating system and that VM. The user command is the same, but the internal path includes the Desktop VM.
Port publishing is about traffic entering a container from outside Docker. Containers on the same Docker network do not need host port mappings to talk to each other; they can use container IPs or, on user-defined networks, container names as DNS names. Publish only the ports that users, tools, or external systems need to reach from the host side.
EXPOSE is related but different. A Dockerfile line such as EXPOSE 80 records metadata saying the image expects to listen on port 80. It does not open that port on the host. Only docker run -p, docker run -P, or Compose ports: publishes a port.
Syntax
The general docker run shape is:
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
Port publishing uses one or more -p options:
docker run -p HOST_PORT:CONTAINER_PORT IMAGE
| Form | Meaning |
|---|---|
-p 8080:80 |
Publish host port 8080 to container port 80 on TCP. |
-p 127.0.0.1:8080:80 |
Bind only to the host loopback address. Other machines cannot connect to it directly. |
-p 8080:80/tcp |
Explicitly publish TCP. TCP is the default when no protocol is provided. |
-p 5353:53/udp |
Publish UDP instead of TCP. |
-p 80 |
Publish container port 80 on a random available host port. |
-P |
Publish all ports listed in image EXPOSE metadata to random host ports. |
The order is always host side first, container side second. Read -p 8080:80 as host 8080 maps to container 80. The long option is --publish, so --publish 8080:80 is equivalent to -p 8080:80.
Examples
Publish Nginx on a Host Port
docker run --name port-demo-nginx --rm -d -p 8080:80 nginx:1.27-alpine
docker ps --filter name=port-demo-nginx
curl http://localhost:8080
docker stop port-demo-nginx
Output:
f2b3c4d5e6f7
CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
f2b3c4d5e6f7 nginx:1.27-alpine "/docker-entrypoint..." Up 3 seconds 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp port-demo-nginx
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
port-demo-nginx
The container runs Nginx, which listens on port 80 inside the container. The -p 8080:80 option tells Docker to listen on host port 8080 and forward traffic to that container port. The PORTS column shows the mapping. The --rm flag removes the container after it stops; it does not remove the image.
Bind a Development Server to Localhost Only
docker run --name local-python-web --rm -d -p 127.0.0.1:8081:8000 python:3.12-alpine python -m http.server 8000
docker port local-python-web 8000
docker stop local-python-web
Output:
a1b2c3d4e5f6
127.0.0.1:8081
local-python-web
This starts Python’s built-in HTTP server on container port 8000 and publishes it only on 127.0.0.1:8081 on the host. That is a good default for local development because tools on your own machine can connect, but Docker does not listen on every network interface. Without the 127.0.0.1: prefix, Docker commonly binds to all host interfaces, shown as 0.0.0.0 and sometimes IPv6 [::] in docker ps.
Let Docker Choose an Available Host Port
docker run --name random-port-nginx --rm -d -p 80 nginx:1.27-alpine
docker port random-port-nginx 80
docker stop random-port-nginx
Output:
9a8b7c6d5e4f
0.0.0.0:49154
[::]:49154
random-port-nginx
When you provide only the container port, Docker chooses a free host port. This is useful for tests, demos, and scripts that may run multiple copies of the same service. Use docker port CONTAINER PORT or docker ps to discover the assigned host port. For a human-facing application, a fixed host port like 8080:80 is usually easier to remember.
Publish More Than One Port
docker run --name dual-port-app --rm -d -p 8080:80 -p 8443:443 nginx:1.27-alpine
docker ps --filter name=dual-port-app
docker stop dual-port-app
Output:
c0ffee123456
CONTAINER ID IMAGE STATUS PORTS NAMES
c0ffee123456 nginx:1.27-alpine Up 2 seconds 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp, 0.0.0.0:8443->443/tcp, [::]:8443->443/tcp dual-port-app
dual-port-app
You can repeat -p for every container port that must be reachable from the host. A real application might publish a public HTTP port, a development debugger, or a metrics endpoint. Do not publish internal-only ports just because they exist; each published port is part of the host’s network surface.
How It Works Step by Step
- The Docker client sends your
docker runrequest to the Docker daemon, including the image name and requested port bindings. - The daemon ensures the image exists locally, pulling read-only image layers from a registry if needed.
- Docker creates a container: a thin writable layer, a main process configuration, and isolated namespaces including a network namespace.
- The container is attached to a network, commonly the default bridge network unless you selected another network.
- The process starts inside the container and listens on its container port, such as
80/tcp. - For each
-pmapping, Docker configures host-side listeners or forwarding rules from the host IP and port to the target container IP and port. - When traffic arrives at the host port, Docker forwards it to the container port. The application sees a normal network connection inside its namespace.
- When the container stops and is removed, Docker removes the port publishing rules. The image layers remain until you remove the image separately.
A port mapping does not change what the process inside the container listens on. If an application only binds to 127.0.0.1 inside the container, it may be reachable only from inside that container, even if you publish the port. Containerized servers should usually listen on 0.0.0.0 inside the container so Docker can forward traffic to them.
Common Mistakes
Reversing Host and Container Ports
docker run --name wrong-order --rm -d -p 80:8080 nginx:1.27-alpine
This is wrong for the standard Nginx image because Nginx listens on container port 80, not 8080. Docker will publish host port 80 to container port 8080, where nothing is listening. Fix the mapping by putting the host port first and the real container port second:
docker run --name right-order --rm -d -p 8080:80 nginx:1.27-alpine
Thinking EXPOSE Publishes a Port
FROM nginx:1.27-alpine
EXPOSE 80
This Dockerfile metadata is useful documentation, but it does not publish anything to the host. If you build and run this image without -p, the server can listen inside the container while your host still has no mapped port. Publish it at runtime:
docker run --name exposed-nginx --rm -d -p 8080:80 nginx:1.27-alpine
Publishing a Database to Every Interface
docker run --name dev-postgres --rm -d -e POSTGRES_PASSWORD=changeme -p 5432:5432 postgres:16-alpine
This works, but it may expose PostgreSQL on all host network interfaces. For a local development database, bind to loopback instead:
docker run --name dev-postgres --rm -d -e POSTGRES_PASSWORD=changeme -p 127.0.0.1:5432:5432 postgres:16-alpine
Using a Host Port That Is Already Taken
Only one process can listen on a specific host IP, protocol, and port combination. If another service already owns host port 8080, Docker cannot publish 8080:80. Choose a different host port, stop the conflicting process, or let Docker assign a random host port with -p 80.
Best Practices
- Read mappings left to right:
host portthencontainer port. - Prefer high, non-privileged host ports such as
8080,8081, or5433for local development. - Bind development-only services to
127.0.0.1unless other machines truly need access. - Publish the fewest ports possible. Containers on the same Docker network can usually talk without host publishing.
- Use specific image tags such as
nginx:1.27-alpineandpostgres:16-alpineinstead oflatestfor reproducible examples and environments. - Remember that
EXPOSEis metadata;-p,-P, or Composeports:performs publishing. - Use
docker psanddocker portto verify the actual published address and port. - For production, treat published ports as part of your security boundary and combine Docker settings with host firewalls, cloud security groups, and reverse proxies as appropriate.
Practice Exercises
- Run
nginx:1.27-alpineso that it is available athttp://localhost:8090. Hint: Nginx listens on container port80. - Start two Nginx containers at the same time, one published on host port
8082and one on host port8083. Expected end state:docker psshows two different host-port mappings. - Run
postgres:16-alpinefor local development with passwordchangeme, but bind it only to127.0.0.1on host port5433. Hint: the container-side PostgreSQL port is5432.
Summary
- Container ports are private to the container’s network namespace until you publish them.
-p HOST_PORT:CONTAINER_PORTforwards traffic from the host into the container.- The host side can include an IP address, such as
127.0.0.1:8080:80, to limit where Docker listens. - Leaving out the host port, as in
-p 80, lets Docker choose a random available host port. EXPOSEdocuments intended ports but does not publish them.- Use
docker psanddocker portto confirm what Docker actually published.
