While it is straightforward to host an ASP.NET Core Open API or a .NET based GRPC service in a Docker container, and the process is well documented (see references below), there is no documentation, or even a blog post that I could find on how to also host a client in a container and call the services using HTTPS. While in the OpenAPI case it's possible to disable HTTPS redirection in development, this is not an option for GRPC.
The challenge in developing a server and client application using (separate) containers rises from the fact that
during development one uses self-signed certifates as server certificates. ASP.NET Core tooling in Visual Studio
creates and manages these certificates for you (and there is also the dotnet dev-certs https tool), but these
are only useful if your client is running on your own computer and not inside a container.
Why does this happen?
When we are running the service in a container, the ports where the service endpoints are exposed are redirected by Docker to host ports, as in the image. The client, running on the host, uses these ports to access the service endpoints. The developer certificate generated by ASP.NET Core is available both on the container as a server certificate, and is installed and trusted on the host, so we can access the HTTPS service endpoint from the client.
As soon as the client moves into a container, we need to use Docker Compose to build the multi-container solution with all our components, like in the image:
Each component has its own service name in the respective configuration files, and now
the client needs to access the service endpoints using the name of the service. For example, if in docker-compose.yml
we have defined our service as below:
aspnet-echo-service:
image: ${DOCKER_REGISTRY-}aspnetechoservice
build:
context: .
dockerfile: aspnet-echo-service/DockerfileThe endpoint that the client needs to use to access the HTTPS endpoint for the Open API, if it is exposed on port 8081,
is https://aspnet-echo-service:8081. The default self-signed service certificate does not include this name though, so client-
service communication fails.
This solution applies to a Windows host and Linux containers, using the default .NET image
dotnet/sdk:9.0.
The approach we need to use to be able to access the service endpoints from the client is to use our own self-signed certificate, which contains all the names of the services that we want to access, and follows the rules for ASP.NET Core self-signed certificates.
The steps we need to follow are:
-
Create the self-signed certificate on the host using PowerShell (the
dotnet dev-certs httpstool does not allow us to specify the parameters we need) and also add it as trusted root. The certificate needs to:- Have
localhostas subject and issuer - Include all the service names as alternative names
- Support Server Authentication as Enhanced Key Usage
- Have the value 02 for property with OID
1.3.6.1.4.1.311.84.1.1, as this is checked by ASP.NET Core to determine that this is a valid development certificate (see link to code in References).
This is what the certificate should look like in the Certificate Manager:
- Have
-
Export this certificate in a pfx file that we will define as the server certificate in the services, using the
ASPNETCORE_Kestrel__Certificates__Default__Pathenvironment variable in thedocker-compose.override.ymlfile. -
Export the certificate's public key in a pem file, that we will define as a trusted issuer in the client, using the
SSL_CERT_PATHenvironment variable in thedocker-compose.override.ymlfile and runc_rehashin the client container before running the application.
Open the solution in Visual Studio.
Before the first build, run the CreateSelfSignedCertificate.ps1 script.
This script uses the New-SelfSignedCertificate cmdlet to generate a self-signed certificate
that is compatible as an ASP.NET self-signed certificate (see the TextExtension properties)
and then export it and copy it into the directories of the services that will use it
(aspnet-echo-service as pfx, grpc-echo-service as pfx, test-client as pem).
docker-compose.override.yml sets the Kestrel certificate to the pfx for the two services,
and includes /app to SSL_CERT_PATH in the client.
Set docker-compose as the startup project and run or debug the solution. The file does
not include a healthcheck as a prerequisite to run the client, so it may run before the two
services are up. You can set a breakpoint in the client to wait for the services to be up.


