Build C++ Applications in a Linux Docker Container with Visual Studio
Docker containers provide a consistent development environment for building, testing, and deployment. The virtualized OS, file system, environment settings, libraries, and other dependencies are all encapsulated and shipped as one image that can be shared between developers and machines. This is especially useful for C++ cross-platform developers because you can target a container that runs a different operating system than the one on your development machine.
In this blog post we’re going to use Visual Studio’s native CMake support to build a simple Linux application in a Linux docker container over SSH. This post focuses on creating your first docker container and building from Visual Studio. If you’re interested in learning more about Docker as a tool to configure reproducible build environments, check out our post on using multi-stage containers for C++ development.
This workflow leverages Visual Studio’s native support for CMake, but the same instructions can be used to build a MSBuild-based Linux project in Visual Studio.
Set-up your first Linux docker container
First, we’ll set-up a Linux docker container on Windows. You will need to download the Docker Desktop Client for Windows and create a docker account if you haven’t already. See Install Docker Desktop on Windows for download information, system requirements, and installation instructions.
We’ll get started by pulling down an image of the Ubuntu OS and running a few commands. From the Windows command prompt run:
> docker pull ubuntu
This will download the latest image of Ubuntu from Docker. You can see a list of your docker images by running:
> docker images
Next, we’ll use a Dockerfile to create a custom image based on our local image of Ubuntu. Dockerfiles contain the commands used to assemble an image and allow you to automatically reproduce the same build environment from any machine. See Dockerfile reference for more information on authoring your own Dockerfiles. The following Dockerfile can be used to install Visual Studio’s required build tools and configure SSH. CMake is also a required dependency but I will deploy statically linked binaries directly from Visual Studio in a later step. Use your favorite text editor to create a file called ‘Dockerfile’ with the following content.
# our local base image FROM ubuntu LABEL description="Container for use with Visual Studio" # install build dependencies RUN apt-get update && apt-get install -y g++ rsync zip openssh-server make # configure SSH for communication with Visual Studio RUN mkdir -p /var/run/sshd RUN echo 'PasswordAuthentication yes' >> /etc/ssh/sshd_config && \ ssh-keygen -A # expose port 22 EXPOSE 22
We can then build an image based on our Dockerfile by running the following command from the directory where your Dockerfile is saved:
> docker build -t ubuntu-vs .
Next, we can run a container derived from our image:
> docker run -p 5000:22 -i -t ubuntu-vs /bin/bash
The -p flag is used to expose the container’s internal port to the host. If this step was successful, then you should automatically attach to the running container. You can stop your docker container at any time and return to the command prompt using the exit command. To reattach, run docker ps -a, docker start <container-ID>, and docker attach <container-ID> from the command prompt.
Lastly, we will interact with our docker container directly to start SSH and create a user account to use with our SSH connection. Note that you can also enable root login and start SSH from your Dockerfile if you want to avoid any manual and container-specific configuration. Replace <user-name> with the username you would like to use and run:
> service ssh start > useradd -m -d /home/<user-name> -s /bin/bash -G sudo <user-name> > passwd <user-name>
The -m and -d flags create a user with the specified home directory, and the -s flag sets the user’s default shell.
You are now ready to connect to your container from Visual Studio.
Connect to your docker container from Visual Studio
Make sure you have Visual Studio 2019 and the Linux development with C++ workload installed.
Open Visual Studio 2019 a create a new CMake Project. CMake is cross-platform and allows you to configure an application to run on both Windows and Linux.
Once the IDE has loaded, you can add a SSH connection to your Linux docker container the same way you would add any other remote connection. Navigate to the Connection Manager (Tools > Options > Cross Platform > Connection Manager) and select “Add” to add a new remote connection.
Your host name should be “localhost”, the port should be whatever you are using for your SSH connection (in this example we’re using 5000), and your username and password should match the user account that you just created for your container.
Configure build in Visual Studio
At this point the project behaves like any other CMake project in Visual Studio. To configure and build the console application in our Linux container navigate to “Manage Configurations…” in the configuration drop-down.
You can then select the green plus sign in the CMake Settings Editor to add a new “Linux-Debug” configuration. Make sure that the remote machine name of your Linux configuration matches the remote connection we created for our Linux docker container.
Save the CMake Settings Editor (ctrl + s) and select your new Linux configuration from the configuration drop-down to kick off a CMake configuration. If you don’t already have CMake installed on your docker container, then Visual Studio will prompt you to deploy statically linked binaries directly to your remote connection as a part of the configure step.
At this point you can build your application in your Linux docker container directly from Visual Studio. Additional build settings (including custom toolchain files, CMake variables, and environment variables) can be configured in the CMake Settings Editor. The underlying CMakeSettings.json file can store multiple build configurations and can be checked into source control and shared between team members.
This post showed you how to build a C++ application in a Linux docker container with Visual Studio. Stay tuned for our next post, where will we show you how to copy the build artifacts back to your local Windows machine and debug using gdbserver on a second remote system.
Give us your feedback
Do you have feedback on our Linux tooling or CMake support in Visual Studio? We’d love to hear from you to help us prioritize and build the right features for you. We can be reached via the comments below, Developer Community, email (email@example.com), and Twitter (@VisualC).
This is really cool, thanks Erika. Now I only need the last step, to deploy the built binaries to a different debug target (which in the embedded case might be to small to be able to host build tools). 🙂
My second post on Debugging Linux CMake Projects with gdbserver is now live.
Nice article, looking forward to the next one.
Hope it will be possible to do the debugging with LLDB server also.
Hi Corentin, the second post on Debugging Linux CMake Projects with gdbsever is now live. We don’t yet have support for the LLDB debugger but please do open a suggestion on Developer Community. This will allow other customers with the same request to comment on the issue and enable our team to engage with your directly. Are you working on macOS? Or is there another reason why you prefer not to use GDB?
Comparing that to my usual native Linux workflow, I can’t help but think that this cross workflow would show me down quite a bit. Notice how you don’t have those gorgeous bind mounts so you have to copy stuff around to and fro the VM where your Docker container is running.
Also notice how your Ubuntu-based image is not really reproducible because your base image isn’t pinned to a version e.g. 18.04, so it can change anytime.
As I developed for Windows, I worked a lot with VS and I loved it. I even tried SUA and it kind of worked. But this “open SSH port to the whole network” workflow is cumbersome and can kick you quite hard on the security side of things.
Thanks for the feedback!
You should be able to tie your Ubuntu-based image to a specific version by adding the version as a tag (e.g. docker pull ubuntu:1804). The rest of the feedback is valuable as we look to improve our Docker support in Visual Studio.
Debugging C# .NET CORE APIs in docker linux containers is solid. With this article debugging with gdbserver in docker linux containers is doable from within Visual Studio 2019. How about in a more complex visual studio 2019 solution where some projects are in c# and others in c++. Can visual studio 2019 be configured to do MIXED MODE DEBUGGING IN DOCKER LINUX CONTAINER(s) so you can single step into both c# and c++ code in linux docker container(s) during one debug session? Can you use two instances of visual studio 2019 to do this?
TLDR; please consider alternative docker integration strategies, and don’t let this become “the only supported docker integration scenario”.
This is a good article showing a visualstudio+docker strategy which will be suitable for a lot of use-cases. However, my feedback is to please consider supporting at least one additional strategy for visualstudio+docker. Support automation of commands similar to/related to the following:
docker run –rm -v :
docker run –rm -v :
docker run –rm -v :
Also, support the association of these commands with “configurations” which can be defined in CMakeSettings.json, and/or make these operations “integratable” with context menu’s. I’ve implemented some of this functionality using Task Runner Explorer plugin, and now Tasks.vs.json but home-growing it has a bunch of annoying consequences in an enterprise team. It’s much different if it’s a first-class feature.
I think it’s also plausible to go a step further and work out the plumbing for the GDB debugging on linux containers, and the associated settings for Launch.vs.json. But, I could see this as a later feature. The most important point I want to suggest is, please keep the door open for new ideas about docker integration.