Docker driver failed programming external connectivity fix
Your container will not start, and the daemon answers with driver failed programming external connectivity on endpoint … Docker could not wire the published host port to the container, so the run or compose up fails before your application ever boots. The useful part of the error is the tail after the endpoint ID, and it comes in three flavors — each one points at a different cause and a different fix.
I burned a Friday-night redeploy on this once. The compose file was unchanged, the port map was correct, and ss showed the port as free — yet every start attempt died with the same message. The container that owned the port was still running, just in another project I had forgotten about, and its docker-proxy was the process answering on the socket. That is the shape of this error: the daemon's message is generic on purpose, and the tail tells you which layer refused the port mapping.
TL;DR
- The error is a port-publishing failure, not an application error. Docker failed to bind a host port to the container, so nothing in your image or entrypoint is the problem.
- Read the tail.
bind: address already in useorFailure EADDRINUSE= a host process owns the port.port is already allocated= Docker's own allocator thinks another container holds it.iptables: No chain/target/match by that name= the firewall rules Docker needs were flushed. - Diagnose before restarting anything:
ss -tlnpfor host listeners,docker ps -afor the invisible container, and only thensudo systemctl restart dockeras the state-reset fix. - Restarting the daemon is a fix, not a root-cause analysis. It recreates firewall chains and reaps stale proxies, but if a host service or another project's container owns the port, the error comes straight back on the next start.
- Do not fix this by silently disabling Docker's firewall integration (
--iptables=false) or by flushing rules yourself — that removes the NAT layer that makes published ports reachable at all.
What the error means: one head, three tails
When you publish a port with -p 8080:80, the daemon does two things. It programs firewall rules that translate traffic on the host address to the container address, and — because the userland proxy is enabled by default — it starts a docker-proxy process that accepts the host connection and forwards it into the container. Both steps are part of Docker's port-publishing mechanism: published ports are mapped to host IP addresses with NAT rules and masquerading, and the userland proxy (--userland-proxy=true, the default) handles the IPv6 and hairpin cases.
When either step fails, the daemon wraps it in the same outer sentence. A real failure looks like this (container name and ID vary; the message format is the daemon's own):
docker: Error response from daemon: driver failed programming external
connectivity on endpoint <name> (<id>): Error starting userland proxy:
listen tcp4 0.0.0.0:80: bind: address already in use.
That tail — Error starting userland proxy with bind: address already in use — is the most common variant. It is the proxy process failing its own bind() system call, and it means something is already listening on the host port. The Stack Overflow thread on this error is one of the most-viewed Docker questions on the site, and the usual culprits are a host web server (nginx or Apache), a leftover container, or a stale proxy from an earlier daemon crash.
The other two tails say different things:
Bind for 0.0.0.0:3306 failed: port is already allocated— Docker's port allocator believes the port belongs to another container it manages. In the classic docker-compose report,netstatshowed onlydocker-proxyon port 3306, which looked like a ghost — untildocker psrevealed a MySQL container from another project, still running, holding the mapping.(iptables failed: iptables --wait -t nat -A DOCKER … : iptables: No chain/target/match by that name.)— the firewall programming step failed because theDOCKERchains were flushed while the daemon was running. This is the signature of a firewall reload oriptables -Fthat wiped Docker's rules, and it needs a daemon restart, not a port change.
Version context, because the firewall side moved recently: Docker Engine still defaults to iptables. Engine 28 updated docker-proxy (older proxy binaries no longer work with the newer daemon) and closed a window where the proxy could accept connections before NAT rules existed. Engine 29 added an experimental nftables backend that you opt into through the daemon's firewall-backend option — the default is still iptables, so the failure modes below apply to current installs.
Diagnose before you change anything
Do not restart the daemon first. In two of the three cases the port is genuinely owned by something, and a restart will not free it. Run these three checks in order and let the output pick the fix:
# 1. Who listens on the host port? Root is required to see process names.
# 'sport = :80' matches only port 80, not 8080/8000.
sudo ss -tlnp 'sport = :80'
sudo lsof -i :80 -sTCP:LISTEN
# 2. Which container claims the port from Docker's side?
docker ps -a --format 'table {{.Names}}\t{{.Ports}}\t{{.Status}}'
docker port <container>
| Error tail | What owns the port | Fix |
|---|---|---|
Error starting userland proxy: … bind: address already in use | Host process (nginx, Apache, another daemon) or a stale docker-proxy | Stop the host service, or remap to a free port (Fix 1 / Fix 3) |
Bind for … failed: port is already allocated | Another running container, often in a different compose project | Remove that container, or change this project's port (Fix 2) |
iptables failed: … No chain/target/match by that name | Flushed DOCKER chains / firewall state after a reload | Restart Docker, then fix firewall service ordering (Fix 4) |
💡 A listening docker-proxy on the port does not mean the port is free. That proxy belongs to whichever container owns the mapping — find the container with docker ps -a, not by killing the proxy. Killing a live docker-proxy out from under its container is how you create the stale state that Fix 3 has to clean up.
Fix 1 — a host process owns the port
If ss names nginx, Apache, or any non-Docker process on the port, you have a straight conflict. On a self-hosted box this is usually the host's own web server sitting on port 80 or 443 while you try to publish a container on the same number. Stop the service and retry, or — cleaner on a host that must keep serving — publish the container on a different host port:
sudo systemctl stop nginx # or: sudo systemctl stop apache2
docker run -d -p 8080:80 --name web nginx:alpine
curl -sS -I http://127.0.0.1:8080 | head -1
If nginx on the host is your reverse proxy in front of containers, this conflict is a symptom of a port plan, not a Docker bug: decide once which layer owns 80/443. The upstream half of that setup has its own failure signatures — if you are seeing 499s from the host proxy, that is a different problem, covered in our nginx 499 write-up.
⚠️ Before you change the host service, check whether it is socket-activated (systemd). A stopped nginx or Apache that comes back on the next boot re-occupies the port and the error returns. If the container is meant to own the port, disable the host service for boot: sudo systemctl disable --now nginx.
Fix 2 — the invisible container in another project
When the tail says port is already allocated, trust Docker's allocator over your memory. A compose project you started last week and forgot, a docker run with -d that you never cleaned up — both hold their published ports even if you are not looking at them. The container's docker-proxy shows up in ss, which is how people conclude the port is "free" while the daemon keeps refusing it.
# The format output shows every container, running or not, with its port map.
docker ps -a --format 'table {{.Names}}\t{{.Ports}}\t{{.Status}}'
# Found the owner? Remove it (or stop it if you still need it):
docker rm -f <container-name>
# Inside compose, list and clean the project's own leftovers:
docker compose ps -a
docker compose rm -f <service>
This is also why a failed start leaves you stuck: when the daemon rejects the port mapping, the container it created stays behind in a half-created state, and the next docker run with the same name and port fails again. Remove the failed container after a failed start (docker rm -f <name>) so the retry starts from a clean slate.
Fix 3 — the port looks free but Docker still refuses
Both checks come back empty — no host listener, no container in docker ps -a — yet the daemon still reports bind: address already in use. That is stale state: a docker-proxy orphaned by a daemon crash or a hard kill is still holding the socket, or the daemon's in-memory allocation table disagrees with reality. The daemon restart is the correct tool here, and only here:
sudo systemctl restart docker
pgrep -a docker-proxy # should be empty after a clean restart
# Name the container web80: Fix 1's container (web) may still be running.
docker run -d -p 80:80 --name web80 nginx:alpine
docker port web80 # prints: 80/tcp -> 0.0.0.0:80
curl -sS -I http://127.0.0.1:80 | head -1
If the container still fails after a daemon restart and pgrep shows a docker-proxy that survives it, that process is not Docker's — check its parent with ps -o ppid= -p <pid> before touching it. A proxy owned by PID 1 or another supervisor belongs to something else on the host, and you are back in Fix 1 territory.
Fix 4 — the firewall backend broke (iptables/nftables tail)
The iptables failed … No chain/target/match by that name tail appears after a firewall reload wiped Docker's chains: systemctl restart firewalld, iptables -F, or a nftables ruleset reload that did not know about Docker. The daemon keeps believing its NAT rules exist; they do not, and every new port mapping fails. Restarting Docker recreates the chains:
sudo systemctl status firewalld # active? firewalld owns the rules
# RHEL/Fedora family: reload firewalld state, then always restart Docker after it
sudo firewall-cmd --reload
sudo systemctl restart docker
The ordering rule is the real fix: on hosts with firewalld, Docker must start after the firewall, or the firewall's own initialization flushes the DOCKER chains again on the next boot. Check both services are enabled and let systemd order them: systemctl is-enabled firewalld docker. If you manage rules with nftables yourself, keep Docker's table out of your flush scope — Docker owns the nat table entries it created, and blanket flush ruleset scripts are the most common way people break this.
On Docker Engine 29, if you opted into the experimental nftables backend via the daemon's firewall-backend option, the same restart logic applies — the backend rebuilds its own tables at daemon start. The default remains iptables, and in both cases a firewall ruleset reload while Docker is running is what triggers this tail.
Docker Desktop and WSL2
On Docker Desktop (macOS and Windows/WSL2) the engine runs in a VM, and the port bind happens on the VM's network stack. The Windows-specific version of this error — the same outer message with a mkdir /port/… or I/O error tail, or EADDRINUSE after every reboot — is usually Docker Desktop state, and the fix is to restart the engine, not the container: quit Docker Desktop and start it again, or wsl --shutdown from PowerShell to reset the WSL2 utility VM. Recurring failures after every Windows boot are frequently Windows Fast Startup restoring the old VM state; disabling it in Power Options is the documented workaround in the Docker on Windows thread. If you run the engine natively inside a WSL2 distro instead of through Docker Desktop, the systemctl restart docker fix from Fix 3 applies inside the distro. The network-mode differences between those two setups are covered in our WSL2 network_mode: host post.
Prevention checklist
- Own your host ports. Keep a written map of 80/443 and the common ports (3306, 5432, 6379, 8080) so a container and a host service never both claim them.
- Bind to a specific address when the container is for local use only:
-p 127.0.0.1:8080:80publishes only on loopback and avoids colliding with wildcard listeners on other interfaces. - Clean up after yourself in CI and on shared runners: use
--rmwhere safe, tear down compose projects when the job ends, and prune unused networks periodically so stale state cannot accumulate. - Never flush firewall rules blindly on a Docker host. If a ruleset reload is unavoidable, schedule a Docker restart immediately after it and verify a published port still answers.
- After any failed start, remove the failed container before retrying, or compose will keep colliding with its own leftover.
When the error reappears after you have applied the matching fix, re-run the three diagnostic commands rather than restarting again — the error tails are stable, and the second occurrence usually reveals the owner you missed the first time.