Developing a Dockerized Asp.Net Core Application Using Visual Studio Code

I recently embarked on a mission with my colleague Kendall Roden to uncover the feature parity between Visual Studio 2017/2019 and Visual Studio Code when it comes to developing a dockerized Asp.Net Core application. We started by identifying the artifacts that get scaffolded by Visual Studio 2017/2019 which would need to be manually generated with Visual Studio Code. Here is the list that we came up with:

  • Generating the Dockerfile
  • Utilizing a Certificate from inside the docker container
  • Add ability to debug the application running inside the Docker container

We started by ensuring that the latest dotnet sdk is installed. Here is the latest version at the time of writing this post:

We won’t go into the details of generating a new ASP.Net Core application using the dotnet cli as this has been heavily documented elsewhere. Instead we will jump directly into addressing the first point on our list which is adding the Dockerfile to the project. This can either be added manually or you can use the docker extension for Visual Studio Code which can be found here. At the time of writing this post the docker extension generated a Dockerfile that utilized the microsoft/aspnetcore image instead of the newly introduced images which are now published to the Microsoft Container Registry (MCR) as discussed here. Here is the updated Dockerfile that utilizes the newly introduced images:

FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base
WORKDIR /app
EXPOSE 5000
FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build
WORKDIR /src
COPY [“wael.csproj”, “./”]
RUN dotnet restore “./wael.csproj”
COPY . .
RUN dotnet build “wael.csproj” -c Release -o /app
FROM build AS publish
RUN dotnet publish “wael.csproj” -c Release -o /app
FROM base AS final
WORKDIR /app
COPY — from=publish /app .
ENTRYPOINT [“dotnet”, “wael.dll”]

The image is built using the following command:

docker build -t myaspnetcoreapp .

After we built the docker image we tried to create a container from it using the following command:

docker run -p 5000:5000 myaspnetcoreapp

What we noticed was that it was listening on port 80 by default although we ran the same application outside the container minutes before and it was listening on port 5000.

Well it turns out that the mcr.microsoft.com/dotnet/core/aspnet:2.2 image sets the ASPNETCORE_URLS to port 80 which overrides the settings inside launchSettings.json. To change this, we modified the Dockerfile to explicitly specify the urls that we want the kestrel web server to listen on. Here is the updated Dockerfile:

FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base
WORKDIR /app
ENV ASPNETCORE_URLS http://+:5000
EXPOSE 5000
FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build
WORKDIR /src
COPY [“wael.csproj”, “./”]
RUN dotnet restore “./wael.csproj”
COPY . .
RUN dotnet build “wael.csproj” -c Release -o /app
FROM build AS publish
RUN dotnet publish “wael.csproj” -c Release -o /app
FROM base AS final
WORKDIR /app
COPY — from=publish /app .
ENTRYPOINT [“dotnet”, “wael.dll”]

Now Kestrel serves the application on port 5000 inside the container. But since the ASP.Net Core application middleware is setup (inside Startup.cs file) to redirect to https we noticed that the application was failing to determine the https port for the https redirect as you can see here:

To fix this issue we had to specify the https port to listen on. We achieved this by modifying to the Dockerfile to include the https port in addition to the http port. Also we had to expose the https port to make it accessible outside the container. Here is the updated Dockerfile:

FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base
WORKDIR /app
ENV ASPNETCORE_URLS http://+:5000;https://+:5001
EXPOSE 5000
Expose 5001
FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build
WORKDIR /src
COPY [“wael.csproj”, “./”]
RUN dotnet restore “./wael.csproj”
COPY . .
RUN dotnet build “wael.csproj” -c Release -o /app
FROM build AS publish
RUN dotnet publish “wael.csproj” -c Release -o /app
FROM base AS final
WORKDIR /app
COPY — from=publish /app .
ENTRYPOINT [“dotnet”, “wael.dll”]

Now we were ready to rerun our docker container with the https port exposed using the following command:

docker run -p 5000:5000 -p 5001:5001 myaspnetcoreapp

Whereas the container was able to determine the https port, we were presented with yet another challenge. We had to provide a certificate inside the container in order to be able to serve our https endpoint as shown here:

This brings us to the second point on our list, which is making a certificate available inside the container. Now if you recall we are trying achieve feature parity between Visual Studio 2017/2019 and Visual Studio Code, so we wanted to utilize the same certificate that is generated on our behalf when we use Visual Studio 2017/2019. Visual Studio 2017/2019 utilizes a self signed certificate which is stored in the windows certificate store. Specifically, it uses a dotnet global tool built into .NET Core 2.1 to help with certs at dev time, called “dev-certs”. You just need to run “dotnet dev-certs https — trust” and you will get a pop up asking if I want to trust the localhost certificate. On Windows it will get added to the certificate store.

You could argue that you can copy the same certificate to the container. But it turns out not to be a straightforward process as the container is running a Linux OS which doesn’t have a standard way across distros to trust the certificate, so you’ll need to perform a distro specific guidance for trusting the development certificate. Also it is considered an anti-pattern to copy a certificate into an image. It makes it harder to use the same image for testing with dev certificates and hosting with production certificates. There is also a significant risk of certificate disclosure if certificates are made part of container images. The alternative here is to run the “dev-certs” tool inside the container to create a self signed certificate. But looking at the Dockerfile we created you will notice that we are using a multi-staged docker build where the final image doesn’t include the .NET Core SDK, so the dev-certs tool is not even available inside the container. Also the same anti patterns that applied to copying apply here.

Given the limitations discussed above we opted with sharing the self signed development certificate that we have on our machine with the Docker container. We started by exporting the certificate to a pfx file, using the following command on our machine (use your own custom path on your machine):

dotnet dev-certs https -v -ep c:\cert\aspnetcore-cert.pfx -p createyourownpassword

Next we had to point Kestrel (web server built into ASP.Net Core) to the certificate location using the environment variables Kestrel__Certificates__Default__Path and Kestrel__Certificates__Default__Password and then shared the pfx file by volume mounting the certificate into the containers as shown here:

docker run -p 5000:5000 -p 5001:5001 -e Kestrel__Certificates__Default__Path=/root/.dotnet/https/aspnetcore-cert.pfx -e Kestrel__Certificates__Default__Password=createyourownpassword -v c:\cert\:/root/.dotnet/https myaspnetcoreapp

Now that we have successfully served the application on an https endpoint it was time to address the final point on our list which was debugging the application running inside the container. If you have developed an Asp.Net Core application with Visual Studio 2017/2019 you know that the debugging experience is seamless there. You simply enable docker support when you create the application and then running in debug mode will automatically attach the debugger to the application running inside the container. The latest Docker extension for Visual Studio Code added support for debugging a docker container (still in preview at the time of writing this blog post) by simply heading to the debug tab and creating a “Docker:Launch .Net Core” configuration as shown here:

We modified the default configuration that was generated by setting the proper environment variables to load the certificate as well as listen on custom ports on the host as shown below. Now clicking on the Start Debugging button should create a docker container from the Dockerfile included in your project and then launch the container with the settings included in the configuration file.

So there you have it, Visual Studio Code offers feature parity with Visual Studio 2017/2019 when it comes to developing a dockerized Asp.Net Core application. With Visual Studio Code being cross platform this means that you can develop your next dockerized Asp.Net Core application on a Linux or Mac while having access to all the great features that windows users enjoy with Visual Studio 2017/2019.

You can find the complete source code on github.