Skip to content

Privileged-port forwards ignore the requested bind address and listen on 0.0.0.0 (TCP, v2.2.3) #2682

Description

@ArtemYurov

Describe the bug

On host ports below 1024, OrbStack ignores the bind address given in -p and creates the
listening socket on 0.0.0.0 instead. Above 1024 the address is honoured.

The visible consequence is that a privileged-port forward now collides with any unrelated
listener that holds the same port on a different host address. The container starts, docker ps
advertises the requested mapping, nothing is reported to the user, and no host socket exists.

This is the same specific-address/privileged-port boundary as #2567, which was closed as
"Fixed in v2.2.2". On the TCP path in v2.2.3 the failure mode has changed rather than gone:
2.2.2 fixed the silent no-socket case, but the socket that is now created is bound to the
wrong address.

To Reproduce

Host: macOS 15.7.4 (24G517), Apple Silicon (arm64), OrbStack 2.2.3, Docker Engine 29.4.0.

Unprivileged host port — bind address honoured (correct):

$ docker run --rm -d -p 127.0.0.1:8443:80 alpine sleep 25
$ docker ps --format '{{.Ports}}'
127.0.0.1:8443->80/tcp
$ netstat -an -p tcp | grep '\.8443 ' | grep LISTEN
tcp4       0      0  127.0.0.1.8443         *.*                    LISTEN

Privileged host port — bind address ignored, wildcard socket (wrong):

$ docker run --rm -d -p 127.0.0.1:444:80 alpine sleep 25
$ docker ps --format '{{.Ports}}'
127.0.0.1:444->80/tcp
$ netstat -an -p tcp | grep '\.444 ' | grep LISTEN
tcp4       0      0  *.444                  *.*                    LISTEN

Both containers request 127.0.0.1, and Docker reports 127.0.0.1 for both; only the
unprivileged one actually binds it.

How this breaks a real setup. A launch daemon on this machine holds 127.0.0.2:443
(an alias on lo0). ddev publishes its router as -p 127.0.0.1:443:443 and
-p 127.0.0.1:80:80. Port 80 comes up, port 443 does not — every ddev site is reachable
over HTTP and refuses connections over HTTPS, with nothing in docker logs or the UI to
explain it. ~/.orbstack/log/vmgr.log is the only place the failure appears:

📦 scon | level=error msg="failed to add forward" container=docker error="host: listen tcp4 0.0.0.0:443: bind: address already in use"

The same substitution is visible on port 443 itself once the conflicting daemon is stopped
and the forward succeeds — the mapping asks for 127.0.0.1, the socket is on 0.0.0.0:

$ docker port ddev-router | grep 443
443/tcp -> 127.0.0.1:443
$ netstat -an -p tcp | grep '\.443 ' | grep LISTEN
tcp4       0      0  *.443                  *.*                    LISTEN

127.0.0.1:443 was free the whole time — only 127.0.0.2:443 was taken, so the requested
forward could have been bound without any conflict. Stopping the other daemon is the only
thing that fixes it. docker.expose_ports_to_lan: false makes no difference; the socket is
still created on 0.0.0.0.

Expected behavior

-p 127.0.0.1:443:443 should bind 127.0.0.1:443 regardless of the port being privileged,
exactly as it does on 8443 — and it should not fail because an unrelated address holds the
same port number.

Secondarily: a forward that cannot be bound should surface somewhere the user can see it
(docker ps, the app UI, or an error at container start) rather than only in vmgr.log.

Related issues

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions