Debugging Docker containers from Visual Studio
I use Visual Studio a fair bit at work during development. Most of the code I write however actually ends up running on some kind of Linux. Microsoft has over the last few years really embraced Linux as an operating system in pretty much every respect including, as it turns out, when it comes to developing native Linux applications. Consider the Visual C++ for Linux Development extension for Visual Studio for instance - which is freely available for all users (including the free community edition). This extension makes it remarkably straightforward to use Visual Studio as your IDE for building and debugging native Linux applications. Information on how you can get setup with this extension and use it to build native Linux apps is available as a blog post.
While this is great, I wanted to see how far I can get trying to get Visual Studio to connect and remote-debug a native app running inside a Linux Docker container. As it turns out there's an informative blog post on this topic as well. The basic idea is that we build a Docker image with all the development tools we need along with an SSH server and then we spin it up and remote debug from Visual Studio like how we do with normal Linux servers. Visual Studio remains oblivious to the fact that the program is running inside a Docker container.
Secure computing mode policies
When I attempted to follow along with what's in the blog post however I ran into the following error when I tried to launch the debugger:
Unable to start debugging. Unexpected GDB output from command "-interpreter-exec console "target remote localhost:18358"". Remote connection closed
After much googling I stumbled upon an image in Docker hub which included some instructions as to how we are to use that image to debug from Visual Studio. Here's the Docker run command that was being proposed that we use:
docker run -d -p 12345:22 --security-opt seccomp:unconfined ducatel/visual-studio-linux-build-box
The specific option of interest is --security-opt seccomp:unconfined
. I use Docker for Windows as my local Docker installation. Docker for Windows makes it drop-dead easy to get going with a fully functional Docker environment. You download and install an MSI (or a DMG on macOS to get Docker setup on your Mac) and everything pretty much just works. As it turns out this results in the creation of a Hyper-V virtual machine (VM) on my Windows host running a MobyLinux distribution. The Linux kernel in the VM appears to have seccomp enabled. Secure computing mode or seccomp is an execution mode on Linux that restricts the set of system calls a given process is allowed to make. One can define a "seccomp profile" to be explicit about exactly which system calls are allowed and which aren't.
Docker now has built in support for seccomp, i.e., it spins up containers with a default seccomp profile enabled. While this default profile tends to work well for a vast majority of the use cases out there, it so happens that it disables some system calls that are necessary for enabling remote debugging from Visual Studio. Passing --security-opt seccomp:unconfined
while running a container essentially causes Docker to disable seccomp restrictions on the container.
After disabling the seccomp profile Visual Studio was able to connect and remote debug perfectly. Visual Studio also includes a built-in Linux Console which is essentially a shell connected to your program's standard input and output (yep, input too!). This means you get to see the console output right there within Visual Studio and you can interactively respond to your program as well.
Generating Dockerfiles for your applications
After having figured out how to get remote debugging working I wanted to be able to automate the creation of Dockerfile definitions for my needs. I put together a small Node.js app to implement this automation. The source is hosted up on GitHub here:
To run the app do the following:
Install a recent version of Node.js
Install the Yarn package manager (if you prefer npm however then that should work too - just replace running
yarn
below withnpm install
)Clone the repo
The repo includes a file called Dockerfile.template
which you can customize to fit your needs. Just make sure you don’t remove the instructions already there for setting up an SSH server. Now open a terminal/command prompt and run the following from the folder where you cloned the repo:
$ yarn
$ node app.js
This will produce output that looks like this:
$ node app.js
[*] Reading Dockerfile template.
[*] Creating output folder C:\code\docker-linux-dev-image\output\HyoI9qTUe.
[*] Generating new keypair.
[*] Saving private key file.
[*] Saving public key file.
[*] Generating Dockerfile.
> The SSH keys and Dockerfile are in the folder C:\code\docker-linux-dev-image\output\HyoI9qTUe.
The generated Dockerfile
along with SSH keys is dropped into the output folder. CD into the output folder and build the Docker image the usual way:
$ cd output/HyoI9qTUe
$ docker build -t vsdebug .
If everything goes well you'll find the newly minted image in your Docker engine. Now to run a container from this image you'd run the following command:
docker run -d -p 2222:22 \
--security-opt seccomp:unconfined \
vsdebug
This will spin a container up with an SSH server listening on local port 2222
. You can test this out by SSHing to this server like so:
$ cd output/HyoI9qTUe
$ ssh -i id_rsa -p 2222 root@localhost
That’s it! Now you should be able to remote debug your apps to your heart’s content. Combining this with the support for volume mounting Windows folders into a Linux Docker container using Docker for Windows, I am able to get the perfect setup! You first enable sharing your Windows drives with Docker from the Settings screen in Docker for Windows.
And then you spin your container up like so and you find yourself lost in debugging nirvana!
$ docker run -d -p 2222:22 \
-v c:/code/my-linux-app:/code/my-linux-app \
--security-opt seccomp:unconfined \
vsdebug
Hope this helps you get a jump-start with your own Visual Studio/Windows/Linux development setup.