docker: Bind for 0.0.0.0:PORT failed: port is already allocated
Hi, it's BlueByte. When Docker says a port is already allocated but docker ps looks empty, the culprit is usually a stopped container still holding the mapping. Let's look in the right place — including stopped containers — find the holder, and free the port cleanly.
What the error means
Starting a container fails:
Error response from daemon: driver failed programming external connectivity
on endpoint web: Bind for 0.0.0.0:8080 failed: port is already allocatedDocker cannot publish the host port because it is already taken — usually by another container it still tracks, occasionally by a plain host process. The confusing part is that docker ps can look empty while the port is still held, so the first step is knowing where to look.
Why the port is still held
A container that still holds the port mapping is bound to it. That container may be running, or — more confusingly — stopped but not removed after a crash or an interrupted docker run: Docker keeps reserving its port until the container is removed. That is why docker ps (running only) can look empty while the port is still "allocated". Less often, a non-Docker process on the host owns the port, or a Compose project left containers behind from a previous up.
Look for the holder — including stopped containers
docker ps hides stopped containers, so look for all of them:
docker ps -a --filter "publish=8080"If a container is listed, that is your cause. If nothing is listed, the holder is not Docker — check the host directly:
ss -ltnp | grep :8080That names the host process bound to the port. Between the two commands you will always know whether it is a container or a host process.
Free the port
- Remove the container holding the port (stop it first if running):
docker rm -f <container_id>- If you use Compose, bring the project down so old containers are removed before recreating:
docker compose down && docker compose up -d- If a host process owns the port, stop it, or publish your container on a different host port:
docker run -p 8081:80 my-imageA real case: an exited container from yesterday
docker compose up fails with "Bind for 0.0.0.0:8080 failed: port is already allocated", but docker ps shows nothing running on 8080. You run docker ps -a --filter publish=8080 and find an exited container from a compose run that crashed yesterday — still holding the port. docker compose down removes it, docker compose up -d recreates cleanly, and the service binds 8080. The lesson: docker ps hid the culprit because it was stopped, not running.
Confirm the port was taken cleanly
Start the container again and confirm the mapping:
docker run -d -p 8080:80 my-image
docker ps --filter "publish=8080"Seeing your container listed on 8080 means the port was freed and taken cleanly.
Keep it from recurring
Always docker compose down before up so old containers do not linger holding ports, and remove one-off docker run containers when you are done (or use --rm). On shared hosts, keep a note of which host ports each service publishes to avoid collisions.
How this differs from the "driver failed" chain error
port is already allocated is a conflict — the chain is fine, the port is just taken. That is the opposite of driver failed programming external connectivity ... No chain/target/match, where the port is free but Docker's iptables chain was wiped. Different cause, different fix — so check docker ps -a for a stopped holder first.
Related questions
docker ps shows nothing, but the port is still allocated. Why?
docker ps lists only running containers. A stopped container still reserves its port mapping until removed. Use docker ps -a, or check for a non-Docker host process with ss/lsof.
How do I avoid this with Compose?
Run docker compose down before up so old containers are removed rather than left holding ports. Recreating without down can collide with the previous run's containers.
Can two containers share a host port?
No — one host port maps to one container. Publish them on different host ports, or put them behind a reverse proxy that listens on the shared port.
The holder is a host process, not Docker.
Stop that process, or run your container on a free host port. ss -ltnp names the process id and program bound to the port.
Restarting Docker freed the port. Is that a fix?
It works because the restart clears stopped containers' reservations, but it is heavy-handed — removing the specific stale container is cleaner and does not disrupt your other containers.
References
Haneul Seo
Infrastructure engineer · 10+ years running Linux fleets
More in this category
Git: fatal: detected dubious ownership in repository
Since the CVE-2022-24765 fix in Git 2.35.2, Git refuses to read a repository whose working tree or .git directory is owned by a different user than the one running the command. It shows up in containers, CI jobs, sudo sessions and shared drives. Fix the ownership if the repo should be yours, or add the exact path to safe.directory in your global config — never in the repo's own config, which Git ignores for this.
Kubernetes: Internal error occurred: failed calling webhook
An admission webhook sits in front of your write, the API server could not get an answer out of it, and failurePolicy: Fail turned that silence into a rejection. The tail of the message is the whole diagnosis: context deadline exceeded means the call went nowhere, no endpoints available means nothing is running, and an x509 line means the API server does not trust the webhook's certificate. Each has a different fix, and none of them is your manifest.
MySQL: ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
A statement waited the full innodb_lock_wait_timeout for a row lock another transaction is still holding, and gave up. sys.innodb_lock_waits names the blocking session and hands you the KILL statement, and a blocking_query of NULL means the blocker is idle on an open transaction. The detail most retry loops get wrong: by default only the timed-out statement is rolled back, so your transaction is still open and still holds every lock it took earlier.
Redis: MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk
Reads keep working and every write is rejected, because the last background save failed and stop-writes-on-bgsave-error defaults to yes. The log names the real cause — no space, a dir the redis user can't write, a read-only mount at rename time, or fork failing with Cannot allocate memory. Fix the cause, run one BGSAVE, and writes come back on their own with no restart: rdb_last_bgsave_status flips from err to ok. Setting stop-writes-on-bgsave-error no restores writes instantly but leaves the snapshot broken, so treat it as a deliberate trade, not the fix.
Node.js: FATAL ERROR: Reached heap limit — JavaScript heap out of memory (exit 134)
The V8 heap has its own ceiling, derived from system memory and the Node release, and it is often far below the RAM you have; when a build or server reaches it, V8 aborts with FATAL ERROR: Reached heap limit and exit code 134. Read the real limit with v8.getHeapStatistics().heap_size_limit, then raise it with --max-old-space-size (in MiB) or NODE_OPTIONS for a large workload, size it below the cgroup limit inside containers, and use --heapsnapshot-near-heap-limit to catch a leak in a long-running process. Exit 137 with no FATAL ERROR line is a container kill, not this.
Docker: "exec format error" when the container starts — wrong-platform image, no emulator, or a script with no shebang
The container exits on its first instruction with exec format error — the kernel's ENOEXEC, meaning the file exists but cannot be executed here. In practice that is an image built on one CPU architecture (an Apple-silicon Mac produces linux/arm64) and run on another (an x86_64 server) with no QEMU handler registered in binfmt_misc, or an entrypoint script whose first line is not a shebang. uname -m, docker image inspect and ls /proc/sys/fs/binfmt_misc tell the causes apart; the fix is an explicit docker buildx build --platform (or a manifest list for both), QEMU registration or --platform when you mean to emulate, and a #!/bin/sh line for the script.