You run docker run, and it refuses to bind port 8080. Something already owns that port. Here is how to find the holder and get your container up.
You run the container you have run a hundred times, and Docker throws this back:
docker: Error response from daemon: driver failed programming external
connectivity on endpoint web (a1b2c3...): Bind for 0.0.0.0:8080 failed:
port is already allocated.
Or the plain kernel version, which shows up when something outside Docker is holding the port:
Error starting userland proxy: listen tcp4 0.0.0.0:8080:
bind: address already in use
Both say the same thing. When you do -p 8080:80, Docker tries to grab host port 8080 so it can forward traffic to the container. If anything already owns 8080 on the host, the bind fails and the container never starts. Your job is to find out who owns it, then either evict them or move your container to a different host port.
Note the shape of the mapping while we are here. -p 8080:80 means host 8080 -> container 80. The number on the left is the one that fights over the host. The number on the right lives inside the container and never conflicts with anything on the host. Almost every "port is already allocated" error is about the left number.
There are two suspects: another Docker container, or a regular process on the host. Check Docker first because it is the fast one.
docker ps --filter publish=8080
That lists only containers publishing 8080. If you get a hit, there is your culprit:
CONTAINER ID IMAGE PORTS NAMES
7f9c1a2b3d4e nginx 0.0.0.0:8080->80/tcp old-web
Stop it and, if you do not need it, remove it:
docker stop old-web
docker rm old-web
Re-run your container. Done in most cases.
If docker ps --filter publish=8080 comes back empty, the port is held by something that is not a running container. Two possibilities remain: a non-Docker process, or a stopped container that never got cleaned up. Time for the host-level tools.
sudo lsof -i :8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 18422 kiril 22u IPv4 ... 0t0 TCP *:http-alt (LISTEN)
On boxes without lsof installed, ss does the same thing and is usually already there:
sudo ss -ltnp | grep :8080
LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* users:(("node",pid=18422,fd=22))
There it is: a stray node dev server on PID 18422 that you left running in another terminal three days ago. This is the single most common cause I see. Someone starts an app locally, forgets it, then wonders why the container will not bind.
Once you know who holds the port, you have two honest options.
Option A: kill the holder. Reasonable when it is a leftover process you actually want gone.
kill 18422
# still there? it ignored SIGTERM, so:
kill -9 18422
Reach for kill -9 only after a plain kill fails to clear it. Give the process a second to shut down before you go nuclear.
Option B: move your container to a free host port. This is the option I reach for most, because whatever is on 8080 is often something I want running too. Change only the host side of the mapping:
docker run -p 8081:80 nginx
Container still listens on 80 internally; you just reach it on localhost:8081 from the host. Nothing inside the app changes. If you do not care which host port you get at all, let Docker pick one:
docker run -p 80 nginx
docker port <container> # tells you which host port it grabbed
Here is the one that catches people even after they think they understand this error. A container that is stopped but not removed can still reserve its published port, especially if it has a restart policy or the daemon was recently bounced. docker ps shows nothing because it only lists running containers. You need -a:
docker ps -a --filter publish=8080
CONTAINER ID IMAGE STATUS PORTS NAMES
c5d6e7f8a9b0 web Exited (0) 2 hours ago web-old
Remove it and the reservation goes with it:
docker rm web-old
Two more that come up constantly.
If you run Docker Compose, an old stack from a previous branch or project may still be up, holding the port. The docker ps filter will show it, but the clean fix is to tear the whole stack down from the project directory:
docker compose down
That stops and removes every service the Compose file defined, which releases their ports in one shot. Beats hunting containers one by one.
The other self-inflicted one: publishing the same host port twice inside a single Compose file or a single docker run. Two services both asking for 8080:80 will fail the moment the second one starts. Grep your Compose file:
grep -n "8080:" docker-compose.yml
If you find two, one of them needs a different host port. This is also where expose versus ports confusion bites. expose only documents that a container listens on a port and makes it reachable to other containers on the same Docker network. It does not bind anything on the host and it never causes this error. Only ports (and -p) publish to the host, and only published ports can collide. If you were reaching for expose to fix an allocation error, you are in the wrong file.
For the broader map of what else goes wrong with containers, see our Docker troubleshooting guide.
Run docker ps --filter publish=8080 first, every time. It answers the question in one line for the majority of cases, and it costs nothing. If it comes back empty, go straight to sudo ss -ltnp | grep :8080 to catch the non-Docker holder, and do not forget docker ps -a for the stopped-container reservation. As for the fix: when the port belongs to something you also want running, remap your container to a free host port and move on. Killing processes to reclaim 8080 is a habit that eventually costs you the wrong process at the wrong time.
Get the latest tutorials, guides, and insights on AI, DevOps, Cloud, and Infrastructure delivered directly to your inbox.
We've shipped production pipelines on both. Here's where GitHub Actions wins, where GitLab CI wins, and how to pick without regretting it in six months.
How pods can talk to AWS, GCP, and Azure with no static keys — using audience-bound projected ServiceAccount tokens and the cluster OIDC issuer.
Explore more articles in this category
The IaC landscape fractured after the Terraform license change. This is the map to what each tool is actually best at, and how to choose without regret.
Terragrunt keeps large Terraform setups DRY and orchestrated, but small teams often pay its learning curve for little return.
A practical comparison of Kubernetes-native continuous reconciliation against the classic CLI-driven, state-file IaC model for platform teams.
Evergreen posts worth revisiting.