Ch. 13 · Docker

Docker 'Bind for 0.0.0.0:8080 failed: port is already allocated' Fix

Fix Docker 'port is already allocated' and 'address already in use': find the container or process on the port, then remap or bind to 127.0.0.1.

~7 min readbeginnerupdated Oct 4, 2026

You start a container or a Compose project and Docker Engine refuses:

docker: Error response from daemon: driver failed programming external connectivity on endpoint web (5b1c0e...): Bind for 0.0.0.0:8080 failed: port is already allocated.
Text

Docker 28 can add a failed to set up container networking: prefix before driver failed, and Compose v2 prints the same daemon text under the failing service. The meaning is the same: you asked Docker to publish host port 8080, and Docker has already handed that host port to another container.

A close cousin appears when the port is held by something that is not a container Docker knows about:

Error starting userland proxy: listen tcp4 0.0.0.0:8080: bind: address already in use
Text

Docker Desktop words it as Ports are not available: exposing port TCP 0.0.0.0:8080 -> ... followed by the same bind: address already in use. The stable, searchable parts are “port is already allocated” and “address already in use”; the surrounding text varies by version and platform.

Quick fix checklist

  • Find a container on the port: docker ps --filter publish=8080.
  • No container? Find the host process: lsof -nP -iTCP:8080 -sTCP:LISTEN (macOS/Linux) or sudo ss -ltnp 'sport = :8080' (Linux).
  • Stop the old container with docker stop <name> or run docker compose down in the project that owns it.
  • Or change only the host side: -p 8081:80 instead of -p 8080:80.
  • In Compose, use "127.0.0.1:${WEB_PORT:-8080}:80" so each checkout can pick its own port.
  • Check for containers with restart policies that come back on their own.
  • Publish dev-only services on 127.0.0.1 so they are not exposed to your network.

Before you start

You should know the -p HOST_PORT:CONTAINER_PORT syntax and the difference between a port the app listens on inside the container and a port published on the host; exposed versus published ports covers that. Examples target Docker Engine 27/28 and Compose v2 (docker compose, not docker-compose). The host-process commands were run on macOS; on Linux, ss is usually available when lsof is not.

Why it happens

-p 8080:80 asks the daemon to listen on host port 8080 and forward traffic to port 80 inside the container. The container side is private to the container’s network namespace, so ten containers can all listen on 80. The host side is a single shared resource per IP address and protocol.

Docker keeps its own table of host ports it has handed out. When a second container asks for 0.0.0.0:8080/tcp, the daemon checks that table first and fails with “port is already allocated” without even asking the operating system. 0.0.0.0 means “every IPv4 address”, so it also collides with a container already holding 127.0.0.1:8080.

If Docker’s table is clear but the operating system already has a listener on 8080 (a local dev server, another runtime such as Podman, a second Docker daemon or Docker Desktop VM), the bind call itself fails and the kernel’s EADDRINUSE comes back as “address already in use”. You can see the same kernel error outside Docker:

node -e "const net=require('net');const a=net.createServer().listen(18082,'0.0.0.0',()=>{net.createServer().listen(18082,'0.0.0.0').on('error',e=>{console.log(e.message);a.close()})})"
Terminal
listen EADDRINUSE: address already in use 0.0.0.0:18082
Text

Common real-world sources of the conflict:

  • A forgotten container from yesterday’s docker run -d -p 8080:80 ....
  • A restart policy (--restart unless-stopped or restart: always in Compose) that revives an old container when the daemon starts, before you start the new one.
  • Two Compose projects with the same ports. Compose names the project after the directory, so ~/src/shop-api and ~/src/shop-api-hotfix are separate projects that both want 5432:5432.
  • Scaling a service with a fixed host port. docker compose up --scale web=2 with ports: ["8080:80"] fails on the second replica.

Stopped containers do not hold their published ports; only running ones do.

Step-by-step walkthrough

Step 1: Look for a container that publishes the port

docker ps --filter publish=8080 \
  --format 'table {{.Names}}\t{{.Ports}}\t{{.Label "com.docker.compose.project"}}'
Terminal

The publish filter matches the host-side port. Typical output:

NAMES          PORTS                  com.docker.compose.project
shop-web-1     0.0.0.0:8080->80/tcp   shop
Text

The third column tells you which Compose project owns it (empty for plain docker run containers). If you use several contexts, repeat with docker --context <name> ps because each daemon has its own table.

Step 2: If no container shows up, find the host process

lsof -nP -iTCP:8080 -sTCP:LISTEN          # macOS and Linux
sudo ss -ltnp 'sport = :8080'             # Linux
Terminal
COMMAND   PID   USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
node    14903 dev     12u  IPv6  0x...       0t0  TCP *:8080 (LISTEN)
Text

On Linux, if the owner is docker-proxy, a container owns the port after all: maybe one from another daemon or context. On Docker Desktop for Mac the owner is a Docker Desktop backend process, which again points at a container. On Windows, netstat -ano | findstr :8080 gives the PID to look up in Task Manager.

Step 3: Free the port or pick another one

Decide whether the old holder should keep the port.

docker stop shop-web-1                      # keeps the container, frees the port
docker compose -p shop down                 # stops and removes that project's containers
docker run -d --name web2 -p 8081:80 nginx  # or simply use a different host port
Terminal

Change only the left-hand number. The right-hand number must match what the application inside listens on; changing it to dodge the conflict leaves you with a container that publishes a port nothing listens on.

Step 4: Make Compose ports configurable

services:
  web:
    image: nginx:1.27
    ports:
      - "127.0.0.1:${WEB_PORT:-8080}:80"
yaml

Each checkout can now set WEB_PORT=8081 in its own .env file. If you do not care which host port you get, omit it: - "127.0.0.1::80" (or -p 127.0.0.1::80) lets Docker choose a free ephemeral port, and docker compose port web 80 tells you which.

Step 5: Prefer 127.0.0.1 for development services

Without an IP, Docker publishes on all interfaces, so your database is reachable by anyone on the same network, and on Linux the iptables rules Docker adds are not filtered by tools like ufw. Binding 127.0.0.1:5432:5432 keeps it local. Note this does not avoid conflicts with a listener already on 0.0.0.0:5432, because that one covers 127.0.0.1 too.

Worked scenario

A developer has the main branch running from ~/src/shop-api and checks out a hotfix branch into ~/src/shop-api-hotfix. Both contain:

services:
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: devpass
    ports:
      - "5432:5432"
yaml

docker compose up -d in the hotfix directory fails:

Error response from daemon: driver failed programming external connectivity on endpoint shop-api-hotfix-db-1 (...): Bind for 0.0.0.0:5432 failed: port is already allocated
Text

Diagnosis:

docker ps --filter publish=5432 --format '{{.Names}} {{.Label "com.docker.compose.project"}}'
Terminal
shop-api-db-1 shop-api
Text

The main checkout’s database holds 5432. The fix keeps both running by parameterising the port and binding to loopback:

    ports:
      - "127.0.0.1:${DB_PORT:-5432}:5432"
yaml
echo "DB_PORT=5433" > ~/src/shop-api-hotfix/.env
cd ~/src/shop-api-hotfix && docker compose up -d
docker compose port db 5432
Terminal
127.0.0.1:5433
Text

The hotfix app’s connection string uses port 5433 on the host, while containers inside the hotfix project still reach the database at db:5432 over the project network, because published ports only matter for traffic coming from the host.

Common mistake

The tempting fixes are the destructive ones:

  • docker rm -f $(docker ps -aq) removes every container on the machine, including ones with data in their writable layers, to free one port. Stop the one container that holds it.
  • Killing the docker-proxy or Docker Desktop process that lsof shows. The daemon still believes the port is allocated and the container’s networking breaks; stop the container through Docker instead.
  • Restarting the daemon or rebooting. It works only until a container with a restart policy grabs the port again on startup.
  • Changing the container port (-p 8080:8081) instead of the host port, which publishes a port the application is not listening on.

Verify the behavior

docker run -d --name web2 -p 127.0.0.1:8081:80 nginx:1.27
docker port web2
curl -sI http://127.0.0.1:8081 | head -n 1
Terminal

Expected output:

80/tcp -> 127.0.0.1:8081
HTTP/1.1 200 OK
Text

Then confirm the port is not reachable from other machines: from another host on the network, curl http://<your-ip>:8081 should fail to connect.

Interview exercise

“Two containers both run nginx on port 80, so why does docker run -p 8080:80 fail for the second one, and what changes if you use -p 127.0.0.1:8080:80?”

Answer and reasoning

Each container has its own network namespace, so port 80 inside one never clashes with port 80 inside another. Publishing is different: -p 8080:80 claims host port 8080 on all host IPv4 addresses, and the host has only one 8080 per address and protocol. Docker tracks those claims and rejects the second one with “port is already allocated”; if a non-Docker process holds the port, the kernel returns EADDRINUSE instead.

127.0.0.1:8080:80 claims 8080 only on the loopback address. That makes the service reachable from the host alone (good for development and admin ports), but it still conflicts with any existing listener on 0.0.0.0:8080, because the wildcard includes loopback. For many parallel instances, let Docker choose the host port (-p 127.0.0.1::80) and discover it with docker port, or put a reverse proxy in front and keep the apps unpublished on a shared network.

Continue learning

More in Docker

esc