docker-machine leverages VirtualBox to create a boot2docker VM (based on Tiny Core Linux) that exposes the required kernel facilities (e.g., cgroups and namespaces) needed by the Docker daemon. The Docker client for OSX relies on the Docker daemon running within the boot2docker VM to perform all of its actions.
A VirtualBox host-only network adapter is used to facilitate communication between the Docker client running on the physical host and the Docker daemon within the boot2docker VM. VirtualBox normally adds routes for this to the routing table. Unfortunately, the Cisco AnyConnect VPN agent manages the routing table with an iron fist and redirects all routes into its utun0
interface. This effectively cuts off communication to the Docker daemon running inside of the boot2docker VM.
$ netstat -nr
Routing tables
Internet:
Destination Gateway Flags Refs Use Netif Expire
<SNIP>
192.168.99 link#11 UC 1 0 vboxnet
192.168.99.100 8:0:27:0:ed:24 UHLWIi 3 11 vboxnet 1179
<SNIP>
$ netstat -nr
Routing tables
Internet:
Destination Gateway Flags Refs Use Netif Expire
<SNIP>
192.168.99 link#9 UCS 0 0 utun0
<SNIP>
Some additional information can be found here.
Are you using a VPN? If so, try disconnecting and see if creation will succeed without the VPN. Some VPN software aggressively controls routes and you may need to manually add the route.
A better solution than manually re-adding routes is to help VirtualBox fix the routing table. The Cisco AnyConnect VPN agent removes/redirects routes upon connection, but doesn't restore them after disconnecting. This seems to make the VirtualBox network kernel modules very unhappy. After dropping off of VPN, VirtualBox is able to add host-only network adapters, but it is NOT able to add the routes needed to connect them. This thread describes one solution.
sudo /Library/Application\ Support/VirtualBox/LaunchDaemons/VirtualBoxStartup.sh restart
The boot2docker VM is also configured with a NAT interface and VirtualBox supports the ability to forward ports between the physical host and the guest VM when using this. By default, docker-machine maps port 22
of the boot2docker VM to a randomly selected port (e.g., 49417
) on the physical host (it's also configured to only accept connections from localhost
, but that's a separate discussion).
The docker-machine ssh <machine_name>
command leverages this feature and executes a command like this behind-the-scenes:
/usr/bin/ssh -o PasswordAuthentication=no -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=quiet -o ConnectionAttempts=3 -o ConnectTimeout=10 -i /Users/jonathan.li/.docker/machine/machines/default/id_rsa -p 49417 docker@localhost
We can restore communication if we also forward the port used by the Docker daemon!
The Docker daemon and client are secured with mutual authentication. docker-machine creates the certificates for both the daemon and the client. Unfortunately, the certificate created for the Docker daemon only contains the IP address of the host-only network adapter (e.g., 192.168.99.100
). This means that the Docker client will only trust the certificate if it communicates with the Docker daemon over the host-only network adapter...which is broken by the Cisco AnyConnect VPN agent.
$ openssl x509 -noout -text -in server.pem
Certificate:
Subject: O=default
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
<SNIP>
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment, Key Agreement
X509v3 Extended Key Usage:
TLS Web Client Authentication, TLS Web Server Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Subject Alternative Name:
IP Address:192.168.99.100
Signature Algorithm: sha256WithRSAEncryption
<SNIP>
If we also forward the Docker daemon port (i.e., 2376
) between the guest VM and the physical host, the last hurdle to enabling communication between the Docker daemon and client is to create a new certificate that also supports localhost
and 127.0.0.1
.
This script will help you:
You must be disconnected from the VPN NOTE: This requirement does not apply when updating an existing VM
$ ./docker-vpn-helper
Please disconnect from the VPN and re-run the command: ./docker-vpn-helper
All VirtualBox processes must be stopped (i.e., all VMs and the GUI)
$ ./docker-vpn-helper
Please stop all VirtualBox.app processes
33540 33542 33571 36180
Your sudo password must be entered when restarting the VirtualBox kernel modules
$ ./docker-vpn-helper
=====Restarting VirtualBox kernel modules=====
Password:
Let's make sure we're starting fresh
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM
Run the helper script
$ ./docker-vpn-helper
=====Restarting VirtualBox kernel modules=====
Password:
Unloading VBoxUSB.kext
Unloading VBoxNetFlt.kext
Unloading VBoxNetAdp.kext
Unloading VBoxDrv.kext
Loading VBoxDrv.kext
Loading VBoxUSB.kext
Loading VBoxNetFlt.kext
Loading VBoxNetAdp.kext
=====[default] Creating Docker host=====
Creating VirtualBox VM...
Creating SSH key...
Starting VirtualBox VM...
Starting VM...
To see how to connect Docker to this machine, run: docker-machine env default
=====[default] Inserting Docker daemon port forwarding rule=====
=====[default] Creating a new Docker daemon certificate=====
Signature ok
subject=/CN=localhost
Getting CA Private Key
=====[default] Deploying Certificate to Docker host=====
-----BEGIN CERTIFICATE-----
<snip>
-----END CERTIFICATE-----
=====[default] Restarting Docker daemon=====
Need TLS certs for default,127.0.0.1,10.0.2.15,192.168.99.100
-------------------
=====Add these to your environment=====
export DOCKER_TLS_VERIFY=1
export DOCKER_HOST=tcp://localhost:2376
export DOCKER_CERT_PATH=/Users/jonathan.li/.docker/machine/machines/default
export DOCKER_MACHINE_NAME=default
Let's make sure a VM was created
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM
default virtualbox Running tcp://192.168.99.100:2376
Export the environment variables and test it out
$ export DOCKER_TLS_VERIFY=1
$ export DOCKER_HOST=tcp://localhost:2376
$ export DOCKER_CERT_PATH=/Users/jonathan.li/.docker/machine/machines/default
$ export DOCKER_MACHINE_NAME=default
$ docker version
Client:
Version: 1.8.2
API version: 1.20
Go version: go1.4.2
Git commit: 0a8c2e3
Built: Thu Sep 10 19:10:10 UTC 2015
OS/Arch: darwin/amd64
Server:
Version: 1.8.2
API version: 1.20
Go version: go1.4.2
Git commit: 0a8c2e3
Built: Thu Sep 10 19:10:10 UTC 2015
OS/Arch: linux/amd64
If you forget to export the environment variables
$ docker images
Get http:///var/run/docker.sock/v1.20/images/json: dial unix /var/run/docker.sock: no such file or directory.
* Are you trying to connect to a TLS-enabled daemon without TLS?
* Is your docker daemon up and running?
If you're on the VPN and export the environment variables from docker-machine env <machine_name>
instead of from this helper script
NOTE: This'll work fine if you've recently rebooted and have never connected to the VPN
$ docker images
An error occurred trying to connect: Get https://192.168.99.100:2376/v1.20/images/json: dial tcp 192.168.99.100:2376: i/o timeout
If the Docker daemon's certificate doesn't include localhost
or 127.0.0.1
$ export DOCKER_TLS_VERIFY=1
$ export DOCKER_HOST=tcp://localhost:2376
$ export DOCKER_CERT_PATH=/Users/jonathan.li/.docker/machine/machines/default
$ export DOCKER_MACHINE_NAME=default
$ docker images
An error occurred trying to connect: Get https://localhost:2376/v1.20/images/json: x509: certificate is valid for , not localhost
NOTE: You can fix this by re-running this helper script
$ ./docker-vpn-helper
=====[default] Creating a new Docker daemon certificate=====
Signature ok
subject=/CN=localhost
Getting CA Private Key
=====[default] Deploying Certificate to Docker host=====
-----BEGIN CERTIFICATE-----
<snip>
-----END CERTIFICATE-----
=====[default] Restarting Docker daemon=====
Need TLS certs for default,127.0.0.1,10.0.2.15,192.168.99.100
-------------------
=====Add these to your environment=====
export DOCKER_TLS_VERIFY=1
export DOCKER_HOST=tcp://localhost:2376
export DOCKER_CERT_PATH=/Users/jonathan.li/.docker/machine/machines/default
export DOCKER_MACHINE_NAME=default
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE