Skip to content
Latchkey LogoLatchkey home

Docker failed to set up container networking, on a runner

Docker failed to set up container networking is a wrapper around four unrelated failures, and the clause after the colon is the only part that tells you which one you have. The container is fine and the image is fine: the daemon could not give it the network it asked for, on this host, right now.

Diagram of the four failures behind one networking message and the check for each
One sentence, four causes. Read the clause after the colon, then run the one check that belongs to it rather than restarting the daemon and hoping.

What this error means

A docker run or a docker compose up fails while starting a container, after the image is present and before anything inside it runs, and the message is long enough that people quote only the first half of it. The half that matters is at the end: a port that could not be bound, an address pool with nothing left in it, a firewall rule that could not be written, or a privileged port in a rootless daemon. We did not reproduce this one. Every shape of it depends on host state rather than on anything in a repository, and a clean runner has to be pushed into that state first, which teaches less than the checks below. The lines here come from named reports.

The line magenx/Magento-2-docker-configuration#263 reports (illustrative, not a recorded run)
Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint varnish (6400df0fc482c2eae663274595f7a1df47633ec29813d27d2b421e4a52f6b8a4): failed to bind host port 10.0.0.2:80/tcp: cannot assign requested address

Read the clause after the colon

The wrapper text is identical in all four cases, which is why searching for it returns four unrelated conversations. Match the tail of your own line against the table and the rest of this page narrows to one row.

Two of these are about the host being full, in different senses: no free port at the address you asked for, or no free subnet to allocate. The other two are about permission: a daemon that may not write firewall rules, or one that may not bind a low port.

The clause after the colonWhat ran out or was refusedFirst check
failed to bind host port ...The port or the address on this hostss -ltnp for the port, and the address you asked for
"could not find an available ... IPv4 address pool"Subnets in the default poolsdocker network ls, then prune
An iptables or firewall rule failureThe daemon may not program rulesdocker info and the daemon flags
cannot expose privileged portA rootless daemon binding below 1024The port number, and the rootless sysctl

Common causes

The port or the address is not available

The commonest shape in CI, because service containers, a previous step and a test harness all want the same conventional port. It also covers an address that does not exist on this host: the report we quote asks for a port on an address the machine does not have, and the daemon answers that it cannot assign the requested address.

The default address pools are exhausted

Each network takes a subnet, Compose creates one per project, and nothing reclaims them until something prunes. A runner that brings up several stacks in one job, or a long-lived runner with a history, reaches the end of the pools and the next network cannot be allocated.

The daemon cannot program its firewall rules

A daemon running inside a container without the privileges it needs, a host where the rules are managed by something else, or a daemon started with rule creation disabled all end up unable to wire external connectivity. In our experience this is the cause whenever the failure appears only in a Docker-in-Docker job.

A rootless daemon asked for a privileged port

A rootless setup cannot bind below 1024 without an explicit change, and the error says so in a long clause about the port manager. yuzu-juice/ft_transcendence#129 is the report: a reverse proxy asking for port 80 under a rootless daemon, with the message naming both remedies and the port number as the third option.

How to fix it

Find out what already owns the port

  1. List listening sockets with the owning process before starting the container.
  2. If a service container has it, change the published port rather than the service.
  3. If nothing owns it, check the address in the publish flag exists on this host.
.github/workflows/ci.yml
ss -ltnp | grep -E ':(80|443|5432)\b' || echo "port is free"
docker run -d -p 18080:80 nginx:1.27

Reclaim networks, then widen the pools if you must

Pruning is the cheap fix and belongs in any job that brings up more than one stack. Widening the pools is a daemon change for a host that genuinely needs many networks at once, and it is worth doing deliberately rather than as a reaction.

.github/workflows/ci.yml
docker compose down --remove-orphans
docker network prune --force

Use the runner daemon instead of nesting one

On a runner that already has Docker, a dind service adds a daemon whose networking you now have to configure. Drop it where you can. Where a tool insists on its own engine, give that engine the privileges it needs explicitly rather than adding flags until something works.

Terminal
# only where nesting is genuinely required
docker run --privileged --network host docker:29-dind

Publish high ports, or fix the rootless limit deliberately

Under a rootless daemon the simplest change is to publish a port above 1024 and let the test talk to that. Changing the unprivileged port floor or granting the capability is a host change, so make it in the runner image rather than in a step of one workflow.

Terminal
docker run -d -p 8080:80 nginx:1.27   # not -p 80:80 under rootless

Address pools, and how a pipeline exhausts them

Every Compose project creates its own network, and every network needs a subnet from the daemon default pools. A job that brings up several stacks, or a long-lived runner whose previous jobs left their networks behind, works through those pools until an allocation fails. The second wording in the table is that moment, and EricJMarti/inventory-hunter#185 is a plain report of it.

The daemon documentation we read on 2026-09-20 exposes the pools as a daemon setting, described as the default address pools for node specific local networks, with a base and a size for each entry. Widening them is the fix when a host genuinely needs many networks at once; removing networks nobody is using is the fix the rest of the time.

Terminal
# what is allocated right now
docker network ls
docker network prune --force
# and, if the host really needs many networks:
# /etc/docker/daemon.json
# { "default-address-pools": [ { "base": "172.30.0.0/16", "size": 24 } ] }

Firewall rules, and daemons inside containers

The Docker documentation states that Docker Engine creates its firewall rules using iptables by default, and the daemon reference lists the flag that disables that behavior, noting that without the masquerading rules containers cannot reach external hosts on a network other than the default bridge. A daemon that cannot write those rules is the third row of the table, and on a runner that daemon is nearly always one you started yourself.

That is the practical advice for CI: a hosted Linux runner already has a working daemon with working networking. A docker:dind service exists to give you one where none is available, and on a runner that has one it buys a second daemon, a second image cache, and this class of failure.

.github/workflows/ci.yml
# prefer the daemon the runner already has
- run: docker run --rm -p 8080:80 nginx:1.27 nginx -t
# over a dind service plus DOCKER_HOST gymnastics

What the runner does about it

No repair, and no recorded run to quote. The six reproductions in this batch all ran on Latchkey latchkey-small runners, and this failure is the one we chose not to manufacture: pushing a clean runner into an exhausted address pool or a broken firewall state produces a log that documents the pushing rather than the failure. Nothing in the self-heal library targets container networking, and changing a host firewall under a running job is not a safe automatic action.

How to prevent it

  • Publish high, unconventional ports in CI and let the tests read them from the environment.
  • Bring stacks down and prune networks at the end of every job that creates them.
  • Use the runner daemon rather than a nested one wherever the job allows it.
  • Print the port list and the network list immediately before the step that starts containers.

Frequently asked questions

What does failed to set up container networking mean?
It means the daemon could not give the container the network it asked for, and the clause after the colon says why. The four we see are a port or address that cannot be bound, an exhausted address pool, firewall rules the daemon could not write, and a privileged port under a rootless daemon. Nothing in the image or the command is involved.
How do I fix could not find an available non-overlapping IPv4 address pool?
Remove the networks nobody is using, with a targeted compose down and a network prune, and check what remains. If the host genuinely needs many networks at once, set the daemon default address pools to a wider base with a smaller size per network, which the daemon reference documents as a configuration entry.
Why does docker compose up fail on networking in CI but not locally?
Because the host differs in exactly the ways this error is about. A runner has its own listening services and a fresh network table, so a port your laptop leaves free may be taken, and a nested daemon you use only in CI may lack the privileges to write firewall rules.
Does Docker need iptables to run containers?
For published ports and for outbound connectivity on a user-defined network, effectively yes on Linux: the documentation says Docker Engine creates its firewall rules using iptables by default, and the daemon reference notes that without the masquerading rules containers cannot reach external hosts on a network other than the default bridge.

Related guides

References

Exhausted address pools come from whatever ran before. Latchkey gives every job its own runner. Start free → 30-day trial · No credit card