Creating A .NET 5 ASP.NET Core App Running In Docker On A Mac

 |  .NET 5, ASP.NET Core, Docker, Mac

This article covers creating a new .NET 5 ASP.NET Core website in Visual Studio 2019 for Mac and running this website from both Visual Studio and from a Docker image.

Before we start, the following prerequisites need to be installed:


Create a new project and solution

Create a new “Web Application” project. Screenshot of VS2019 new project dialog box

Use the target framework of “.NET 5” and “No Authentication”. Screenshot of VS2019 configure new app dialog box

Name the project core5-website in a new solution core5-docker-playarea. Screenshot of VS2019 project and solution name dialog box

Now if you run the project from Visual Studio, after accepting any development certificate prompts displayed, your browser should load the “Welcome” page on the URL https://localhost:5001

ASP.NET Core welcome page

The site is actually running in HTTP mode on port 5000 (http://localhost:5000) as well as HTTPS mode on port 5001 (https://localhost:5001). The website is configured to redirect HTTP requests to HTTPS via the HTTPS Redirection Middleware (UseHttpsRedirection) in Startup.cs.

We’ll make a change to the “Welcome” page to output the current time and to output (via an environment variable) whether the code is running in a container. Add the following code to the /Pages/Index.cshtml page:

@{
ViewData["Title"] = "Home page";
var runningInContainer = Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true";
}

<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
<p>Current time: @DateTime.Now.ToLongTimeString()</p>
<p>Running in container: @runningInContainer</p>
</div>

Now when you run the site from Visual Studio, the “Welcome” page should display the time as well as detail that the website it’s not running in a container.

ASP.NET Core welcome page with changes

Create a Dockerfile file

We now want to take this .NET 5 ASP.NET Core website and run it up in Docker. The first step to do this is to create a Dockerfile file at the solution root to hold the commands needed to build a Docker image:

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /DockerSource

# Copy csproj and restore as distinct layers
COPY *.sln .
COPY core5-website/*.csproj ./core5-website/
RUN dotnet restore

# Copy everything else and build website
COPY core5-website/. ./core5-website/
WORKDIR /DockerSource/core5-website
RUN dotnet publish -c release -o /DockerOutput/Website --no-restore

# Final stage / image
FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /DockerOutput/Website
COPY --from=build /DockerOutput/Website ./
ENTRYPOINT ["dotnet", "core5-website.dll"]

These commands can look a bit cryptic so I’ll explain them individually.

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build

Specifies the base image to use for our image. In our case, we’re using the .NET 5 SDK image provided by Microsoft. The SDK image is being used so we can compile the app but we’ll actually swap to the .NET 5 runtime base image later as this is smaller so will result in smaller output image.

WORKDIR /DockerSource

In the image we’re creating, change to the /DockerSource folder. WORKDIR will create this folder for us if it doesn’t exist.

COPY *.sln .
COPY core5-website/*.csproj ./core5-website/

Copy the solution file to the current image folder (/DockerSource) and copy the project file to the folder /DockerSource/core5-website in the image. COPY like WORKDIR will create a target folder if it doesn’t exist in the image.

RUN dotnet restore

Restore NuGet dependencies. In the previous step, we individually copied the solution and project files to form a separate layer to take advantage of Docker’s build cache. If neither of these files change then Docker knows dotnet restore will yield the same output so can safely bypass this command and use the built-in cache for the corresponding dotnet restore layer instead.

COPY core5-website/. ./core5-website/

Copy all the files from the core5-website project to the to the folder /DockerSource/core5-website in the image.

WORKDIR /DockerSource/core5-website
RUN dotnet publish -c release -o /DockerOutput/Website --no-restore

Swap to the /DockerSource/core5-website folder and compile the website with the output going to the /DockerOutput/Website folder in the image. The --no-restore option prevents the NuGet packages from being restored again as this was done previously in an earlier command.

FROM mcr.microsoft.com/dotnet/aspnet:5.0

Now we’ve compiled and published the website, use the .NET 5 ASP.NET Core runtime image provided by Microsoft as the base image (rather than the SDK image) to reduce the size of the final image created. The use of multiple FROM statements in the Dockerfile is known as a multi-stage build.

WORKDIR /DockerOutput/Website
COPY --from=build /DockerOutput/Website ./

Copy the artifacts from the previous stage of the multi-stage build.

ENTRYPOINT ["dotnet", "core5-website.dll"]

And the last command ENTRYPOINT takes an array of values with the first being the executable to run and the remaining values the arguments to use. When the container starts, the ENTRYPOINT command runs and starts the app.


Create and run the Docker image

Now we have our Dockerfile file in place, we can create our Docker image with the following command:

docker build --pull -t core5-website .

The --pull parameter ensures the latest upstream .NET 5 SDK and .NET 5 runtime images are used and the -t parameter provides the tag / name of the image.

With the Docker image created, we can run an instance of the image:

docker run --rm -it -p 8000:80 core5-website --name core5-website-app

The parameter -rm removes the container once it’s finished running, -it starts the container in interactive mode, -p 8080:80 exposes the port 80 of the container to port 8080 on the host and --name provides the name of the instance.

With the container now running, if you browse to http://localhost:8000 you should see the “Welcome” page and you should see that it details the app is running in a container.

The observant of you may be wondering why the ASP.NET Core app is running over HTTP on port 80 in the container instead of HTTPS on port 5001. Port 80 has been used because the base image from Microsoft contains the environment variable ASPNETCORE_URLS with the value http://+:80, which controls the port the app will listen on and the app is only running over HTTP as we haven’t configured a certificate for Docker to use.


Configuring a certificate to support HTTPS in Docker

The first step is to remove an existing self-signed certificate if there is one.

dotnet dev-certs https --check --verbose
dotnet dev-certs https --clean
dotnet dev-certs https --check --verbose

Now we’ll create a new certificate (with a password of your choosing) and check that the certificate was created successfully.

dotnet dev-certs https -ep ${HOME}/.aspnet/https/core5-website.pfx -p your_password_here
dotnet dev-certs https --trust
dotnet dev-certs https --check --verbose

And with that, we can start the container with some additional parameters to provide details of the certificate we just generated and to map port 443 of the container to port 8001 on the host.

docker run --rm -it -p 8000:80 -p 8001:443 -e ASPNETCORE_URLS="https://+;http://+" -e ASPNETCORE_HTTPS_PORT=8001 -e ASPNETCORE_Kestrel__Certificates__Default__Password="your_password_here" -e ASPNETCORE_Kestrel__Certificates__Default__Path=/https/core5-website.pfx -v ${HOME}/.aspnet/https:/https/ core5-website

Now if you browse to http://localhost:8000, you will be redirected to the HTTPS version on https://localhost:8001 with the certificate we just generated being used.

If you are running on a different operating system other than macOS, the previous steps for HTTPS support are slightly different. Please refer to the Microsoft Hosting ASP.NET Core images with Docker over HTTPS page for more detail.