Getting Started with Docker in Visual Studio 2019

Development frameworks, platforms, and tools that do not offer a rich development experience will ultimately lack in adoption. Docker is an amazing technology but, what is the development experience like? Not too long ago, I wrote about creating and debugging an ASP.NET Core Docker container in two different ways.

  1. Visual Studio 2017 (Windows)
  2. Visual Studio Code (Linux)

With the launching of Visual Studio 2019 recently, I felt compelled to target one more IDE 🙂 .


The first step is making sure Visual Studio is set up correctly. This is a simple as installing Visual Studio 2019 with the .NET Core cross-platform development workload installed. More specifically, if you select Individual Components, you need to ensure that the Container Development Tools component is selected as shown below.

Lastly, you will need to have Docker Desktop for Windows installed if you haven’t already. Once this is done, we are ready to create an ASP.NET Core Docker container in Visual Studio.

Creating an ASP.NET Core Docker Container

As explained in this blog post, the new project dialog in Visual Studio has been given an overhaul. I have become quite accustomed to the previous version however, I must admit the improvements are very intuitive. For this tutorial, we will select the ASP.NET Core Web Application template and click Next.

Once this is done, we can give our new project a name, location, and solution name. As you can see, this process is much more like a wizard as opposed to the monolithic dialog that was used in previous versions.

Now we can provide some more specifics for our new application. For the purposes of this example we will select the API project template. That said, a key piece can be found in the advanced section. Here we want to select Enable Docker Support and make sure Linux is selected in the following drop-down.

Similar to when working with Visual Studio 2017, a Dockerfile is generated with four named build stages (base, build, publish, and final). Multistage builds are helpful to optimize layers and keep our Dockerfile easy to maintain.

FROM AS base

FROM AS build
COPY ["JrTech.Docker.Vs2019/JrTech.Docker.Vs2019.csproj", "JrTech.Docker.Vs2019/"]
RUN dotnet restore "JrTech.Docker.Vs2019/JrTech.Docker.Vs2019.csproj"
COPY . .
WORKDIR "/src/JrTech.Docker.Vs2019"
RUN dotnet build "JrTech.Docker.Vs2019.csproj" -c Release -o /app

FROM build AS publish
RUN dotnet publish "JrTech.Docker.Vs2019.csproj" -c Release -o /app

FROM base AS final
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "JrTech.Docker.Vs2019.dll"]

Next lets take a look at how our application gets built and deployed as a container.

Building The Container

Prior to building or debugging our application, we will already notice some activity in the Container Tools output in the output window.

========== Checking for Container Prerequisites ==========
Verifying that Docker Desktop is installed...
Docker Desktop is installed.
========== Verifying that Docker Desktop is running... ==========
Verifying that Docker Desktop is running...
Docker Desktop is running.
========== Verifying Docker OS ==========
Verifying that Docker Desktop's operating system mode matches the project's target operating system...
Docker Desktop's operating system mode matches the project's target operating system.
========== Pulling Required Images ==========
Checking for missing Docker images...
Docker images are ready.
========== Warming up container(s) for JrTech.Docker.Vs2019 ==========
Starting up container(s)...
docker build -f "C:\Users\jason\source\repos\JrTech.Docker.Vs2019\JrTech.Docker.Vs2019\Dockerfile" -t jrtechdockervs2019:dev --target base  --label "" --label "" "C:\Users\jason\source\repos\JrTech.Docker.Vs2019" 
Sending build context to Docker daemon  18.94kB

Step 1/6 : FROM AS base
 ---> 9a8e320a271f
Step 2/6 : WORKDIR /app
 ---> Using cache
 ---> 3bca35715a51
Step 3/6 : EXPOSE 80
 ---> Using cache
 ---> 854d77a40024
Step 4/6 : EXPOSE 443
 ---> Using cache
 ---> 962750b42169
Step 5/6 : LABEL
 ---> Using cache
 ---> 09e977d58879
Step 6/6 : LABEL
 ---> Using cache
 ---> 5053ced48dc0
Successfully built 5053ced48dc0
Successfully tagged jrtechdockervs2019:dev
SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.
docker run -dt -v "C:\Users\jason\vsdbg\vs2017u5:/remote_debugger:rw" -v "C:\Users\jason\source\repos\JrTech.Docker.Vs2019\JrTech.Docker.Vs2019:/app" -v "C:\Users\jason\AppData\Roaming\Microsoft\UserSecrets:/root/.microsoft/usersecrets:ro" -v "C:\Users\jason\AppData\Roaming\ASP.NET\Https:/root/.aspnet/https:ro" -v "C:\Users\jason\.nuget\packages\:/root/.nuget/fallbackpackages2" -v "C:\Program Files\dotnet\sdk\NuGetFallbackFolder:/root/.nuget/fallbackpackages" -e "DOTNET_USE_POLLING_FILE_WATCHER=1" -e "ASPNETCORE_ENVIRONMENT=Development" -e "NUGET_PACKAGES=/root/.nuget/fallbackpackages2" -e "NUGET_FALLBACK_PACKAGES=/root/.nuget/fallbackpackages;/root/.nuget/fallbackpackages2" -p 49558:80 -p 44304:443 --entrypoint tail jrtechdockervs2019:dev -f /dev/null 
Container started successfully.
========== Finished ==========

This is a new optimization added to Visual Studio 2019. In order to allow our application to build, deploy, and run quickly Visual Studio preemptively creates a container. We can see the container by running docker ps from the command line.

λ  docker ps
CONTAINER ID        IMAGE                    COMMAND               CREATED             STATUS              PORTS                                           NAMES
e93c776e500a        jrtechdockervs2019:dev   "tail -f /dev/null"   20 seconds ago      Up 19 seconds>80/tcp,>443/tcp   flamboyant_shirley

With the http and https ports exposed, the container is primed and ready to go. If we open a browser and browse to the http port, 49558 in my case, we see that we do not get a response yet. This makes sense because while our container is started, nothing is actually deployed to it yet.

To build and deploy our application to the running container, we must debug using the Docker configuration profile. This should be selected by default. Once we are up and running, we can see that our application is available through http/https ports that were exposed in our Docker container.

We can also see that the same container that was started when we created our application is still running. When we run our application a new container isn’t created, rather the output from our project is copied into the running container. We can see this by observing the running containers which shows or original container is still running.

λ  docker ps
CONTAINER ID        IMAGE                    COMMAND               CREATED             STATUS              PORTS                                           NAMES
e93c776e500a        jrtechdockervs2019:dev   "tail -f /dev/null"   7 minutes ago       Up 7 minutes>80/tcp,>443/tcp   flamboyant_shirley

Visual Studio remotely attaches to the process running inside the container. This gives us the ability to set breakpoints and debug our application while it is running.

The development experience in Visual Studio 2019 is very similar to Visual Studio 2017 as it pertains to building Docker containers. That said, there are some nice enhancements under the hood that make the development process even more seemless. It is great to Microsoft’s continued investment in this great new technology!

Happy Coding!