As a developer, I'm generally pretty happy with my Mac as a development machine. As I spend more time with Docker though, I certainly wish that I could run my containers natively on the Mac. Unfortunately, OSX is not Linux and it simply doesn't have the kernel features that are required to run Docker containers.
As a result, we Mac users typically find ourselves running a Linux distro in a VM in order to get our Docker fix. The boot2docker tool makes this about as easy as it can be by provisioning a Tiny Core Linux virtual machine running the Docker daemon and installing a Mac version of the Docker client which will communicate with that daemon.
The end result is that it (almost) feels like we're running Docker containers natively on the Mac -- the client tool runs in the local terminal and transparently interacts with the Docker server running inside the VM. For most people boot2docker is the perfect tool for working with Docker on the Mac (or Windows for that matter).
There are, however, a few scenarios where boot2docker doesn't work. Here at CenturyLink Labs we've spent a lot of time recently developing against some of the features unique to CoreOS. So I'm running CoreOS instead of boot2docker's Tiny Core Linux as my Docker host. Similarly, you may already have an existing Ubuntu VM with Docker that you'd like to use. In these cases, you're typically stuck having to log into your VM and executing your Docker commands there.
So what do you do when you want that native-ish experience on your Mac but you're running a Docker host that doesn't work with boot2docker? Let's have a look.
The key to making this work is recognizing that the Docker client does all of its interaction with the Docker daemon through a RESTful API. Any time you use
docker build or
docker run the Docker client is simply issuing HTTP requests to the Docker Remote API on your behalf.
So the trick to getting a Docker client running on your Mac to interact with a Docker host in a virtual machine is to make sure that the API is accessible and the client knows how to find it. This is work that boot2docker will do automatically but I'll explain how to set it up manually in the sections below.
TCP Port Binding
In most installations the Docker API is configured by default to listen on
unix:///var/run/docker.sock which only allows local connections by the root user. The first step is to bind the Docker daemon to a TCP port so that the client (running outside the VM) can interact with it.
Note: This is most definitely NOT something that you want to do in a production environment as it effectively gives full Docker daemon access to anyone who can reach that port. However, there should be little danger in doing this on a VM which is only accessible to your local machine. To do this in CoreOS (or any other platform where the Docker daemon is being managed by systemd) you'll need to create a file named
/etc/systemd/system/docker-tcp.socket with the following contents:
[Unit] Description=Docker Socket for the API [Socket] ListenStream=2375 BindIPv6Only=both Service=docker.service [Install] WantedBy=sockets.target
Then enable the socket binding by issuing the following commands:
sudo systemctl enable docker-tcp.socket sudo systemctl stop docker sudo systemctl start docker-tcp.socket sudo systemctl start docker
Note: When executing the steps above you may find that the docker service starts again immediately after the
systemctl stop docker command. If this happens, the attempt to start the docker-tcp.socket may fail. If you experience this, try stopping any running containers you have before executing these steps.
There are instructions on the CoreOS site for doing this set-up automatically via cloud-config in case you don't want to manually set it up every time you start a new CoreOS instance.
On Ubuntu you can edit the
/etc/default/docker.io config file and add the following line:
Then restart the Docker daemon:
sudo service docker.io restart
You can check that your new port binding is functional by running the Docker client with a
-H flag that points to the TCP port:
docker -H tcp://127.0.0.1:2375 ps
If you're using some other platform or manually starting the Docker daemon, there are some general instructions on the Docker site for binding to a different port.
Regardless of which method you use to set-up the binding make sure you use port number 2375 as this is the port that has been registered with the IANA specifically for the Docker API and should help ensure that you don't run into conflicts with other services you may be running.
Now that you have the Docker daemon listening on a TCP port the next step is to make that port visible to your Mac. If your virtual machine was set-up with private or host-only networking you can skip this step -- the Docker port should already be accessible on the private network.
However, if you are using NAT networking (which is the default when using VirtualBox) you'll need to make sure that the Docker port is forwarded from the virtual machine to the host operating system. There are a few different ways to configure this mapping which we'll discuss below (all the instructions that follow assume that you're using VirtualBox as your VM platform).
The first option is to do it from the terminal with the
VBoxManage controlvm vm_name natpf1 docker,tcp,,2375,,2375
This invokes the VirutalBox command
controlvm to add a port forwarding rule to the VM named vm_name. The rule is named docker, the protocol is set to tcp and port 2375 on the host is forwarded to port 2375 in the guest OS. You can find the names of all your virtual machines by using the
VBoxManage list vms command.
Note: The double commas
,, in the command above are intentional and used to indicate that we're omitting some optional parameters.
Alternatively, the port mapping can be managed via the VirtualBox GUI. Simply open the settings for the virtual machine, navigate to the Network tab and click the Port Forwarding button. From there, insert a new rule that matches the one named docker shown below
Finally, if you are using Vagrant to manage your virtual machines, you can add the following port forwarding instruction to the
configure block in your Vagrantfile:
config.vm.network "forwarded_port", guest: 2375, host: 2375
You can test your port forwarding by opening a terminal on your Mac and issuing the following curl command:
If you get back a JSON response containing version information about your Docker installation you'll know everything is working properly.
With the Docker API now accessible to your Mac, the next step is to install the Docker client application. The easiest way to get the Docker client is to use the Homebrew package manager:
brew install docker
Pay attention to the version of the Docker client that is installed by Homebrew. It is important that the client version match the version of Docker that is running in your virtual machine. If they don't match you may need to update your Homebrew formulas or the version of Docker installed inside the VM.
With the Docker client installed, you should be able to test the installation by issuing the following command:
docker -H tcp://localhost:2375 ps
-H flag instructs the Docker client to connect to the API at the specified endpoint (instead of the default UNIX socket). If your VM is using private/host-only networking, you'll want to substitute your VM's IP address for localhost in the command above.
At this point you should have a working Docker client which is able to communicate with the Docker API running inside a virtual machine. The only downside is that the
-H flag is now needed for each docker command you issue. The good news is that you can omit the
-H altogether by simply setting an environment variable that is read by the Docker client. From the Mac terminal, do the following:
Again, if you're using private networking, substitute your VM's IP address for localhost in the command above.
Now try the
docker ps again, but without the
-H flag this time. To make this environment variable permanent, simply add the line above to your
When you installed Docker with Homebrew you may have seen a message along the lines of:
Bash completion has been installed to: /usr/local/etc/bash_completion.d
Using the Bash completion script isn't strictly necessary but does make the Docker client a lot nicer to use on your Mac. If you source this file you'll get tab completion for most of the Docker commands as well as image and container names.
To make sure Docker tab completion is available any time you open a new terminal session you can paste the following three lines into your
if [ -f `brew --prefix`/etc/bash_completion.d/docker ]; then . `brew --prefix`/etc/bash_completion.d/docker fi
If everything has gone smoothly you should now have a boot2docker-like experience on your Mac with the Docker host of your choice. Enjoy!