Docker-fout oplossen: Bind for 0.0.0.0 failed (poort al in gebruik)
Wanneer je Docker gebruikt om een container te starten, kom je vroeg of laat deze fout tegen:
Bind for 0.0.0.0:PORT failed: port is already allocatedlisten tcp 0.0.0.0:PORT: bind: address already in useError starting userland proxy: listen tcp4 0.0.0.0:PORT: bind: address already in use- of een variant daarop in Docker Desktop / Compose logs
Deze melding betekent vrijwel altijd: op jouw host (je laptop/VM/server) is die poort al bezet, waardoor Docker de poort niet kan binden aan 0.0.0.0 (alle netwerkinterfaces). In deze tutorial leer je niet alleen hoe je het oplost, maar ook waarom het gebeurt, hoe je het structureel voorkomt, en hoe je de juiste diagnose stelt op Linux, macOS en Windows.
Inhoud
- Wat betekent “Bind for 0.0.0.0 failed”?
- Snelcheck: welke poort en welke container?
- Stap 1: achterhaal welk proces de poort gebruikt
- Stap 2: kies een oplossing (met voor- en nadelen)
- Docker Compose: typische valkuilen en fixes
- Waarom is 0.0.0.0 speciaal? (netwerk-uitleg)
- Geavanceerde diagnose: Docker, proxies, IPv4/IPv6 en TIME_WAIT
- Best practices om poortconflicten te voorkomen
- Checklist: in 60 seconden naar de oplossing
Wat betekent “Bind for 0.0.0.0 failed”?
Wanneer je een container start met port mapping, bijvoorbeeld:
docker run -p 8080:80 nginx:alpine
dan vraag je Docker: “publiceer containerpoort 80 op mijn hostpoort 8080”. Docker probeert dan op de host een socket te openen op 0.0.0.0:8080 (IPv4) en/of :::8080 (IPv6), afhankelijk van je systeeminstellingen. 0.0.0.0 betekent: luister op alle netwerkinterfaces (Wi‑Fi, Ethernet, loopback, VPN, enz.).
Als er al iets luistert op die hostpoort, kan Docker die poort niet claimen. Het OS weigert dat met EADDRINUSE (“address already in use”), en Docker vertaalt dat naar de bekende foutmelding.
Belangrijk: dit is bijna altijd een host-probleem, niet een probleem ín de container. De container kan intern prima op poort 80 luisteren; de conflict zit in de host-port mapping.
Snelcheck: welke poort en welke container?
Lees eerst exact af welke poort in de fout staat. Voorbeelden:
Bind for 0.0.0.0:5432 failed→ hostpoort 5432 (vaak PostgreSQL)Bind for 0.0.0.0:80 failed→ hostpoort 80 (vaak Apache/Nginx/IIS)Bind for 0.0.0.0:3000 failed→ hostpoort 3000 (vaak Node/React dev server)
Check ook of je container al bestaat en misschien in een crashloop zit:
docker ps -a
En kijk welke containers poorten publiceren:
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}"
Als je ziet dat een andere container al 0.0.0.0:8080->80/tcp gebruikt, dan is de oorzaak meteen duidelijk.
Stap 1: achterhaal welk proces de poort gebruikt
De kernvraag: wat gebruikt poort X op de host? Dat kan zijn:
- een lokale service (Apache, Nginx, PostgreSQL, Redis, VPN agent, IDE plugin)
- een andere Docker-container
- een Kubernetes/minikube/Kind cluster
- een dev tool (Vite, Next.js, Docker Desktop extensions)
- een “vergeten” achtergrondproces
Hieronder per OS de meest betrouwbare commando’s.
Linux
Optie 1: ss (aanbevolen)
sudo ss -lntp | grep ':8080'
Uitleg:
-l= listening sockets-n= numeriek (geen DNS)-t= TCP-p= toon proces (PID/naam)
Voorbeeldoutput:
LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("node",pid=12345,fd=21))
Dan weet je: node met PID 12345 gebruikt poort 8080.
Optie 2: lsof
sudo lsof -iTCP:8080 -sTCP:LISTEN -n -P
Voorbeeld:
node 12345 user 21u IPv4 ... TCP *:8080 (LISTEN)
Optie 3: netstat (legacy, niet altijd geïnstalleerd)
sudo netstat -tulpn | grep ':8080'
macOS
Op macOS is lsof meestal de snelste:
sudo lsof -iTCP:8080 -sTCP:LISTEN -n -P
Of met netstat:
netstat -anv | grep '\.8080 ' | grep LISTEN
Let op: macOS toont soms niet direct de procesnaam met netstat; lsof is dan handiger.
Windows (PowerShell)
Gebruik PowerShell om de PID te vinden:
netstat -ano | findstr :8080
Voorbeeld:
TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 15432
Zoek vervolgens het proces bij die PID:
Get-Process -Id 15432
Of uitgebreider:
Get-CimInstance Win32_Process -Filter "ProcessId = 15432" | Select-Object Name, CommandLine
Stap 2: kies een oplossing (met voor- en nadelen)
Als je weet wie de poort gebruikt, kies je de beste fix. Er is zelden “één juiste” oplossing; het hangt af van je doel (lokaal ontwikkelen, productie, security, samenwerking).
Oplossing A: stop of herconfigureer het proces op de host
Als een lokale service de poort gebruikt (bijv. Apache op 80, PostgreSQL op 5432), kun je die stoppen of naar een andere poort verplaatsen.
Linux (systemd)
Stel: nginx gebruikt poort 80:
sudo systemctl status nginx
sudo systemctl stop nginx
Of permanent uitschakelen:
sudo systemctl disable --now nginx
Voor PostgreSQL:
sudo systemctl stop postgresql
macOS (Homebrew services)
brew services list
brew services stop nginx
brew services stop postgresql@16
Windows (Services)
In PowerShell:
Get-Service | Where-Object {$_.Name -like "*nginx*"}
Stop-Service -Name "W3SVC" # IIS (voorbeeld)
Of via services.msc.
Wanneer kies je dit?
- Als je die host-service nu niet nodig hebt.
- Als je Docker de “bron van waarheid” wil maken voor die service.
Nadeel
- Mogelijk breek je andere projecten die op die host-service vertrouwen.
Oplossing B: gebruik een andere host-poort
Dit is de meest voorkomende en vaak veiligste fix: laat de container dezelfde interne poort gebruiken, maar map naar een andere host-poort.
Met docker run
In plaats van:
docker run -p 8080:80 nginx:alpine
gebruik:
docker run -p 8081:80 nginx:alpine
Nu is je app bereikbaar op http://localhost:8081.
Je kunt ook expliciet TCP/UDP kiezen:
docker run -p 8081:80/tcp nginx:alpine
Met Docker Compose
Voorbeeld docker-compose.yml snippet:
services:
web:
image: nginx:alpine
ports:
- "8081:80"
Toepassen:
docker compose up -d
Wanneer kies je dit?
- Als je een conflict hebt met een andere tool die je wél nodig hebt.
- Als je meerdere instanties naast elkaar wil draaien.
Nadeel
- Je moet onthouden dat je service niet op de “standaardpoort” draait.
- Sommige clients/redirects verwachten standaardpoorten (80/443) en moeten aangepast worden.
Oplossing C: stop/verwijder de container die de poort al claimt
Soms is de poort niet bezet door een host-proces, maar door een andere Docker-container die eerder is gestart.
Bekijk containers met poorten:
docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}"
Stop de container die de poort gebruikt:
docker stop <containernaam-of-id>
Verwijder hem (als je zeker weet dat hij weg mag):
docker rm <containernaam-of-id>
Als het om een Compose project gaat:
docker compose down
Tip: soms draait er een oude Compose stack met dezelfde ports: mapping. Check alle projecten:
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
Oplossing D: bind alleen op 127.0.0.1 i.p.v. 0.0.0.0
Als je service alleen lokaal nodig is, kun je de binding beperken tot loopback. Dit lost niet elk conflict op (als iets anders ook op 127.0.0.1 luistert), maar het is vooral nuttig voor security en voorspelbaarheid.
docker run
docker run -p 127.0.0.1:8080:80 nginx:alpine
Nu is de service alleen bereikbaar vanaf je eigen machine via localhost:8080, niet vanaf andere devices in je netwerk.
Docker Compose
services:
web:
image: nginx:alpine
ports:
- "127.0.0.1:8080:80"
Waarom helpt dit soms?
- Als een andere service alleen op een specifieke interface luistert (bijv. op je LAN-IP) en jij op loopback wilt luisteren, of andersom.
- In praktijk is de meest voorkomende conflict-situatie echter dat iets al op
0.0.0.0:8080luistert; dat blokkeert ook127.0.0.1:8080. Maar het omgekeerde kan wél: als iets alleen op127.0.0.1:8080luistert, kan0.0.0.0:8080niet meer.
Extra voordeel
- Minder blootstelling: je dev database of admin UI staat niet open op je hele netwerk.
Oplossing E: vermijd vaste poorten (dynamische mapping)
Voor sommige workflows hoef je geen vaste hostpoort te kiezen. Je kunt Docker een vrije poort laten kiezen.
docker run -P of -p 0:...
docker run -P nginx:alpine
Docker publiceert dan alle EXPOSE poorten naar willekeurige hostpoorten.
Of specifieker:
docker run -p 0:80 nginx:alpine
Zoek daarna de toegewezen poort:
docker ps --format "table {{.Names}}\t{{.Ports}}"
Voorbeeld:
nginx-container 0.0.0.0:54732->80/tcp
Dan ga je naar http://localhost:54732.
Wanneer kies je dit?
- Voor testcontainers/integratietests.
- Wanneer je parallel veel instanties wil draaien zonder poortplanning.
Nadeel
- Minder handig voor handmatig gebruik; je moet steeds opzoeken welke poort gekozen is.
Docker Compose: typische valkuilen en fixes
Compose maakt het makkelijk om poorten te declareren, maar ook makkelijk om per ongeluk conflicten te creëren.
Valkuil 1: meerdere services mappen dezelfde hostpoort
Voorbeeld (fout):
services:
api:
ports:
- "8080:8080"
admin:
ports:
- "8080:3000"
Beide claimen hostpoort 8080. Fix: kies verschillende hostpoorten:
services:
api:
ports:
- "8080:8080"
admin:
ports:
- "8081:3000"
Valkuil 2: je draait dezelfde Compose stack twee keer
Als je in twee mappen hetzelfde composeproject draait met dezelfde poorten, krijg je conflict. Compose gebruikt projectnamen; standaard is dat de mapnaam. Check actieve projecten:
docker compose ls
Stop de stack die je niet nodig hebt:
docker compose -p <projectnaam> down
Of ga naar de juiste map en doe:
docker compose down
Valkuil 3: network_mode: host (Linux)
Met network_mode: host gebruikt de container direct het host-netwerk. Dan gelden poorten alsof het host-processen zijn. Je gebruikt dan meestal geen ports:. Als je toch poortconflicten ziet, komt dat doordat de applicatie in de container zelf probeert te luisteren op een hostpoort die al bezet is.
Voorbeeld:
services:
app:
network_mode: host
Dan moet je de app in de container configureren om een vrije poort te gebruiken, of de host-service stoppen.
Valkuil 4: oude containers blijven bestaan
Soms zijn containers gestopt maar niet verwijderd, en Compose kan namen hergebruiken. Gebruik:
docker compose down --remove-orphans
En ruim ongebruikte resources op:
docker system prune
Let op: docker system prune verwijdert ongebruikte containers/netwerken/images; lees de output goed.
Waarom is 0.0.0.0 speciaal? (netwerk-uitleg)
0.0.0.0 is geen “echt” adres waar je naartoe surft; het is een wildcard die betekent: “bind op alle IPv4 interfaces”. Concreet:
127.0.0.1:8080→ alleen bereikbaar vanaf dezelfde machine (loopback)192.168.1.10:8080→ alleen op die LAN-interface0.0.0.0:8080→ op alle IPv4 interfaces tegelijk (inclusief 127.0.0.1)
Wanneer een proces bindt op 0.0.0.0:8080, claimt het effectief die poort op alle interfaces. Daarom kan een tweede proces niet ook op 0.0.0.0:8080 binden.
Docker port publishing werkt vergelijkbaar: -p 8080:80 betekent doorgaans “publiceer op alle interfaces”, oftewel 0.0.0.0:8080.
Waarom doet Docker dit standaard?
Omdat het voorspelbaar is: je service is bereikbaar via localhost, maar ook via je IP-adres in het netwerk (handig voor testen op mobiel of andere machines). In productie is dit niet altijd gewenst; daarom is expliciet binden op 127.0.0.1 vaak veiliger.
Geavanceerde diagnose: Docker, proxies, IPv4/IPv6 en TIME_WAIT
Soms lijkt het alsof “niemand” de poort gebruikt, maar Docker faalt toch. Dan zijn er een paar minder voor de hand liggende oorzaken.
1) IPv6 binding (:::) vs IPv4 (0.0.0.0)
Op veel systemen luistert een proces op :::8080 (IPv6 wildcard). Afhankelijk van OS-instellingen kan dit ook IPv4 verkeer accepteren (dual stack). Dan lijkt 0.0.0.0:8080 vrij, maar is hij feitelijk bezet.
Check beide:
Linux
sudo ss -lntp | grep 8080
Je ziet dan regels met 0.0.0.0:8080 en/of [::]:8080.
macOS
sudo lsof -iTCP:8080 -sTCP:LISTEN -n -P
2) Docker “userland proxy” en verschillen per platform
Docker kan poorten publiceren via iptables/nftables (Linux) of via een userland proxy. In sommige situaties zie je errors als:
Error starting userland proxy: listen tcp4 0.0.0.0:PORT: bind: address already in use
Dit wijst nog steeds op een poortconflict, maar het verklaart waarom de fouttekst soms “userland proxy” noemt.
3) TIME_WAIT is meestal niet de boosdoener (maar soms verwarrend)
TIME_WAIT sockets zijn geen listeners; ze blokkeren normaal gesproken geen bind op een luisterpoort. Maar als een proces nog draait of net herstart, kan het zijn dat het nog steeds luistert, of dat je naar de verkeerde poort kijkt (bijv. UDP vs TCP).
Controleer expliciet op LISTEN:
- Linux:
ss -lntp - macOS:
lsof ... LISTEN - Windows:
netstat -anoen kijk naarLISTENING
4) UDP vs TCP
Je kunt een conflict hebben op UDP of TCP. Docker publiceert standaard TCP, tenzij je /udp specificeert. Als jouw app UDP nodig heeft (bijv. DNS, syslog), publiceer dan correct:
docker run -p 5353:5353/udp some-dns-image
En check UDP listeners:
sudo ss -lnup | grep ':5353'
5) “Verborgen” processen: IDE’s, dev servers, achtergrondagents
Veel tools starten automatisch servers:
- JetBrains IDE’s kunnen een ingebouwde webserver/remote debug openen
- Node tooling (Vite/Next) blijft draaien in een terminal-tab
- VPN clients of security agents openen lokale proxies (bijv. 3128, 8080)
- Docker Desktop zelf kan services draaien
Als je PID hebt, bekijk de commandline:
Linux
ps -p 12345 -o pid,cmd
macOS
ps -p 12345 -o pid,command
Windows
Get-CimInstance Win32_Process -Filter "ProcessId = 15432" | Select Name, CommandLine
Best practices om poortconflicten te voorkomen
1) Kies een poortconventie per project
Bijvoorbeeld:
- Project A: 8081 (web), 5433 (db)
- Project B: 8082 (web), 5434 (db)
Documenteer dit in je README en Compose file.
2) Bind lokaal op 127.0.0.1 voor databases en admin tools
Voorbeeld Compose:
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example
ports:
- "127.0.0.1:5433:5432"
Dan is je database niet per ongeluk bereikbaar vanaf je hele netwerk.
3) Publiceer alleen wat je nodig hebt
Als containers onderling praten via Docker-netwerken, hoef je vaak geen ports: te gebruiken. Interne communicatie werkt via service names.
Voorbeeld: je webapp praat met db:5432 via het Compose netwerk. Dan hoef je de database niet naar de host te publiceren.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example
# geen ports nodig voor interne communicatie
4) Gebruik .env voor poorten (makkelijk wisselen)
Compose ondersteunt variabelen:
services:
web:
image: nginx:alpine
ports:
- "${WEB_PORT:-8080}:80"
In .env:
WEB_PORT=8087
Start:
docker compose up -d
5) Ruim regelmatig op
Oude containers en netwerken zorgen niet direct voor poortconflicten als ze niet draaien, maar het helpt om overzicht te houden:
docker ps
docker ps -a
docker network ls
docker volume ls
En opruimen (voorzichtig):
docker system prune
Checklist: in 60 seconden naar de oplossing
- Lees de fout: welke hostpoort is het? (bijv. 8080)
- Check Docker containers:
docker ps --format "table {{.Names}}\t{{.Ports}}" - Zoek host-proces op die poort:
- Linux:
sudo ss -lntp | grep ':8080' - macOS:
sudo lsof -iTCP:8080 -sTCP:LISTEN -n -P - Windows:
netstat -ano | findstr :8080 Get-Process -Id <PID>
- Linux:
- Kies fix:
- stop het proces / container, of
- wijzig hostpoort:
-p 8081:80, of - bind op loopback:
-p 127.0.0.1:8080:80, of - gebruik dynamische poort:
-p 0:80
- Start opnieuw:
docker compose up -d # of docker run ...
Praktisch voorbeeld: conflict op poort 5432 (PostgreSQL)
Stel je Compose file:
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
Je start:
docker compose up
En krijgt:
Bind for 0.0.0.0:5432 failed: port is already allocated
Diagnose (Linux)
sudo ss -lntp | grep ':5432'
Je ziet:
LISTEN 0 244 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=912,fd=7))
Dus je host draait al PostgreSQL. Je opties:
- Host-Postgres stoppen:
sudo systemctl stop postgresql
- Of Docker-Postgres op andere hostpoort:
ports:
- "5433:5432"
Daarna:
docker compose up -d
En verbinden met:
psql "postgresql://postgres:secret@localhost:5433/postgres"
Praktisch voorbeeld: conflict op poort 80 (Nginx/Apache/IIS)
Je draait:
docker run --rm -p 80:80 nginx:alpine
Fout: poort 80 bezet. Vaak is dat een webserver die automatisch draait.
Linux
sudo ss -lntp | grep ':80'
Als je apache2 of nginx ziet:
sudo systemctl stop apache2
# of
sudo systemctl stop nginx
Of kies een andere hostpoort:
docker run --rm -p 8080:80 nginx:alpine
Windows (IIS)
Poort 80 wordt vaak gebruikt door IIS (W3SVC). Check:
netstat -ano | findstr :80
Get-Process -Id <PID>
Stop IIS service (als dat kan in jouw context):
Stop-Service W3SVC
Of map naar 8080:
docker run --rm -p 8080:80 nginx:alpine
Samenvatting
De fout “Bind for 0.0.0.0 failed (poort al in gebruik)” betekent dat Docker een hostpoort wil claimen die al bezet is. De oplossing is altijd een variant van:
- vind het proces dat luistert op die poort (met
ss,lsof,netstat) - stop/verwijder de veroorzaker, of
- kies een andere hostpoort, of
- bind alleen op 127.0.0.1, of
- laat Docker een vrije poort kiezen
Als je wilt, kan ik je specifieke foutlog en je docker run/docker-compose.yml laten analyseren. Plak dan:
- de exacte foutregel,
- je startcommando of Compose
ports:sectie, - en je OS (Linux/macOS/Windows).