Fixing 'Port Is Already Allocated' Errors in Compose
Resolve the Docker Compose error 'Bind for 0.0.0.0:8080 failed: port is already allocated'. Diagnose the conflicting process, free the port, and prevent it recurring.
Fixing 'Port Is Already Allocated' Errors in Compose
docker compose up aborts with Bind for 0.0.0.0:8080 failed: port is already allocated because something already holds the host port your service wants to publish. This is the most common networking failure in Local Network & Port Mapping, and it has three distinct culprits: a leftover container, a host process, or a duplicate mapping in your own Compose files.
Diagnostic
Reproduce and identify the holder. The error names the exact host port:
Error response from daemon: driver failed programming external connectivity on endpoint app:
Bind for 0.0.0.0:8080 failed: port is already allocated
First check whether another container already publishes the port:
#!/usr/bin/env bash
# who-has-the-port.sh
set -euo pipefail
PORT=8080
echo "== Docker containers publishing :$PORT =="
docker ps --filter "publish=${PORT}" --format '{{.Names}}\t{{.Ports}}'
echo "== Host processes listening on :$PORT =="
ss -tulpn "sport = :${PORT}" 2>/dev/null || lsof -nP -iTCP:${PORT} -sTCP:LISTEN
# BAD: a stale container from a previous run still holds the port
== Docker containers publishing :8080 ==
oldstack-app-1 0.0.0.0:8080->8080/tcp
== Host processes listening on :8080 ==
COMMAND PID USER FD TYPE DEVICE NODE NAME
com.docke 4312 you 37u IPv4 0x... TCP *:8080 (LISTEN)
Root cause
A host TCP port can be bound by exactly one process. The error fires when Docker's proxy tries to bind a port that is already held — usually a container from a prior docker compose up that was never torn down (Compose keeps containers across runs unless you down), a non-Docker host process such as a local dev server or a system service, or a second ports: mapping for the same host port elsewhere in your merged Compose files.
Resolution
- Stop the stale Compose stack that still owns the port. Running
down(not juststop) releases the published ports and removes the containers.
#!/usr/bin/env bash
set -euo pipefail
docker compose down --remove-orphans
- If a container from a different project holds it, stop that one specifically.
#!/usr/bin/env bash
set -euo pipefail
docker stop "$(docker ps --filter "publish=8080" -q)"
- If a host process (not Docker) holds it, stop that process or change your published port.
#!/usr/bin/env bash
set -euo pipefail
# Identify, then stop the host process by PID from the diagnostic above
kill "$(lsof -nP -iTCP:8080 -sTCP:LISTEN -t)"
- If you cannot free the port, remap to a free host port and bind to loopback. The container port stays the same, so internal service-to-service URLs are unaffected.
# docker-compose.override.yml
services:
app:
ports:
- "127.0.0.1:${APP_PORT:-8081}:8080"
- Bring the stack back up and confirm.
#!/usr/bin/env bash
set -euo pipefail
docker compose up -d --wait
Expected output
[+] Running 2/2
✔ Container app-db-1 Healthy
✔ Container app-app-1 Started
docker compose ps --format '{{.Name}}\t{{.Ports}}'
# app-app-1 127.0.0.1:8081->8080/tcp
Prevention
- Always tear down before switching branches or stacks. A
make downthat runsdocker compose down --remove-orphanskeeps host ports clean. - Drive every published port through an
.envdefault so two stacks can coexist, and reject hardcoded ports in review.
# docker-compose.yml
services:
app:
ports:
- "${APP_PORT:-8080}:8080"
- Add a pre-up check that fails fast with a readable message instead of the raw daemon error.
#!/usr/bin/env bash
# bin/check-ports.sh — run before `docker compose up`
set -euo pipefail
for p in "${APP_PORT:-8080}" "${DB_PORT:-5432}"; do
if ss -tuln "sport = :${p}" 2>/dev/null | grep -q ":${p}"; then
echo "Port ${p} is in use; set a different value in .env before starting." >&2
exit 1
fi
done
echo "All required ports are free."
macOS (Docker Desktop):
lsofmay needsudoto see all listeners; preferssinside the Linux VM. Some Apple system services (AirPlay Receiver) hold :5000 — disable it or remap. WSL2: run the scan inside the distro; from PowerShell you query the Windows host stack and miss WSL2-bound listeners. Bind to0.0.0.0rather than[::]to avoid IPv6 binding failures. Apple Silicon (ARM64): behavior matches AMD64, but BSDlsofoutput formatting differs from GNUlsof; thesspath is more portable.
Rollback
#!/usr/bin/env bash
set -euo pipefail
git checkout -- docker-compose.override.yml 2>/dev/null || true
docker compose down --remove-orphans && docker compose up -d --wait