Fix Docker "Port Is Already Allocated"
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.
Key takeaways
- Docker's "Bind for 0.0.0.0:8080 failed: port is already allocated" means something already holds that host port, usually another container or a forgotten dev server.
- Find it with docker ps --filter publish=8080, then sudo ss -ltnp.
- Stop the holder, or change the left side of -p to a free host port.
On this page
Docker's "Bind for 0.0.0.0:8080 failed: port is already allocated" means another container or host process already owns that host port. Find the holder with docker ps --filter publish=8080 (or sudo ss -ltnp | grep :8080), stop it, or publish to a free host port instead, for example -p 8081:80.
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. With -p 8080:80, Docker binds host port 8080 on every host address (0.0.0.0 and [::]) to forward traffic to the container. If anything already owns 8080, the bind fails and the container never starts.
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.
First, find who owns the port
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 three days ago, the single most common cause I see. On Docker Desktop for Mac, lsof may instead show com.docker.backend, which means a container (possibly in another Compose project or context) owns the port.
Fix it: evict the holder or move your port
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
The stopped-container trap
A container that is stopped but not removed keeps its port mapping in its config, and with a restart policy like unless-stopped it grabs the port again whenever the daemon restarts, often before your new container does. 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 it can never reclaim the port:
docker rm web-old
If nothing at all shows up in docker ps -a, lsof, or ss and the error persists, the daemon's own port bookkeeping is stale, typically after a crash. sudo systemctl restart docker clears it.
Compose leftovers and double-publishing
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.
Questions people ask
What does "Bind for 0.0.0.0:8080 failed: port is already allocated" mean?
Docker tried to publish host port 8080 on all interfaces and found it taken. The 0.0.0.0 part just means "every IPv4 address on the host". The port number is the left side of your -p flag, so either free that port or choose a different one.
How do I find which container is using a port?
Run docker ps --filter publish=8080, which lists running containers publishing or exposing that port. Add -a to include stopped ones. If neither finds anything, the holder is a regular process: use sudo lsof -i :8080 or sudo ss -ltnp | grep :8080 to get its PID.
Can I bind the container only to localhost to avoid conflicts?
Binding to 127.0.0.1 with -p 127.0.0.1:8080:80 limits who can reach the port, but it still collides with anything already listening on 127.0.0.1:8080 or 0.0.0.0:8080. It is a security choice, not a conflict fix. To avoid conflicts, pick an unused host port.
The call we'd make
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.
Sources
GitHub Actions Best Practices in 2026: Workflows You Can Trust
A production-focused GitHub Actions guide: reusable workflows, least-privilege permissions, keyless OIDC to the cloud, SHA-pinned actions, environments with approvals, concurrency-safe deploys, and CodeQL/Dependabot gates — with copy-paste examples.
Kubernetes FailedScheduling — Taints, Affinity, and Resources
A pod sits Pending and the scheduler quietly logs why it rejected every node. Learn to read that one line and fix the real cause instead of guessing.
More from DevOps
Explore more articles in this category
GitHub Copilot CLI Sandboxing Is GA: What Actually Changed
GitHub made local sandboxing generally available for Copilot CLI on October 7, so the default now restricts the agent instead of trusting it.
The Tensorlake npm Compromise Targeted Your AI Tool Configs
A compromised npm release went after cloud credentials as expected, and local AI coding-assistant config files, which most teams never treated as secrets.
GitLab's New Rate Limits: What to Fix Before Oct 19
GitLab is capping unauthenticated API calls at 60 an hour starting October 19, and the preview windows land before most teams will have noticed.
You might have missed
Evergreen posts worth revisiting.