

Sarthak Varshney is a Docker Captain, 5x C# Corner MVP, and 2x Alibaba Cloud MVP, with over six years of hands-on experience in the IT industry, specializing in cloud computing, DevOps, and modern application infrastructure. He is an Author and Associate Consultant, known for working extensively with cloud platforms and container-based technologies in real-world environments.
So you've got Docker running, you pulled an image, started a container, and… nothing. You open your browser, type localhost, and it just sits there spinning. What's going on?
Welcome to the world of port mapping. This is one of those things that trips up almost every beginner, but once it clicks, it really clicks. Let's get into it.
Think of your computer as a massive apartment building. It has one street address — that's your IP address, say 127.0.0.1 or localhost. But inside that building, there are thousands of individual apartments. Those apartments? Those are your ports. Ports go from 0 all the way up to 65535.
Now, each service that runs on your computer picks an apartment to live in. Your web browser talks to apartment 80 (HTTP) or 443 (HTTPS). Your SSH client knocks on apartment 22. Your database probably lives in 5432 (PostgreSQL) or 3306 (MySQL).
When you run a Docker container, here's the thing people miss — the container is its own separate apartment building. It has its own internal address, its own set of ports, its own little world. When nginx starts inside a container, it opens up port 80 inside that container's building. That's great for the container, but your browser is standing outside on the street trying to reach your building, not the container's.
Port mapping is you saying: "Hey, whenever someone knocks on apartment 8080 of my building, forward them over to apartment 80 in the container's building."
That's it. That's the whole concept. Let's see how Docker does this.
-p FlagThe flag you're going to use all the time is -p. It stands for "publish" — you're publishing a container port out to the host machine.
The syntax looks like this:
docker run -p HOST_PORT:CONTAINER_PORT image_name
Left side of the colon = your machine (the host). Right side of the colon = inside the container.
So if you want to access something on port 80 inside a container via port 8080 on your laptop, you'd write:
docker run -p 8080:80 nginx
That maps your laptop's port 8080 → container's port 80.
Now when you open http://localhost:8080 in your browser, Docker intercepts that, goes "oh, port 8080? Let me route that to port 80 inside the nginx container," and boom — you see the nginx welcome page.
Enough theory. Let's run something real. nginx is a web server, and Docker Hub has an official nginx image that's perfect for learning this because it has a built-in welcome page. No setup needed.
docker pull nginx
You'll see Docker downloading layers. Once it's done, the image is cached locally.
docker run -d -p 8080:80 --name my-nginx nginx
Let's break down every part of that command:
docker run — start a new container-d — "detached" mode, meaning it runs in the background so your terminal stays usable-p 8080:80 — map port 8080 on your machine to port 80 inside the container--name my-nginx — give the container a friendly name so you're not stuck using a random IDnginx — the image to useGo to http://localhost:8080
You should see this:
Welcome to nginx! If you see this page, the nginx web server is successfully installed and working.
That's not a webpage you made. That's nginx, running inside a Docker container, serving you that page through the port tunnel you just created with -p 8080:80. Pretty satisfying, right?
docker ps
Output will look something like this:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a3f2b1c4d5e6 nginx "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:8080->80/tcp my-nginx
See that PORTS column? 0.0.0.0:8080->80/tcp — that's Docker telling you exactly what's mapped. Port 8080 on all interfaces (0.0.0.0) is being forwarded to port 80 inside the container.
docker stop my-nginx
docker rm my-nginx
Or do both in one shot:
docker rm -f my-nginx
The -f flag forces it to stop and remove even if the container is running.
-p at All?Great question. Let's find out:
docker run -d --name no-port-nginx nginx
Now try visiting http://localhost:80 or http://localhost:8080. Nothing. Just your browser sitting there like it's waiting for a bus that's never coming.
The container is running. nginx is serving content on port 80 inside the container. But you've built no bridge between your machine and the container. The container is in its own isolated world, happy as a clam, totally unreachable from outside.
This actually isn't always a problem — sometimes you want containers that don't expose ports (like backend workers or database containers that only communicate with other containers on an internal network). But for web servers you want to access from your browser, you need that -p.
Clean that container up:
docker rm -f no-port-nginx
Let's say you're already running something on port 80 on your laptop. Maybe Apache, maybe another web server, maybe some dev tool. If you try to bind a container to port 80 on your host, Docker will throw an error:
Error response from daemon: driver failed programming external connectivity on endpoint ...:
Bind for 0.0.0.0:80 failed: port is already allocated
The solution is simple — pick a different host port. The container's internal port doesn't change (nginx still listens on 80), but you change which door on your building leads there:
docker run -d -p 9090:80 --name nginx-alt nginx
Now it's at http://localhost:9090. The container has no idea anything changed. It's still nginx on port 80 inside, just reached through a different external door.
This is really useful when you're running multiple containers at the same time. You can have three different nginx containers, all using port 80 internally, each accessible on different host ports:
docker run -d -p 8081:80 --name nginx-one nginx
docker run -d -p 8082:80 --name nginx-two nginx
docker run -d -p 8083:80 --name nginx-three nginx
Each one is its own apartment building, each has a resident on port 80, and you've given them different street-level mailboxes (8081, 8082, 8083) on your host machine.
Some applications use more than one port. A Node.js app might serve the API on 3000 and a websocket on 3001. You can map multiple ports by using -p multiple times:
docker run -d -p 3000:3000 -p 3001:3001 --name my-app my-node-image
Each -p flag handles one port mapping. Stack as many as you need.
If you want your host port and container port to match — which is fine when there's no conflict — you can write:
docker run -d -p 80:80 nginx
Simple, clean, obvious. Port 80 on your host goes to port 80 in the container. Most people do this unless they have a reason not to.
The order matters. It's always HOST:CONTAINER, not CONTAINER:HOST. If you write -p 80:8080 you're saying "take my host port 80 and route it to the container's port 8080." If nginx is listening on 80 inside and you flip it, you'll get nothing.
Just remember: left is yours, right is theirs. Host is left, container is right.
-p entirelyYou run the container, you try localhost:80, nothing works. You check docker ps, the container's running. First thing to check — is there anything in the PORTS column? If it's empty, you forgot -p.
If you see the "port already allocated" error, something on your machine is already using that port. Find what's using it:
On Mac/Linux:
lsof -i :8080
On Windows (PowerShell):
netstat -ano | findstr :8080
Either stop what's using that port, or pick a different host port for your Docker container.
Docker images often declare EXPOSE in their Dockerfile. This is documentation — it tells you which port the app inside listens on. But EXPOSE alone does not publish the port to your host. You still need -p to actually make it accessible. EXPOSE is just the label on the apartment door saying "hey, someone lives here." -p is the actual forwarding rule.
Your container does have its own IP (something like 172.17.0.2). You can technically reach it from your host in some configurations, but it's fragile and not how Docker is meant to be used. Always use -p and localhost. That's the reliable, portable way.
When you use -p, Docker is actually configuring iptables rules (on Linux) to route traffic from your host port to the container's network. On Mac and Windows, Docker runs a Linux VM under the hood, and the port forwarding goes through that VM as well.
You don't need to know the details of iptables to use Docker effectively. But it's good to know that the magic isn't magic — it's network routing, and it's reliable and fast.
If you want to peek at what Docker is doing at the network level:
docker inspect my-nginx | grep -A 20 "Ports"
You'll see a JSON blob showing the full port binding configuration. Good stuff when you're debugging.
By default, -p 8080:80 binds to 0.0.0.0, which means all network interfaces on your machine. That means if your machine is on a local network, other devices on that network could potentially reach your container at your-ip:8080.
If you want to restrict it to only your own machine (localhost only), you can specify the IP:
docker run -d -p 127.0.0.1:8080:80 --name local-only-nginx nginx
Now only localhost:8080 works. Other machines on your network can't reach it. Useful for local development where you don't want to accidentally expose something.
The welcome page is cool, but let's make it serve your own content. Create a simple HTML file:
mkdir ~/my-site
echo "<h1>Hello from my Docker container!</h1>" > ~/my-site/index.html
Now run nginx and mount your folder into the container's web root:
docker run -d \
-p 8080:80 \
-v ~/my-site:/usr/share/nginx/html:ro \
--name custom-nginx \
nginx
The -v ~/my-site:/usr/share/nginx/html:ro part mounts your local folder into the container (:ro means read-only). Now visit http://localhost:8080 and you'll see your own HTML file being served by nginx in Docker.
That's three Docker concepts working together — images, port mapping, and volume mounts. Feels good, right?
Alright, here's something to actually sit down and build. Don't just read through it — do it.
The Challenge: Run two web servers simultaneously on different ports
Create two separate HTML files:
~/site-a/index.html with the content <h1>Site A — Port 8081</h1>~/site-b/index.html with the content <h1>Site B — Port 8082</h1>Run two nginx containers at the same time, each serving one of your sites on different ports:
site-a → accessible at localhost:8081site-b → accessible at localhost:8082Open both in your browser and confirm they show different content.
Run docker ps and look at the PORTS column — understand exactly what you're seeing.
Bonus: Try to run a third container on port 8081 and see what error you get. Then fix it by using a different port.
Clean everything up with docker rm -f when you're done.
If you can do all of that without copying commands from somewhere, you've genuinely understood port mapping. That's the milestone.
Port mapping is one of those things where the concept sounds more complicated than it is. Your machine has ports. Your container has ports. -p connects the two. Left side is yours, right side is the container's.
Once you've got that locked in, you can run any service in Docker and expose it to your browser, your frontend, your API client — whatever needs to talk to it. nginx today, a Python API tomorrow, a database next week. Same concept every time.
The -p flag is one of the most-used flags in everyday Docker work. You'll type it so many times it'll become muscle memory. And every time you do, you're building that bridge between the isolated container world and your own machine.
Now go run some containers.