Docker Deployment using Docker Compose
This document assumes that you prefer to use MVC / Razor Pages as the UI framework and Entity Framework Core as the database provider. For other options, please change the preference on top of this document.
This guide will be guide you through how to build docker images for your application and run on localhost using docker compose. You will learn the provided build scripts and docker compose files in detail and how to modify for production environment.
Building Docker Images
Each application contains a dockerfile called Dockerfile.local for building the docker image. As naming implies, these dockerfiles are not multi-stage dockerfiles and requires project to be built in Release mode to create the image. Currently, if you are building your images using CI&CD pipeline, you either need to include the SDK to your pipeline before building the images or add your own multi-stage dockerfiles.
Since they are not multi-staged dockerfiles, if you want to build the images individually, you can navigate to the related to-be-hosted application folder and run
dotnet publish -c Release
to populate the Release folder first which will be used to build the docker images. Afterwards, you can run
docker build -f Dockerfile.local -t mycompanyname/myappname:version .
to manually build your application image.
To ease the process, application templates provide a build script to build all the images with a single script under etc/build folder named as build-images-locally.ps1. Based on your application name, ui and type; build image script will be generated.
param ($version='latest')
$currentFolder = $PSScriptRoot
$slnFolder = Join-Path $currentFolder "../../"
Write-Host "********* BUILDING DbMigrator *********" -ForegroundColor Green
$dbMigratorFolder = Join-Path $slnFolder "src/Acme.BookStore.DbMigrator"
Set-Location $dbMigratorFolder
dotnet publish -c Release
docker build -f Dockerfile.local -t acme/bookstore-db-migrator:$version .
Write-Host "********* BUILDING Web Application *********" -ForegroundColor Green
$webFolder = Join-Path $slnFolder "src/Acme.BookStore.Web"
Set-Location $webFolder
dotnet publish -c Release
docker build -f Dockerfile.local -t acme/bookstore-web:$version .
	
	Write-Host "********* BUILDING Api.Host Application *********" -ForegroundColor Green
	$hostFolder = Join-Path $slnFolder "src/Acme.BookStore.HttpApi.Host"
	Set-Location $hostFolder
	dotnet publish -c Release
	docker build -f Dockerfile.local -t acme/bookstore-api:$version .
	
	Write-Host "********* BUILDING AuthServer Application *********" -ForegroundColor Green
	$authServerAppFolder = Join-Path $slnFolder "src/Acme.BookStore.AuthServer"
	Set-Location $authServerAppFolder
	dotnet publish -c Release
	docker build -f Dockerfile.local -t acme/bookstore-authserver:$version .
		
### ALL COMPLETED
Write-Host "COMPLETED" -ForegroundColor Green
Set-Location $currentFolder
The image tag is set to latest by default. You can update the param $version at the first line as you want to set as tag for your images.
You can examine all the provided dockerfiles required to publish your application below;
DBMigrator
DbMigrator is a console application that is used to migrate the database of your application and seed the initial important data to run your application. Such as pre-defined langauges, admin user and role, OpenIddict applications and scopes.
Dockerfile.local is provided under this project as below;
FROM mcr.microsoft.com/dotnet/aspnet:7.0
COPY bin/Release/net7.0/publish/ app/
WORKDIR /app
ENTRYPOINT ["dotnet", "BookStore.DbMigrator.dll"]
If you don't want to use the build-images-locally.ps1 to build the images or to build this image individually and manually, navigate to DbMigrator folder and run:
dotnet publish -c Release #Builds the projects in Release mode
docker build -f Dockerfile.local -t acme/bookstore-db-migrator:latest . #Builds the image with "latest" tag
MVC/Razor Pages
 MVC/Razor Pages application is a server-side rendering application that uses Cookie authentication as default scheme and OpenIdConnect as the default challange scheme.
In the WebModule under authentication configuration, there is an extra configuration for containerized environment support:
if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"]))
{
    context.Services.Configure<OpenIdConnectOptions>("oidc", options =>
    {
        options.TokenValidationParameters.ValidIssuers = new[]
        {
            configuration["AuthServer:MetaAddress"].EnsureEndsWith('/'), 
            configuration["AuthServer:Authority"].EnsureEndsWith('/')
        };
        options.MetadataAddress = configuration["AuthServer:MetaAddress"].EnsureEndsWith('/') +
                                ".well-known/openid-configuration";
        var previousOnRedirectToIdentityProvider = options.Events.OnRedirectToIdentityProvider;
        options.Events.OnRedirectToIdentityProvider = async ctx =>
        {
            // Intercept the redirection so the browser navigates to the right URL in your host
            ctx.ProtocolMessage.IssuerAddress = configuration["AuthServer:Authority"].EnsureEndsWith('/') + "connect/authorize";
            if (previousOnRedirectToIdentityProvider != null)
            {
                await previousOnRedirectToIdentityProvider(ctx);
            }
        };
        var previousOnRedirectToIdentityProviderForSignOut = options.Events.OnRedirectToIdentityProviderForSignOut;
        options.Events.OnRedirectToIdentityProviderForSignOut = async ctx =>
        {
            // Intercept the redirection for signout so the browser navigates to the right URL in your host
            ctx.ProtocolMessage.IssuerAddress = configuration["AuthServer:Authority"].EnsureEndsWith('/') + "connect/logout";
            if (previousOnRedirectToIdentityProviderForSignOut != null)
            {
                await previousOnRedirectToIdentityProviderForSignOut(ctx);
            }
        };
    });
}
This is used when the AuthServer is running on docker containers(or pods) to configure the redirection URLs for internal network and the web. The application must be redirected to real DNS (localhost in this case) when the /authorize and /logout requests over the browser but handle the token validation inside the isolated network without going out to internet. "AuthServer:MetaAddress" appsetting should indicate the container/pod service name while the AuthServer:Authority should be pointing to real DNS for browser to redirect.
The appsettings.json file does not contain AuthServer:IsContainerizedOnLocalhost and AuthServer:MetaAddress settings since they are used for orchestrated deployment scenarios, you can see these settings are overridden by the docker-compose.yml file.
Dockerfile.local is provided under this project as below;
FROM mcr.microsoft.com/dotnet/aspnet:7.0
COPY bin/Release/net7.0/publish/ app/
WORKDIR /app
ENTRYPOINT ["dotnet", "Acme.BookStore.Web.dll"]
If you don't want to use the build-images-locally.ps1 to build the images or to build this image individually and manually, navigate to Web folder and run:
dotnet publish -c Release #Builds the projects in Release mode
docker build -f Dockerfile.local -t acme/bookstore-web:latest . #Builds the image with "latest" tag
AuthServer
This is the openid-provider application, the authentication server which should be individually hosted compared to non-tiered application templates. The dockerfile.local is located under the AuthServer project as below;
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
COPY bin/Release/net7.0/publish/ app/
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
RUN dotnet dev-certs https -v -ep authserver.pfx -p 2D7AA457-5D33-48D6-936F-C48E5EF468ED
FROM base AS final
WORKDIR /app
COPY --from=build /src .
ENTRYPOINT ["dotnet", "Acme.BookStore.AuthServer.dll"]
You can come across an error when the image is being built. This occurs because of dotnet dev-certs command trying to list the existing certificates inside the container and unavailable to. This is not an important error since we aim to generate the authserver.pfx file and discard the container it is built in.

The AuthServer docker image building process contains multi-stages to generate authserver.pfx file which is used by OpenIddict as signing and encryption certificate. This configuration is found under the PreConfigureServices method of the AuthServerModule:
if (!hostingEnvironment.IsDevelopment())
{
    PreConfigure<AbpOpenIddictAspNetCoreOptions>(options =>
    {
        options.AddDevelopmentEncryptionAndSigningCertificate = false;
    });
    PreConfigure<OpenIddictServerBuilder>(builder =>
    {
        builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration));
        builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration));
        builder.SetIssuer(new Uri(configuration["AuthServer:Authority"]));
    });
}
This configuration disables the DevelopmentEncryptionAndSigningCertificate and uses a self-signed certificate called authserver.pfx. for signing and encrypting the tokens. This certificate is being created when the docker image is build using the dotnet dev-certs tooling . It is a sample generated certificate and it is recommended to update it for production environment. You can check the OpenIddict Encryption and signing credentials documentation for different options and customization.
The GetSigningCertificate method is a private method located under the same AuthServerModule:
private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration)
{
    var fileName = "authserver.pfx";
    var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED";
    var file = Path.Combine(hostingEnv.ContentRootPath, fileName);
    if (!File.Exists(file))
    {
        throw new FileNotFoundException($"Signing Certificate couldn't found: {file}");
    }
    return new X509Certificate2(file, passPhrase);
}
You can always create any self-signed certificate using any other tooling outside of the dockerfile. You need to keep on mind to set them as embedded resource since the
GetSigningCertificatemethod will be checking this file physically.
If you don't want to use the build-images-locally.ps1 to build the images or to build this image individually and manually, navigate to AuthServer folder and run:
dotnet publish -c Release #Builds the projects in Release mode
docker build -f Dockerfile.local -t acme/bookstore-authserver:latest . #Builds the image with "latest" tag
Http.Api.Host
This is the backend application that exposes the endpoints and swagger UI. It is not a multi-stage dockerfile hence you need to have already built this application in Release mode in order to use this dockerfile. The dockerfile.local is located under the Http.Api.Host project as below;
FROM mcr.microsoft.com/dotnet/aspnet:7.0
COPY bin/Release/net7.0/publish/ app/
WORKDIR /app
ENTRYPOINT ["dotnet", "Acme.BookStore.HttpApi.Host.dll"]
If you don't want to use the build-images-locally.ps1 to build the images or to build this image individually and manually, navigate to Http.Api.Host folder and run:
dotnet publish -c Release #Builds the projects in Release mode
docker build -f Dockerfile.local -t acme/bookstore-api:latest . #Builds the image with "latest" tag
Running Docker-Compose on Localhost
Under the etc/docker folder, you can find the docker-compose.yml to run your application. To ease the running process, the template provides run-docker.ps1 (and run-docker.sh) scripts that handles the HTTPS certificate creation which is used in environment variables;
$currentFolder = $PSScriptRoot
$slnFolder = Join-Path $currentFolder "../"
$certsFolder = Join-Path $currentFolder "certs"
If(!(Test-Path -Path $certsFolder))
{
    New-Item -ItemType Directory -Force -Path $certsFolder
    if(!(Test-Path -Path (Join-Path $certsFolder "localhost.pfx") -PathType Leaf)){
        Set-Location $certsFolder
        dotnet dev-certs https -v -ep localhost.pfx -p 91f91912-5ab0-49df-8166-23377efaf3cc -t        
    }
}
Set-Location $currentFolder
docker-compose up -d
run-docker.ps1 (or run-docker.sh) script will be checking if there is an existing dev-cert already under the etc/certs folder and generates a localhost.pfx file if doesn't exist. This file will be used by Kestrel as HTTPS certificate.
You can also manually create the localhost.pfx file in a different path with different name and a different password by using dotnet dev-certs https -v -ep myCert.pfx -p YOUR_PASSWORD_FOR_HTTPS_CERT -t or with using any other self-signed certificate generation tool.
You need to update the service environment variables Kestrel__Certificates__Default__Path with the path and filename you have created and the  Kestrel__Certificates__Default__Password with your new password in the docker-compose.yml file.
Now lets break down each docker compose service under the docker-compose.yml file:
bookstore-web
bookstore-web:
    image: acme/bookstore-web:latest
    container_name: bookstore-web
    hostname: bookstore-web
    build:
      context: ../../
      dockerfile: src/Acme.BookStore.Web/Dockerfile.local
    environment:
      - ASPNETCORE_URLS=https://+:443;http://+:80;
      - Kestrel__Certificates__Default__Path=/root/certificate/localhost.pfx
      - Kestrel__Certificates__Default__Password=91f91912-5ab0-49df-8166-23377efaf3cc
      - App__SelfUrl=https://localhost:44353
      - AuthServer__RequireHttpsMetadata=false        
  	  - AuthServer__IsContainerizedOnLocalhost=true
	  - AuthServer__Authority=https://localhost:44334/
      - RemoteServices__Default__BaseUrl=http://bookstore-api
      - RemoteServices__AbpAccountPublic__BaseUrl=http://bookstore-authserver
      - AuthServer__MetaAddress=http://bookstore-authserver    
      - App__HealthCheckUrl=http://bookstore-web/health-status      	
      - ConnectionStrings__Default=Data Source=sql-server;Initial Catalog=BookStore;User Id=sa;Password=myPassw0rd;MultipleActiveResultSets=true;TrustServerCertificate=True;			
      - Redis__Configuration=redis  	
    ports:
      - "44353:443"
    restart: on-failure
    volumes:
      - ./certs:/root/certificate
    networks:
      - abp-network
This is the MVC/Razor Page application docker service is using the acme/bookstore-web:latest image that we have built using the build-images-locally.ps1 script. It runs on https://localhost:44353 by default, by mounting the self-signed certificate we've generated under the etc/certs folder.
The MVC/Razor Page is a server-side rendering application that uses the hybrid flow. This flow uses browser to login/logout process to the openid-provider but issues the access_token from the back-channel (server-side). To achieve this functionality, the module class has extra OpenIdConnectOptions to override some of the events:
if (Convert.ToBoolean(configuration["AuthServer:IsContainerizedOnLocalhost"]))
{
    context.Services.Configure<OpenIdConnectOptions>("oidc", options =>
    {
        options.TokenValidationParameters.ValidIssuers = new[]
        {
            configuration["AuthServer:MetaAddress"].EnsureEndsWith('/'), 
            configuration["AuthServer:Authority"].EnsureEndsWith('/')
        };
        options.MetadataAddress = configuration["AuthServer:MetaAddress"].EnsureEndsWith('/') +
                                ".well-known/openid-configuration";
        var previousOnRedirectToIdentityProvider = options.Events.OnRedirectToIdentityProvider;
        options.Events.OnRedirectToIdentityProvider = async ctx =>
        {
            // Intercept the redirection so the browser navigates to the right URL in your host
            ctx.ProtocolMessage.IssuerAddress = configuration["AuthServer:Authority"].EnsureEndsWith('/') + "connect/authorize";
            if (previousOnRedirectToIdentityProvider != null)
            {
                await previousOnRedirectToIdentityProvider(ctx);
            }
        };
        var previousOnRedirectToIdentityProviderForSignOut = options.Events.OnRedirectToIdentityProviderForSignOut;
        options.Events.OnRedirectToIdentityProviderForSignOut = async ctx =>
        {
            // Intercept the redirection for signout so the browser navigates to the right URL in your host
            ctx.ProtocolMessage.IssuerAddress = configuration["AuthServer:Authority"].EnsureEndsWith('/') + "connect/logout";
            if (previousOnRedirectToIdentityProviderForSignOut != null)
            {
                await previousOnRedirectToIdentityProviderForSignOut(ctx);
            }
        };
    });
}
- App__SelfUrlpoints to the localhost with the port we expose- https://localhost:44353. It must point to a real DNS when deploying to production.
- AuthServer__RequireHttpsMetadatais the option for the openid-provider to enforce HTTPS. Since we are using isolated internal docker network. We want to use HTTP in the internal network communication without SSL overhead, therefore it is set to- falseby default.- AuthServer__IsContainerizedOnLocalhostis the configuration to enable the OpenIdConnectOptions to provide different endpoint for the MetaAddress of the openid-provider and intercepting the URLS for authorization and logout endpoints.
- AuthServer__MetaAddressis the- .well-known/openid-configurationendpoint for issuing access_token and internal token validation. It is the containerized- http://bookstore-authserverby default.
- AuthServer__Authorityis the issuer URL.- https://localhost:44334/is the issuer that the application redirects to on- authorizationand- logoutprocess. It must point to a real DNS when deploying to production.
- RemoteServices__Default__BaseUrlis the backend; API endpoint application uses the access_token to get the resources. It is the containerized- http://bookstore-apiby default.
- RemoteServices__AbpAccountPublic__BaseUrlis the account URL used to get the profile picture of the user. Since account related information is located in the authserver, it is the containerized- http://bookstore-authserverby default.
 
- ConnectionStrings__Defaultis the overridden default connection string. It uses the containerized sql-server with the sa user by default.- Redis__Configurationis the overridden redis configuration. It uses the containerized redis service. If you are not using containerized redis, update with your redis URL.
 
This service runs in docker network called
abp-network, awaits for the redis service and the database container for starting up and restarts when fails. You can customize these orchestration behaviours as you prefer.
bookstore-api
bookstore-api:
    image: acme/bookstore-api:latest
    container_name: bookstore-api
    hostname: bookstore-api
    build:
      context: ../../
      dockerfile: src/Acme.BookStore.HttpApi.Host/Dockerfile.local
    environment:
      - ASPNETCORE_URLS=https://+:443;http://+:80;
      - Kestrel__Certificates__Default__Path=/root/certificate/localhost.pfx
      - Kestrel__Certificates__Default__Password=91f91912-5ab0-49df-8166-23377efaf3cc
      - App__SelfUrl=https://localhost:44354
      - App__HealthCheckUrl=http://bookstore-api/health-status
	  - AuthServer__Authority=http://bookstore-authserver
      - AuthServer__RequireHttpsMetadata=false  
      - ConnectionStrings__Default=Data Source=sql-server;Initial Catalog=BookStore;User Id=sa;Password=myPassw0rd;MultipleActiveResultSets=true;TrustServerCertificate=True;    
      - Redis__Configuration=redis
    ports:
      - "44354:443"
    depends_on: 
      sql-server:
        condition: service_healthy        
      redis:
        condition: service_healthy
    restart: on-failure
    volumes:
      - ./certs:/root/certificate
    networks:
      - abp-network
This service is the backend application of the MVC/Razor Page application that is using the acme/bookstore-api:latest image we have built using the build-images-locally.ps1 script. It runs on https://localhost:44354 by default, by mounting the self-signed certificate we've generated under the etc/certs folder.
- App__SelfUrlpoints to the localhost with the port we expose- https://localhost:44354. It must point to a real DNS when deploying to production.
- App__HealthCheckUrlis the health check url. Since this request will be done internally, it points to the service name in containerized environment- http://bookstore-api/health-status
- AuthServer__Authorityis the issuer URL.- http://bookstore-authserveris the containerized issuer. It must point to a real DNS when deploying to production.
- AuthServer__RequireHttpsMetadatais the option for the openid-provider to enforce HTTPS. Since we are using isolated internal docker network. We want to use HTTP in the internal network communication without SSL overhead, therefore it is set to- falseby default.
- ConnectionStrings__Defaultis the overridden default connection string. It uses the containerized sql-server with the sa user by default.
- Redis__Configurationis the overridden redis configuration. It uses the containerized redis service. If you are not using containerized redis, update with your redis URL.
This service runs in docker network called
abp-network, awaits for the redis service and the database container for starting up and restarts when fails. You can customize these orchestration behaviours as you prefer.
bookstore-authserver
bookstore-authserver:
    image: acme/bookstore-authserver:latest
    container_name: bookstore-authserver
    build:
      context: ../../
      dockerfile: src/Acme.BookStore.AuthServer/Dockerfile.local
    environment:
      - ASPNETCORE_URLS=https://+:443;http://+:80;      
      - Kestrel__Certificates__Default__Path=/root/certificate/localhost.pfx
      - Kestrel__Certificates__Default__Password=91f91912-5ab0-49df-8166-23377efaf3cc
      - App__SelfUrl=https://localhost:44334
      - App__CorsOrigins=https://localhost:44353,https://localhost:44354
      - AuthServer__Authority=http://bookstore-authserver
      - AuthServer__RequireHttpsMetadata=false      
      - ConnectionStrings__Default=Data Source=sql-server;Initial Catalog=BookStore;User Id=sa;Password=myPassw0rd;MultipleActiveResultSets=true;TrustServerCertificate=True;            
      - Redis__Configuration=redis
    ports:
      - "44334:443"
    depends_on:     
      sql-server:
        condition: service_healthy              
      redis:
        condition: service_healthy
    restart: on-failure
    volumes:
      - ./certs:/root/certificate
    networks:
      - abp-network
This is the authentication server application that handles the authentication between applications using the OpenIddict library.  It is using the acme/bookstore-authserver:latest image we have built using the build-images-locally.ps1 script. It runs on https://localhost:44334 by default, by mounting the self-signed certificate we've generated under the etc/certs folder.
- App__SelfUrlpoints to the localhost with the port we expose- https://localhost:44334. It must point to a real DNS when deploying to production.
- App__CorsOriginsis the override configuration for CORS. It is- https://localhost:44353,https://localhost:44354by default. It must point to a real DNS when deploying to production.
- AuthServer__Authorityis the issuer URL.- http://bookstore-authserveris the endpoint for the authserver by default.
- AuthServer__RequireHttpsMetadatais the option for the openid-provider to enforce HTTPS. Docker-compose is using isolated internal docker network called- abp-network. It is set to- falseby default.
- ConnectionStrings__Defaultis the overridden default connection string. It uses the containerized sql-server with the sa user by default.
- Redis__Configurationis the overridden redis configuration. It uses the containerized redis service. If you are not using containerized redis, update with your redis URL.
This service runs in docker network called
abp-network, awaits for the redis service and the database container for starting up and restarts when fails. You can customize these orchestration behaviours as you prefer.
db-migrator
db-migrator:
    image: acme/bookstore-db-migrator:latest
    container_name: db-migrator
    build:
      context: ../../
      dockerfile: src/BookStore.DbMigrator/Dockerfile.local
    environment:
      - OpenIddict__Applications__BookStore_Web__RootUrl=https://localhost:44353      
      - OpenIddict__Applications__BookStore_Swagger__RootUrl=https://localhost:44354            
      - ConnectionStrings__Default=Data Source=sql-server;Initial Catalog=BookStore;User Id=sa;Password=myPassw0rd;MultipleActiveResultSets=true;TrustServerCertificate=True;            
    depends_on:     
      sql-server:
        condition: service_healthy                  
    networks:
      - abp-network
This is the database migrator service that migrates the database and seeds the initial data. OpenIddict data is one of the most important seeded data for your application to run. On production environment, you need to override the root URL of your application (https://localhost:44353) and the swagger-ui client URL (https://localhost:44354) so that the authentication can work properly.
This service runs in docker network called
abp-network, awaits for the database container for starting up and restarts when fails. You can customize these orchestration behaviours as you prefer.
 
                                    