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.
- Visual Studio 2017 (Windows)
- 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 mcr.microsoft.com/dotnet/core/aspnet:2.1-stretch-slim AS base WORKDIR /app EXPOSE 80 EXPOSE 443 FROM mcr.microsoft.com/dotnet/core/sdk:2.1-stretch AS build WORKDIR /src 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 WORKDIR /app 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 "com.microsoft.created-by=visual-studio" --label "com.microsoft.visual-studio.project-name=JrTech.Docker.Vs2019" "C:\Users\jason\source\repos\JrTech.Docker.Vs2019" Sending build context to Docker daemon 18.94kB Step 1/6 : FROM mcr.microsoft.com/dotnet/core/aspnet:2.1-stretch-slim 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 com.microsoft.created-by=visual-studio ---> Using cache ---> 09e977d58879 Step 6/6 : LABEL com.microsoft.visual-studio.project-name=JrTech.Docker.Vs2019 ---> 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 e93c776e500a98321e35fa1c02d2d89e6f64ff92f4c3e945f635f9c590f17d70 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.
E:\Software\cmder_mini λ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e93c776e500a jrtechdockervs2019:dev "tail -f /dev/null" 20 seconds ago Up 19 seconds 0.0.0.0:49558->80/tcp, 0.0.0.0:44304->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.
E:\Software\cmder_mini λ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e93c776e500a jrtechdockervs2019:dev "tail -f /dev/null" 7 minutes ago Up 7 minutes 0.0.0.0:49558->80/tcp, 0.0.0.0:44304->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!