Ports in Docker control how traffic reaches a service running inside a container from your browser or any other application on the outside. Without port mapping, the container runs in an isolated network, and nothing can reach it, even if the service inside is working perfectly.
Why containers need port mapping
Containers have their own internal network. A web server running inside a container listens on port 80 of that container’s network, not on port 80 of your machine. From outside the container, that port doesn’t exist.
Port mapping creates a forwarding rule: traffic arriving on a specific port on your machine is forwarded to a specific port inside the container.
The -p flag
Port mapping is set at container startup with the -p flag:
docker run -p 8080:80 nginx
The format is always host_port:container_port, with the host on the left and the container on the right.
This command tells Docker: any traffic arriving on port 8080 on your machine should be forwarded to port 80 inside the container. Open a browser to http://localhost:8080 and you hit the NGINX server running on port 80 inside the container.
The application determines the container ports on which NGINX listens by default: 80 for HTTP, 443 for HTTPS, and so on. PostgreSQL listens on 5432 by default. The host port is your choice. You can map to any available port on the host side.
Host port vs container port
The host port must be unique on your machine. No two running containers, nor any other application, can use the same host port at the same time. If port 8080 is already in use, Docker will fail when you try to map another container to it.
The container port has no such restriction. Two containers can both listen on port 80 internally without conflicting, because each container has its own isolated network. You map them to different host ports:
docker run -p 8081:80 nginx
docker run -p 8082:80 httpd
Both containers use port 80 internally. On the host, one is reachable at 8081 and the other at 8082.
Multiple ports
A container can expose multiple ports. Map each one with its own -p flag:
docker run -p 8080:80 -p 8443:443 nginx
In a docker-compose.yml file, the same syntax applies under the ports key:
services:
web:
image: nginx
ports:
- "8080:80"
- "8443:443"
Limiting access with an IP
By default, a mapped port listens on all network interfaces, so that any device on your network can reach it. To restrict a port to localhost only, add the IP in front:
docker run -p 127.0.0.1:5432:5432 postgres
This is useful for databases or internal services that should only be accessible from the same machine, not from other devices on the network.
Checking what’s mapped
To see the port mappings for a running container:
docker port container_name
Or check the PORTS column in docker ps. The output shows the full forwarding rule, for example, 0.0.0.0:8080->80/tcp, which means all interfaces on port 8080 forward to container port 80 over TCP.
EXPOSE vs -p
Dockerfiles sometimes include an EXPOSE instruction. This does not publish the port or make it accessible; it’s documentation that signals which port the application uses. The -p flag at runtime is what actually opens the port. If you see EXPOSE 80 in a Dockerfile and run the container without -p 8080:80, nothing will be reachable.
The Takeaway
Ports in Docker connect a port on your host machine to a port inside a container using the -p host_port:container_port syntax. The application fixes the container port; the host port is your choice and must be unique on the host. Without port mapping, a containerised service is only reachable from inside the container itself.
