Running 16 concurrent CI shards with rootless Docker on a single host exposes two race conditions: bridge network creation failures and host port binding collisions. The network race can be mitigated with a larger default address pool in daemon.json (256 /24 networks instead of ~31) and a flock-based shim serializing docker network create calls. The host port race, caused by RootlessKit's port forwarding, cannot be solved at the client layer. The real fix is running the job itself inside a container on the same Docker network as the service containers, using service names (mysql, redis, clickhouse) instead of localhost ports — eliminating host port publishing entirely. This approach reduced burst time from 20s to 3s and achieved 16/16 success vs. 14/16 with host ports. Tested on Docker 29.6.0 rootless on Ubuntu 24.04.4, arm64.

7m read timeFrom ma.ttias.be
Post cover image
Table of contents
Two races, not one #The mitigations that only half worked #The actual fix: stop publishing host ports #Proving it #Disclaimer time ;-) #

Questions this post answers

How do I fix 'address already in use' errors when running many concurrent Docker containers with published ports on rootless Docker?

The root cause is RootlessKit's port forwarding racing to bind the same ephemeral port (e.g., 32768) across concurrent containers. The only reliable fix is to stop publishing host ports entirely. Run the job inside a container on the same Docker network as the service containers and reference services by name (e.g., mysql:3306) instead of localhost. This eliminates the RootlessKit port-forwarding path where the race occurs. Teams hitting this in dense CI pipelines track rootless Docker workarounds like this on daily.dev.

How do I fix 'all predefined address pools have been fully subnetted' in Docker when running many concurrent jobs?

Expand the default address pool in Docker's daemon.json. The default carves out only ~31 networks; setting a wider base like 10.200.0.0/16 with size 24 gives 256 /24 networks instead. Add: {"default-address-pools": [{"base": "10.200.0.0/16", "size": 24}]} to the rootless daemon's daemon.json and restart Docker. Engineers scaling self-hosted runners find configuration fixes like this faster on daily.dev.

How do I serialize docker network create calls to prevent 'operation not permitted' namespace errors in rootless Docker?

Wrap the docker CLI with a shell shim that uses flock around the network create subcommand. The shim checks if the arguments are 'network create' and, if so, acquires a per-user lock file before calling the real docker binary. This serializes concurrent network creation without affecting other docker commands, eliminating the RootlessKit network-namespace race. Platform engineers running dense rootless Docker workloads share solutions like this on daily.dev.