Using Docker tools for Visual Studio with a Hyper-V based Docker host
In the past few weeks I’ve been playing around with containerizing an ASP.NET Core application using the Docker tools for Visual Studio. This allows you to develop and debug your app locally inside a Docker container. To do this, you’ll need a local Docker host. While you could ask your IT department to provide one for you, I found it much more convenient to run a virtual machine locally on my laptop, so I have it available everywhere I go. To create a local Docker host, you need to use the Docker Toolbox. This will use VirtualBox to create a local virtual machine which will serve as your Docker host. However, I already had Hyper-V installed as a virtualization hypervisor. Hyper-V works great on Windows 10, so I wanted to keep that. Sadly, VirtualBox doesn’t play nice with Hyper-V (in short, VirtualBox won’t install if Hyper-V is enabled).
Solution: create a local Docker host on Hyper-V. Unfortunately, the process is a little finicky, so I thought I’d describe it here.
(Note: of course you can also use Azure to run a Docker host. However, for the “Edit & Refresh” experience to function, you need a shared drive between your Docker host and your local development machine. Because of common firewall/network restrictions, this is often not so easy to achieve with an Azure VM. That’s when a local Docker host is nice to have.)
You’ll need to install a few bits before you can work with the Docker tools for Visual Studio:
- Visual Studio 2015 Update 2 (or higher)
- .Net Core SDK RC2 (or higher)
- Docker Toolbox
- Enable Hyper-V on your development machine
Setting up the network
Next thing to do is create a local Docker host. First, let’s get our Hyper-V infrastructure set up.
We’ll need to create a Virtual Switch to which the Docker host will connect, and make sure that it has internet access. Inside Hyper-V manager open up the Virtual Switch Manager and create a new virtual switch. Give it a name and make sure that it’s an Internal network:
We’re using an internal network here, to make sure that the IP address stays the same, even through reboots or when you connect your development machine to a different network. The downside of using an internal network is that it’s not connected to your external network, and so things like downloading Docker images from public repositories or restoring NuGet packages from public feeds will not work. To overcome this, we can use the Internet Connection Sharing feature to share our internet connection with the newly created internal network.
Open up the “View network connections” window (in Windows 10, just search for “network” and it will appear):
From there, open up the properties of the adapter which is your internet connection (in my case it’s a bridged adapter) and enable Internet Connection Sharing from the Sharing tab. Select your internal network as the Home networking connection:
Create the Docker host virtual machine
Next thing to do is create the virtual machine that will be your Docker host. The Docker Toolbox provides a nice little commandline tool to manage Docker hosts: docker-machine. You can use this to create, start, stop or delete a Docker host.
Open up a PowerShell prompt and issue the command to create a Docker host:
$ docker-machine create --driver hyperv --hyperv-virtual-switch "<your virtual switch>" <your vm name>
It will take a bit of time, but when it completes you should see your new VM running in Hyper-V manager:
The last thing to do is set your environment to use the newly created VM as the active machine. You can do this by typing in a PowerShell prompt:
$ docker-machine env <your vm name> | Invoke-Expression
Afterwards you can type “docker-machine ps” to verify that your new VM is indeed active:
Sharing a drive to make “Edit & Refresh” work
To make the “Edit & Refresh” experience work, the Docker tools for Visual Studio expect that the folder in which you keep your code on your development machine is shared to the Docker host VM at the same path. The Docker toolbox takes care of this automatically if you use VirtualBox. However, when using Hyper-V you will need to take care of this yourself.
In my case, I keep all my code on my Windows machine under “D:Git”. We’ll need to share this to the Docker host VM, but since this is a Linux VM, the path will look a little bit different: “/d/Git”. We’ll use normal Windows network sharing to achieve this.
First, share the folder on your Windows machine. Open up the properties of the folder and go to the “Sharing” tab. From there, share the folder:
Now, we need to connect to this shared drive from the Docker host. To do this, first connect to your Docker host using SSH. You can easily do this by using:
$ docker-machine ssh <your vm name>
Then, create the Linux equivalent path of your shared folder (in my case “/d/Git”) and then connect to it using “mount”:
$ sudo mkdir –p <your path> $ sudo mount –t cifs //192.168.137.1/Git /d/Git -o user=keesv,pass=<your password>,domain=<your domain>
Of course replacing the correct IP (of your Windows development machine), paths, username and password. If you’re not using a domain account, you can leave out the “,domain=<your domain>” part. Now check that the contents of the shared folder is available by using “ls /d/Git”:
You’ll notice that the shared drive is no longer connected when you restart your Docker host VM. To avoid having to reconnect it every time, we can make the connection persistent. To do this (while still connected through SSH), create a script which will be called upon each boot:
$ sudo touch /mnt/sda1/var/lib/boot2docker/bootlocal.sh
Then open up the file for editing (I’ll use vi here, since it’s available by default):
$ sudo vi /mnt/sda1/var/lib/boot2docker/bootlocal.sh
To start editing in vi, hit “i”. Then type the following (replacing for your configuration where appropriate):
mkdir –p /d/Git mount –t cifs //192.168.137.1/Git /d/Git -o user=keesv,pass=<your password>,domain=<your domain>
Exit vi by typing Ctrl + c, then “:wq” and hit <enter>.
Now reboot your VM, SSH back in and check that the shared folder is available:
Running an ASP.NET Core app
To see that all the bits are working together, we’ll create a very basic ASP.NET Core app and run it in a Docker container on our freshly created Docker host.
First, create the app. Make sure that it’s created inside the folder that you just shared to your Docker host!
Then, use the Docker tools for Visual Studio to add Docker support to the project:
We’ll need to specify the Docker host on which we want to run the container. This needs to be a host which is known by docker-machine. Open up the “Docker.props” file and change the machine Docker machine name:
You’ll need to restart Visual Studio after making this change, so do that now. When you have your project re-opened, set the debugging target to “Docker” and hit F5:
This will build the Docker image inside your Docker host VM and then run a container based on it. After a while, the web page should pop up:
Notice that the Visual Studio debugger is connected to your app running inside the container, so you can set breakpoints, use watches and all the other goodies that make Visual Studio so nice for debugging!