You start a container and Docker stops cold with this:
Error response from daemon: driver failed programming external connectivity on endpoint web:
failed to bind host port for 0.0.0.0:8080:172.17.0.2:80/tcp: address already in use
The wording comes straight from the kernel. When Docker publishes a port it asks the operating system to bind() that host port, and the kernel refuses because another socket already owns the same address and port. The container never gets to run.
This is the same root cause as the “port is already allocated” error, just reported from a different layer. The fix follows the same path: find what’s holding the port, then decide whether to free it or move your container to a port that’s free. The commands below are for Linux hosts. On Docker Desktop the logic is identical, you just use the host OS tools to inspect ports.
Where the error comes from
A published port like -p 8080:80 has two halves. The left number, 8080, is the host port. The right number, 80, is the port inside the container. Docker binds 8080 on the host and forwards traffic into the container’s port 80.
The kernel allows exactly one listener per address and port. If anything already listens on 0.0.0.0:8080, the bind() call returns EADDRINUSE, and that surfaces as address already in use. So the real question is never “what’s wrong with Docker.” It’s “what already owns this port.”
Step 1: Rule out another container
Containers are the most common offender, so check them first. List what’s running and read the PORTS column:
docker ps
Anything publishing your host port shows up like 0.0.0.0:8080->80/tcp. Filter straight to it:
docker ps --filter "publish=8080"
A stopped container that was never removed can still hold its mapping in Docker’s records, which is enough to block a fresh bind. Show stopped containers too:
docker ps -a
Found one you don’t need? Stop and remove it:
docker stop <container-name-or-id>
docker rm <container-name-or-id>
For a Compose project you’re finished with, take it down from its folder:
docker compose down
Step 2: Find the host process holding the port
If docker ps is empty, the port belongs to a process on the host. Two tools will name it, and either is fine.
ss is part of iproute2 and ships on modern Linux. It’s the fastest way to ask “who is listening on this port”:
sudo ss -ltnp 'sport = :8080'
Read the flags as listening (-l), TCP (-t), numeric (-n), with process info (-p), filtered to source port 8080. The output names the program and PID, for example users:(("nginx",pid=812,fd=6)).
lsof shows the same thing in a different layout:
sudo lsof -i :8080
On an older host without ss, fall back to netstat:
sudo netstat -ltnp | grep ':8080'
Tools for finding what owns a port
| docker ps | First check — is a container publishing this host port? |
|---|---|
| sudo ss -ltnp | Fast and modern. Lists listening sockets with process and PID. |
| sudo lsof -i :PORT | Per-port view showing process, PID, and user. |
| sudo netstat -ltnp | Legacy fallback when ss isn't available. |
Step 3: Free the port or change the mapping
Now you know what’s on the port, pick the fix that fits instead of killing things on reflex.
If it’s a leftover container or a process you don’t need, stop it. For a managed service, use the service manager rather than kill:
# Example: a host Nginx sitting on 80/8080
sudo systemctl stop nginx
# Keep it from starting at boot again
sudo systemctl disable nginx
If it’s a real service you rely on, leave it running and move your container to a free host port. This is usually the safer choice. Only the host side of the mapping changes:
# Was -p 8080:80, publish on 8081 instead
docker run -d --name web -p 8081:80 nginx
In Compose, edit the ports: line and bring the stack back up:
services:
web:
image: nginx
ports:
- "8081:80" # host 8081 -> container 80
docker compose up -d
The container still listens on 80 inside; you’ve only picked a different door on the host.
Bind to 127.0.0.1 to dodge conflicts and exposure
By default -p 8080:80 binds to 0.0.0.0, meaning every interface on the host. That invites collisions with other listeners and, worse, exposes the container to your whole network. For anything that only needs to be reached from the host itself — a database, an admin UI, a backend an app talks to locally — bind it to localhost:
docker run -d --name db -p 127.0.0.1:5432:5432 postgres:16
Or in Compose:
services:
db:
image: postgres:16
ports:
- "127.0.0.1:5432:5432"
This narrows the bind to the loopback interface, so it can’t clash with whatever’s bound on the public interface, and the service stays off the LAN. A Postgres or Redis container that nothing external should reach has no reason to sit on 0.0.0.0.
Why the port can stay busy right after you stop something
A common follow-up: “I stopped the process and the port is still taken.” Usually it’s one of these.
A TCP socket can linger in TIME_WAIT for a short window after the owning process exits. The kernel holds the address briefly to catch any straggling packets, so an immediate restart on the same port can fail with the same error. Wait a few seconds and try again, or check the state:
ss -tan | grep ':8080'
If you see the port in TIME_WAIT, it’ll clear on its own shortly. The other cause is a container that stopped but wasn’t removed — docker ps -a will show it, and docker rm clears the mapping. If a stuck container won’t go cleanly, force it:
docker rm -f <container-name-or-id>
Quick checklist
Clearing 'bind: address already in use'
- Run docker ps and docker ps -a — is a container (running or stopped) on that host port?
- If yes and you don't need it: docker stop + docker rm, or docker compose down
- If no container: sudo ss -ltnp 'sport = :PORT' or sudo lsof -i :PORT to name the process
- Real service you need? Remap your container to a free host port
- Local-only service? Bind it to 127.0.0.1 to avoid conflicts and exposure
- Port still busy right after stopping? Check for TIME_WAIT and wait a few seconds
- Re-run docker run / docker compose up -d and confirm it starts
Wrapping up
bind: address already in use is the kernel telling you a host port is already taken, nothing more exotic than that. Work it in order: check docker ps and docker ps -a for a container holding the port, then ss or lsof for a host process. From there the decision is clean — stop what you don’t need, remap onto a free host port what you do, and bind local-only services to 127.0.0.1.
If you’d rather see the Docker-side version of this same conflict, read how to fix “port is already allocated”. For how published ports fit into a multi-container stack, the Docker Compose beginner guide walks through the ports: block, and the rest of the Docker & Containers guides cover more everyday fixes.