--net host
would be awesome, but it is also extremely complex.
To understand why, let’s examine how Docker Mac works, in particular the network stack.
Docker Mac includes a Linux VM (nicknamed “Moby”). It runs as a regular process on OS X. If you have already used something like QEMU, this is very similar: a regular process, running as a regular user (not root, no special privilege). If you already used QEMU, you know that it is extremely slow and limited. To make Docker Mac usable, it relies on Hypervisor.framework (which gives access to virtualization instructions; think of it as hardware acceleration for VMs!) and on a number of other subsystems.
One subsystem named VPNKit is responsible for network connectivity. On a traditional VM environment, when a process inside the VM tries to send packets to the outside world, the packets go to a virtual network interface, are passed to the hypervisor, and the hypervisor injects the packets on your local network interface. Injecting arbitrary packets is not something that ordinary (non-privileged) processes can do. Therefore, when a process inside Moby (the Linux VM) tries to connect to the outside world, VPNKit intercepts the outgoing traffic, and reconstructs it using normal (non-privileged) system calls. In other words, when you do curl google.com
in a container on Docker Mac, VPNKit will:
- detect that you are trying to open a TCP connection to the outside world
- establish a TCP connection (using normal system API calls like
socket()
, connect()
, etc.)
- reconstruct the TCP session so that the Linux VM (and ultimately your container running
curl
) thinks that it’s talking to the actual service
This is (kind of!) similar to masquerading, but taken to the “network traffic analysis / userland reconstruction” level. The idea is not new; this is how QEMU “slirp mode” works.
By the way, VPNKit is called VPNKit because this technique (which might seem overly complicated!) is the only way to make Docker Mac work reliably with all kinds of VPNs out there. If you are using any kind of enterprise VPN, you need this special mode to get your container traffic going.
Now, why is --net host
complicated? Because VPNKit works automatically for outgoing traffic, but it needs help for incoming traffic. When an external connection is made to, e.g. port 80, VPNKit won’t be able to handle it, unless it’s already listening on port 80. When you start a container with -p 80:1234
, VPNKit is informed that you want to expose something on port 80, and it will setup a listening socket there. Otherwise (e.g. if you run a container in host mode) it has no way to know.
So, what’s the solution?
-
If you don’t need the upsides of VPNKit, you can always revert back to Docker-in-Virtualbox using the Docker Toolbox. In fact, I have Docker Mac and the Docker Toolbox installed side by side on this machine and it works like a charm. Problem solved!
-
If you have ideas to make it work, you can contribute to VPNKit. It is open source, and your contributions will make Docker Mac better, but also be usable by other projects (e.g. if some day other container engines try to have a native experience like Docker Mac).
-
If you think that Docker Inc. (the company) should spend more time and resources on this, you can vote with your wallet Keep in mind that Docker Inc. does not charge a cent for Docker Mac, and that while Docker Mac itself is not open source, all the key components (VPNKit and others) are open source and available to the community.
Thank you!