When you expose container ports using Docker's -p
flag, you'll notice docker-proxy
processes appearing in your process list. Each published port gets its own proxy process, as shown in your example:
docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 22222 -container-ip 172.17.0.2 -container-port 22
docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 5555 -container-ip 172.17.0.3 -container-port 5555
Docker uses two complementary mechanisms for port forwarding:
- iptables NAT rules (primary method)
- docker-proxy (fallback method)
The iptables rules handle most traffic efficiently in kernel space, while the userspace proxy serves important edge cases:
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 127.0.0.1 tcp dpt:22222 to:172.17.0.2:22
The userspace proxy handles scenarios where pure iptables would fail:
- Localhost traffic: iptables NAT rules don't apply to connections from localhost
- IPv6 support: Some systems need userspace proxy for IPv6 compatibility
- Complex routing: When packets bypass standard network paths
While userspace proxying adds overhead, Docker's implementation is optimized:
# Compare performance:
$ iperf -s -p 5555 # Native
$ docker run -p 5555:5555 network-test iperf -s -p 5555 # Through proxy
For most workloads, the difference is negligible (1-3% overhead). The proxy only handles traffic that couldn't be routed via iptables.
You can control proxy behavior with these Docker daemon flags:
--userland-proxy=true|false
--userland-proxy-path="/usr/libexec/docker/docker-proxy"
Disabling the proxy (--userland-proxy=false
) improves performance but breaks localhost access to containers.
When debugging connection problems:
# Check active proxies:
$ sudo ss -tulnp | grep docker-proxy
# Monitor traffic:
$ sudo strace -p $(pgrep -f "docker-proxy.*5555")
Remember that published ports appear in three places: proxy processes, iptables rules, and Docker's internal state.
When you run containers with published ports (-p
flag), Docker creates a docker-proxy
process for each port mapping. This becomes visible when you inspect running processes:
$ ps -Af | grep proxy
root 4776 1987 0 01:25 ? 00:00:00 docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 22222 -container-ip 172.17.0.2 -container-port 22
root 4829 1987 0 01:25 ? 00:00:00 docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 5555 -container-ip 172.17.0.3 -container-port 5555
The traffic flow works like this:
- External request hits host IP:port (e.g., 127.0.0.1:22222)
- iptables DNAT rule redirects to docker-proxy
- docker-proxy forwards to container (172.17.0.2:22)
- Response travels back through the same path
Here are the relevant iptables rules Docker creates:
$ sudo iptables -t nat -L -n -v
Chain PREROUTING (policy ACCEPT 1 packets, 263 bytes)
pkts bytes target prot opt in out source destination
0 0 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain OUTPUT (policy ACCEPT 1748 packets, 139K bytes)
pkts bytes target prot opt in out source destination
32 7200 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 127.0.0.1 tcp dpt:22222 to:172.17.0.2:22
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 127.0.0.1 tcp dpt:5555 to:172.17.0.3:5555
The userspace proxy becomes crucial in these scenarios:
- When using
--net=host
but still needing port publishing - On systems without hairpin NAT support
- When dealing with ICMP or non-TCP/UDP protocols
- For localhost-bound services (
-p 127.0.0.1:8080:80
)
While docker-proxy adds some overhead, it's generally minimal for most workloads. You can benchmark the difference:
$ docker run -d -p 8080:80 nginx
$ ab -n 10000 -c 100 http://localhost:8080/
$ docker run --net=host nginx
$ ab -n 10000 -c 100 http://localhost:80/
For high-performance scenarios, consider these alternatives:
# Disable userland proxy globally
dockerd --userland-proxy=false
# Disable for specific container
docker run -p 8080:80/tcp --userland-proxy=false nginx