How to run ASP.NET Core 3.1 over HTTPS in Docker using Linux Containers

How to run ASP.NET Core 3.1 over HTTPS in Docker using Linux Containers

Tom Chantler

Summary

Having recently installed WSL 2 (using Ubuntu 20.04) on my main Windows 10 machine and integrated it with Docker (more about that another time), I decided to change one of my apps to run inside Linux containers in Docker. That was easy enough, but it ran on port 80 using HTTP by default. That's not really good enough, so I decided I'd change it to use HTTPS and it was more difficult than I'd anticipated. In this article I'll explain how I got the development SSL certificate to work (and you'll see how it could be extended to use real certificates as well, although there are many other ways you might want to achieve that).

Background

Today I was doing some work on an ASP.NET Core 3.1 app and, as I was about to start adding some more applications to the solution, I decided that it would be a good idea to get everything to run in Docker using Linux containers. I right-clicked on the Project file in Visual Studio 2019, chose Add → Docker Support... and it worked fine. Then I thought it might make more sense to do the same again, but to choose Add → Container Orchestration Support... and then choose docker-compose. And it still worked fine. But it only ran on port 80 using HTTP. It's not been okay to run web applications over plain HTTP for a long time and, since it's important for the development environment to be broadly the same as the production environment, I needed to get my app to run over HTTPS.

At first, I thought I could just change the Dockerfile to add port 443, changing EXPOSE 80 to become EXPOSE 80 443 and then mapping those ports in the docker-compose.yml file, but that didn't work.

The problem seemed to be getting it to trust the self-signed certificates from within the Linux container environment and there are a lot of articles online which tell you how to make that work, but I couldn't get any of them to work.

Procedure

.NET comes with the global command line tool dotnet and, for a couple of years (I think since .NET Core 2.1 came out. almost exactly two years ago), it's been possible to create and trust your own development SSL certificate using a dotnet command.

To get this to work in Docker using Linux containers, you will need to create your dev certificate and then export it into a password-protected .pfx file and then import that into your container. That sounds fairly straightforward, but everything I read online (and I read a lot) didn't actually work.

After quite a lot of trial and error, here is what I found worked best.

1. Remove your existing dev certificate(s)

Open a PowerShell prompt and run:

dotnet dev-certs https --clean

If you've already got a certificate installed, it will pop up a Root Certificate Store window asking you to confirm this.

Delete Trusted Root Certificate Confirmation Dialog
2. Create, trust and export your new development certificate

Next you need to generate a new self-signed certificate, trust it and also export it to a password-protected .pfx file, all in a single step, otherwise it won't work properly.

In the same PowerShell prompt, run the following command, replacing SECRETPASSWORD with a secret password of your own choosing:

dotnet dev-certs https --trust -ep $env:USERPROFILE\.aspnet\https\aspnetapp.pfx -p SECRETPASSWORD

This will create and trust your dev certificate and export it to C:\Users\[USERNAME]\.aspnet\https\aspnetapp.pfx. It will pop up another window asking you to trust the new certificate.

Install new development certificate

As previously mentioned (and despite documentation to the contrary), I had to do all of this in one single command, or it didn't work properly.

NOTE: You will find articles saying you should refer to your user profile directory by using %USERPROFILE% or ~, but these won't work when running dotnet dev-certs in PowerShell, although you might not realise this straight away (it will just create folders with those names under the folder in which you execute the command). If you get strange errors about Interop+Crypto+OpenSslCryptographicException: error:2006D080:BIO routines:BIO_new_file:no such file in your container logs, it's quite likely that you can't find the .pfx file due to this reason. You need to use $env:USERPROFILE.

Interestingly, you can navigate to the output folder using cd ~/.aspnet/https and you can hit tab at any point to expand the ~ to C:\Users\[USERNAME] (and change the other instances of / to \), but you can't refer to it using the ~ in the dotnet dev-certs command.

When you run this command, you will notice that it gives you an insane message about running a sudo command.

A valid HTTPS certificate with a key accessible across security partitions was not found. The following command will run to fix it:
'sudo security set-key-partition-list -D localhost -S unsigned:,teamid:UBF8T346G9'
This command will make the certificate key accessible across security partitions and might prompt you for your password. For more information see: https://aka.ms/aspnetcore/2.1/troubleshootcertissues
A valid HTTPS certificate with a key accessible across security partitions was not found. The following command will run to fix it:
'sudo security set-key-partition-list -D localhost -S unsigned:,teamid:UBF8T346G9'
This command will make the certificate key accessible across security partitions and might prompt you for your password. For more information see: https://aka.ms/aspnetcore/3.1/troubleshootcertissues
Trusting the HTTPS development certificate was requested. A confirmation prompt will be displayed if the certificate was not previously trusted. Click yes on the prompt to trust the certificate.

Don't worry about it. This is a known issue and will presumably be fixed soon.

At this point we've run two simple PowerShell commands and have created a new development SSL certificate, added it to the trusted root store and exported it as a password-protected .pfx file. Next we just need to get it to work in Docker.

Install, trust and export development SSL certificate
3. Modify your Dockerfile to expose port 443

As I already alluded to in the Background section of this article, you need to modify the Dockerfile for your app and change EXPOSE 80 to become EXPOSE 80 443.

4. Add your certificate to your docker-compose.override.yml file

Now all that remains to be done is to add your new certificate to your docker-compose.override.yml file, so that it will be installed in the container to be used when you run your application(s) locally. You will also need to modify docker-compose.yml to include port 443 if you haven't already done so, like this (you'll see I am not running any other services on my dev machine, so I am mapping the ports directly):

version: '3.4'

services:
  tomssl.app:
    image: ${DOCKER_REGISTRY-}tomssl-app
    ports:
     - 80:80
     - 443:443
    build:
      context: .
      dockerfile: src/TomSSL.App/Dockerfile

And you need to modify the Environment: section of docker-compose.override.yml to update the URLS, add the path to and password for the .pfx file and also add a volumes: entry, to map the file location. Like this:

version: '3.4'

services:
  tomssl.app:
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_URLS=https://+:443;http://+:80
      - ASPNETCORE_Kestrel__Certificates__Default__Password=SECRETPASSWORD
      - ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx
    volumes:
      - ~/.aspnet/https:/https:ro

Make sure you use the password that you set when you created (and exported) your certificate in step 2.

And that's it.

Now when you run your app, it will run over HTTPS in Docker, like this:

Troubleshooting

Depending on how you've set up Docker, you might get a strange message about Could not find the global property 'UserSecretsId' in MSBuild project 'C:\path\to\project.csproj'. Ensure this property is set in the project or use the '--id' command line option. when you try to run your Docker container(s). If that happens (and it might not), then you just need to create the UserSecretsId in your .csproj file. To do this you can either edit the .csproj file directly and simply add a UserSecretsId value to the top main PropertyGroup, so that the top of the .csproj file now looks like this (it's usually a GUID, but it can be any text you like, as long as it's unique and can be used as the name of a directory):

 <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
    <DockerfileContext>..\..</DockerfileContext>
    <DockerComposeProjectPath>..\..\docker-compose.dcproj</DockerComposeProjectPath>
    <UserSecretsId>f9753959-3692-4c8c-a8ae-69f3c7a75ef8</UserSecretsId>
  </PropertyGroup>

Or you can right-click on the project in Visual Studio and click "Manage User Secrets" and it will add it for you.

Th is used to store development user values in a secrets.json stored in a folder in $env:USERPROFILE\AppData\Roaming\Microsoft\UserSecrets with the folder being the same as the UserSecretsId value which you added into the .csproj.

But don't worry too much about it. If you need to store User Secrets, you may already have done this and, if you get this specific error when you try to run your project in Docker, you can fix it by adding a single line to your .csproj.

Conclusion

Running ASP.NET Core 3.1 over HTTPS in Docker using Linux containers can be tricky to set up, but is actually very easy once you know what to do. And now you do. You just need to run a couple of PowerShell commands (literally two) and make some small modifications to your docker-compose files.