Docker Deployment using Docker Compose
This document assumes that you prefer to use Angular as the UI framework and MongoDB as the database provider. For other options, please change the preference at the top of this document.
This guide will 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 them for the production environment.
Building Docker Images
Each application contains a dockerfile called Dockerfile.local
for building the docker image. As the naming implies, these Dockerfiles are not multi-stage Dockerfiles and require the 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 the following command:
dotnet publish -c Release
To populate the Release folder first, which will be used to build the docker images. Afterward, you can run the following command:
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 build-images-locally.ps1
.
Based on your application name, UI and type, a 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 Angular Application *********" -ForegroundColor Green
$angularAppFolder = Join-Path $slnFolder "../angular"
Set-Location $angularAppFolder
yarn
npm run build:prod
docker build -f Dockerfile.local -t acme/bookstore-angular:$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 .
$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 to set it as a 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 languages, 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
Angular
The angular application uses nginx:alpine-slim base image to host the angular application. You can modify the base image based on your preference in the Dockerfile.local
, which is provided under the angular folder of your solution as below;
FROM nginx:alpine-slim
WORKDIR /app
COPY dist/BookStore /usr/share/nginx/html
COPY dynamic-env.json /usr/share/nginx/html
COPY /nginx.conf /etc/nginx/conf.d/default.conf
You can notice that two more files are copied into the application image beside the built Angular application.
The dynamic-env.json
file is an empty JSON file representing the angular application's environment variables. The environment variable on image runtime will override this file. If you examine the environment.prod.ts
file under the angular/src/environments
folder, there is a remote environment configuration for production:
remoteEnv: {
url: '/getEnvConfig',
mergeStrategy: 'deepmerge'
}
This configuration is used to get the environment variables from a remote service. This configuration is used to override environment variables without rebuilding the image. The URL /getEnvConfig
is defined in the nginx.conf
file:
server {
listen 80;
listen [::]:80;
server_name _;
#listen 443 ssl;
#server_name www.myapp.com;
#ssl_certificate www.myapp.com.crt;
#ssl_certificate_key www.myapp.com.key;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html =404;
}
location /getEnvConfig {
default_type 'application/json';
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Content-Type' 'application/json';
try_files $uri /dynamic-env.json;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
This configuration allows returning the dynamic-env.json
file as a static file, which ABP Angular application uses for environment variables in one of the first initial requests when rendering the page. The dynamic-env.json
file you need to override is located under aspnet-core/etc/docker
folder.
{
"production": true,
"application": {
"baseUrl":"http://localhost:4200", // https://myapp.com
"name": "BookStore",
"logoUrl": ""
},
"oAuthConfig": {
"issuer": "https://localhost:44334/", // https://myauthserver.com/
"redirectUri": "http://localhost:4200", // https://myapp.com
"clientId": "BookStore_App",
"responseType": "code",
"scope": "offline_access openid profile email phone BookStore"
},
"apis": {
"default": {
"url": "https://localhost:44354", // https://myapi.com
"rootNamespace": "BookStore"
},
"AbpAccountPublic": {
"url": "https://localhost:44334", // https://myauthserver.com
"rootNamespace": "AbpAccountPublic"
}
}
}
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 the angular folder and run:
yarn #Restores the project
npm run build:prod #Builds on production environment. You can also use "ng build --prod" if you have the angular CLI
docker build -f Dockerfile.local -t acme/bookstore-angular: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 a 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 created when the docker image is built using the dotnet dev-certs
tooling. It is a sample-generated certificate, and it is recommended to update it for the 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 the dockerfile. You need to remember to set them as embedded resource since the
GetSigningCertificate
method 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 the 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 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 handle 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 check if there is an existing dev-cert already under the etc/certs
folder and generate a localhost.pfx
file if it doesn't exist. Kestrel will use this file as an 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-angular
services:
bookstore-angular:
image: acme/bookstore-angular:latest
container_name: bookstore-angular
build:
context: ../../../
dockerfile: angular/Dockerfile.local
ports:
- "4200:80"
depends_on:
- bookstore-api
volumes:
- ./dynamic-env.json://usr/share/nginx/html/dynamic-env.json
networks:
- abp-network
This is the angular application we deploy on http://localhost:4200 by default using the image that we have built using the build-images-locally.ps1
script. It is not running on HTTPS using the localhost.pfx
since it is running on Nginx and it doesn't accept pfx
files for SSL. You can check Nginx Configuring HTTPS Servers documentation for more information and apply the necessary configurations to nginx.conf
file under the angular
folder.
Don't forget to rebuild the
acme/bookstore-angular:latest
image after updating thenginx.conf
file.
The bookstore-angular service mounts the etc/docker/dynamic-env.json
file to change the existing dynamic-env.json file, which is copied during image creation, to change the environment variables on deployment time instead of re-creating the docker image after each environmental variable change. Do not forget to override the dynamic-env.json
located under the aspnet-core/etc/docker
folder.
If you are not using Docker with WSL, you may have problems with the volume mount permissions. You need to grant docker to be able to use the local file system. See this SO answer for more information.
{
"production": true,
"application": {
"baseUrl":"http://localhost:4200", // https://myapp.com
"name": "BookStore",
"logoUrl": ""
},
"oAuthConfig": {
"issuer": "https://localhost:44334/", // https://myauthserver.com/
"redirectUri": "http://localhost:4200", // https://myapp.com
"clientId": "BookStore_App",
"responseType": "code",
"scope": "offline_access openid profile email phone BookStore"
},
"apis": {
"default": {
"url": "https://localhost:44354", // https://myapi.com
"rootNamespace": "BookStore"
},
"AbpAccountPublic": {
"url": "https://localhost:44334", // https://myauthserver.com
"rootNamespace": "AbpAccountPublic"
}
}
}
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__AngularUrl=http://localhost:4200
- App__CorsOrigins=http://localhost:4200
- App__HealthCheckUrl=http://bookstore-api/health-status
- AuthServer__Authority=http://bookstore-authserver
- AuthServer__RequireHttpsMetadata=false
- ConnectionStrings__Default=mongodb://mongodb/BookStore
- Redis__Configuration=redis
ports:
- "44354:443"
depends_on:
mongo-db:
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 angular application that uses 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__SelfUrl
points to the localhost with the port we exposehttps://localhost:44354
. It must point to a real DNS when deploying to production.App__AngularUrl
is the override configuration of URLs for account-related endpoints. It ishttp://localhost:4200
by default. It must point to a real DNS when deploying to production.App__CorsOrigins
is the override configuration for CORS. It ishttp://localhost:4200
by default. It must point to a real DNS when deploying to production.App__HealthCheckUrl
is the override configuration for the health check URL. Since this request will be done internally, it points to the service name in the containerized environmenthttp://bookstore-api/health-status
.AuthServer__Authority
is the issuer URL.http://bookstore-authserver
is the containerized issuer. It must point to a real DNS when deploying to production.AuthServer__RequireHttpsMetadata
is the option for the openid-provider to enforce HTTPS. Docker-compose is using an isolated internal docker network calledabp-network
. We want to use HTTP in the internal network communication without SSL overhead; therefore it is set tofalse
by default.ConnectionStrings__Default
is the overridden default connection string. It uses the containerized mongodb service by default.Redis__Configuration
is the overridden Redis configuration. It uses the containerized redis service. If you are not using containerized Redis, update your Redis URL.
This service runs in a docker network called
abp-network
, and awaits the Redis service and the database container for starting up and restarts when it fails. You can customize these orchestration behaviors 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=http://localhost:4200
- App__RedirectAllowedUrls=http://localhost:4200
- AuthServer__Authority=https://localhost:44334/
- AuthServer__RequireHttpsMetadata=false
- ConnectionStrings__Default=mongodb://mongodb/BookStore
- Redis__Configuration=redis
ports:
- "44334:443"
depends_on:
mongo-db:
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 uses 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__SelfUrl
points to the localhost with the port we exposehttps://localhost:44334
. It must point to a real DNS when deploying to production.App__CorsOrigins
is the override configuration for CORS. It ishttp://localhost:4200
by default. It must point to a real DNS when deploying to production.App__RedirectAllowedUrls
is the override configuration of redirect URLs for the angular application. It ishttp://localhost:4200
by default. It must point to a real DNS when deploying to production.AuthServer__Authority
is the issuer URL.https://localhost:44334/
is the endpoint for the authserver by default. It must point to a real DNS when deploying to production.AuthServer__RequireHttpsMetadata
is the option for the openid-provider to enforce HTTPS. Docker-compose uses using isolated internal docker network calledabp-network
. It is set tofalse
by default.ConnectionStrings__Default
is the overridden default connection string. It uses the containerized mongodb service by default.Redis__Configuration
is the overridden Redis configuration. It uses the containerized redis service. If you are not using containerized Redis, update 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 it fails. You can customize these orchestration behaviors 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_App__RootUrl=http://localhost:4200
- OpenIddict__Applications__BookStore_Swagger__RootUrl=https://localhost:44354
- ConnectionStrings__Default=mongodb://mongodb/BookStore
depends_on:
mongo-db:
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 (http://localhost:4200) 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 to start up and restarts when it fails. You can customize these orchestration behaviors as you prefer.