Bind for 127.0.0.1:80 failed when running multiple projects on secondary loopback addresses

I’m trying to set up multiple projects for development environments on my Linux Mint 22.3 laptop. I’m running Docker v29.5.2, I’ve setup rootless Docker, and enabled the capability to allow binding to privileged ports. I tend to have multiple projects I work on throughout the day, so I’m trying to configure the networking to avoid conflicts. The idea is to bind service ports on separate loopback IP addresses. For example, with two projects I’m working with today that each use Caddy, one of them looks like this:

ports:
  - "127.0.1.2:80:80"
  - "127.0.1.2:443:443"

And the other looks like this:

ports:
  - "127.0.1.3:80:80"
  - "127.0.1.3:443:443"

When I up the first one, everything works fine, docker ps -a shows the container with the exposed ports and mappings, and running sudo ss -nlpt shows:

LISTEN 0      4096       127.0.1.2:80         0.0.0.0:*    users:(("rootlesskit",pid=53401,fd=19)) 

However, when I up the second one, it ends with the error:

Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint myproj-caddy-1 (...): Bind for 127.0.0.1:80 failed: port is already allocated

I find it really odd that it’s complaining about the primary loopback address, since neither service references it (I’ve also confirmed this via docker compose config to ensure I didn’t miss anything). In my research, at least one source mentioned added the IPs specifically to the interface, so I tried that via sudo ip addr add 127.0.1.2/8 dev lo (also for .3), but that doesn’t seem to make a difference.

Am I missing something in my setup to allow the separate loopback IP binding? Is this a limitation in rootless docker? Or maybe a bug in some sort of port allocator in Docker?

Thanks in advance for any pointers.

When you use rootless Docker, your entire docker daemon runs isolated from the host. So when you set 127.0.1.2, it will use that IP in that isolated network namespace. Then I assume the actual host port is forwarded to the internal IP’s ports. You can disable it with the following command:

systemctl --user edit docker.service

and add

[Service]
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_DETACH_NETNS=false"

and restart Docker

systemctl --user restart docker

Then docker compose up will work. Maybe there are other ways as well. You can check the content of /usr/bin/dockerd-rootless.sh for the list of supported variables.

Hmm… That doesn’t seem to make a difference for me. I made sure to down the projects before making the changes, and then up’d them after, just to avoid anything possibly holding on to something. I also ran systemctl --user daemon-reload and then systemctl --user restart docker again, but to no avail.

I looked at the available environment vars. Admittedly, this is an area outside of my current skillset, so my attempts are mostly going to be shots in the dark, but I’ll try playing around with different vars. Even so, any further ideas or guidance are appreciated.

I will recheck again later to make sure my test was not just somehow accidentally working.

In the meantime, can you make a small example compose file to reproduce the issue? I made one if I misunderstood anything, it is possible that I didn’t test the right case. But it indeed failed first showing that the service only listened on 127.0.0.1 and after the attempted fix, docker compose up finished without any issue.

Please, also share the output of

docker version

which shows more than just the docker engine version.

I also tested it on Ubuntu, not on Linux Mint. Linux Mint is not officially supported, but i don’t think there would be a big difference in this case.

Here’s a simple one I just tried, and ended up with the same error:

services:
  web1:
    image: crccheck/hello-world:latest
    ports:
      - "127.0.1.2:80:8000"
  web2:
    image: crccheck/hello-world:latest
    ports:
      - "127.0.1.3:80:8000"

This is what uping it looks like:

$ docker compose up
[+] up 6/6
 ✔ Image crccheck/hello-world:latest Pulled                                                                                                   3.6s
 ✔ Network web-port-test_default     Created                                                                                                  0.0s
 ✔ Container web-port-test-web2-1    Created                                                                                                  0.1s
 ✔ Container web-port-test-web1-1    Created                                                                                                  0.1s
Attaching to web1-1, web2-1
web2-1  | httpd started
Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint web-port-test-web1-1 (8d207df9d439fefdcd58118df145bdbe0cb0f6d91c0c9791d94e65fef2ef1a45): Bind for 127.0.0.1:80 failed: port is already allocated

I tried this with DOCKERD_ROOTLESS_ROOTLESSKIT_DETACH_NETNS set to false. I also tried the DISABLE_HOST_LOOPBACK one just because it seemed possibly related, but neither one seemed to make a difference. I ran systemctl --user daemon-reload and systemctl --user restart docker between each override edit and down/up.

$ cat ~/.config/systemd/user/docker.service.d/override.conf
[Service]
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_DETACH_NETNS=false"
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_DISABLE_HOST_LOOPBACK=false"

And here’s my version info:

$ docker version
Client: Docker Engine - Community
 Version:           29.5.3
 API version:       1.54
 Go version:        go1.26.4
 Git commit:        d1c06ef
 Built:             Wed Jun  3 18:00:12 2026
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          29.5.3
  API version:      1.54 (minimum version 1.40)
  Go version:       go1.26.4
  Git commit:       285b471
  Built:            Wed Jun  3 18:00:12 2026
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v2.2.4
  GitCommit:        193637f7ee8ae5f5aa5248f49e7baa3e6164966e
 runc:
  Version:          1.3.5
  GitCommit:        v1.3.5-0-g488fc13e
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
 rootlesskit:
  Version:          3.0.1
  ApiVersion:       1.1.1
  NetworkDriver:    gvisor-tap-vsock
  PortDriver:       builtin
  StateDir:         /run/user/1000/dockerd-rootless

I’m running Linux Mint 22.3, which is basically just Ubuntu 24.04 with Cinnamon instead of GNOME, so yeah, I would think that the desktop environment would hopefully not affect the networking in this way. But do let me know if you encounter something different with Ubuntu still! I’ll spin up an old laptop with whatever you’re running just for testing if need be.

Thank you for the compose file.

I was wrong, becuse when I ran the compose up command the second time, the container started without error just also without port forwarding. So if what you experience is not a bug (still possible that it is), the fact that it allows starting the containers without port forwards definitely seems to be a bug.

I will do some more investigation before reporting any bug. This is the ps output I got after the second time

NAME           IMAGE     COMMAND                  SERVICE   CREATED          STATUS          PORTS
test-test1-1   nginx     "/docker-entrypoint.…"   test1     14 seconds ago   Up 11 seconds
test-test2-1   nginx     "/docker-entrypoint.…"   test2     14 seconds ago   Up 13 seconds   127.0.1.2:8080->80/tcp

This could be relted to this known issue: docker start ignores conflicting ports for "created" containers · Issue #51758 · moby/moby · GitHub

update:

docker compose up -d fails once and succeds the second time and fails the third time.

update2

Even rootful Docker fails, so it doesn’t look like a rootless Docker issue. I judged too quickly before my first post and I was happy that my first idea worked when it didin’t. Sorry about that.

I’m pretty sure I used the same idea before with using different IPs not ports. Although I should not say “pretty sure” anymore :slight_smile:

uping it a second time does indeed appear to work in the sense that no binding error is emitted. However, I still see only one port actually bound via sudo ss -nlpt, and using curl to test it shows the one with the error originally doesn’t allow connections.

For example:

$ sudo ss -nlpt | grep :80
LISTEN 0      4096       127.0.1.3:80         0.0.0.0:*    users:(("rootlesskit",pid=307019,fd=4))

$ curl http://127.0.1.2:80
curl: (7) Failed to connect to 127.0.1.2 port 80 after 0 ms: Couldn't connect to server

$ curl http://127.0.1.3:80
<pre>
Hello World


                                       ##         .
                                 ## ## ##        ==
                              ## ## ## ## ##    ===
                           /""""""""""""""""\___/ ===
                      ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
                           \______ o          _,/
                            \      \       _,'
                             `'--.._\..--''
</pre>

$ docker ps
CONTAINER ID   IMAGE                         COMMAND                   CREATED         STATUS                   PORTS                    NAMES
5b532c9d7e4b   crccheck/hello-world:latest   "/bin/sh -c 'echo \"h…"   6 minutes ago   Up 6 minutes (healthy)                            web-port-test-web1-1
248ccd024373   crccheck/hello-world:latest   "/bin/sh -c 'echo \"h…"   6 minutes ago   Up 6 minutes (healthy)   127.0.1.3:80->8000/tcp   web-port-test-web2-1

Does it look like this for you, too?

Yes, it does the same for me. I actually shared the same behavior in my previous post just with a different image.
I could not figure out why it happens. I tried to change port drivers as well, but nothing helped, but the port drivers required some packages I didn’t try to install so they failed.

Haha, whoops. Looks like I was multitasking too much and just focused on the “without error” part. Sorry for the confusion.

The bug report you linked to seems like it might be related, but it’s hard to say for sure since they’re discussion different symptoms.

Is the next step to do a bug report, or is there more testing you wanted to do?