Published on

Deploy ABP Framework dotnet core tiered app to docker swarm. Part 9

4 min read
Table of Contents

Intro

In this post we will see how to deploy your dotnet core app with docker container.

Create ABP Tired application

abp new AbpDocker -t app -u mvc --tiered -dbms PostgreSQL

Once the app is created update the connection string in all the project and run the DbMigration project to setup the migrations and database seeding.

Create a same site cookies extension

you can find the code for that here: https://community.abp.io/articles/patch-for-chrome-login-issue-identityserver4-samesite-cookie-problem-weypwp3n

Here is the code

public static class SameSiteCookiesServiceCollectionExtensions
{
    public static IServiceCollection AddSameSiteCookiePolicy(this IServiceCollection services)
    {
        services.Configure<CookiePolicyOptions>(options =>
        {
            options.MinimumSameSitePolicy = SameSiteMode.Lax;
            options.OnAppendCookie = cookieContext =>
                CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
            options.OnDeleteCookie = cookieContext =>
                CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
        });

        return services;
    }

    private static void CheckSameSite(HttpContext httpContext, CookieOptions options)
    {
        if (options.SameSite == SameSiteMode.None)
        {
            var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
            if (!httpContext.Request.IsHttps || DisallowsSameSiteNone(userAgent))
            {
                // For .NET Core < 3.1 set SameSite = (SameSiteMode)(-1)
                options.SameSite = SameSiteMode.Lax;
            }
        }
    }

    private static bool DisallowsSameSiteNone(string userAgent)
    {
        // Cover all iOS based browsers here. This includes:
        // - Safari on iOS 12 for iPhone, iPod Touch, iPad
        // - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
        // - Chrome on iOS 12 for iPhone, iPod Touch, iPad
        // All of which are broken by SameSite=None, because they use the iOS networking stack
        if (userAgent.Contains("CPU iPhone OS 12") || userAgent.Contains("iPad; CPU OS 12"))
        {
            return true;
        }

        // Cover Mac OS X based browsers that use the Mac OS networking stack. This includes:
        // - Safari on Mac OS X.
        // This does not include:
        // - Chrome on Mac OS X
        // Because they do not use the Mac OS networking stack.
        if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") &&
            userAgent.Contains("Version/") && userAgent.Contains("Safari"))
        {
            return true;
        }

        // Cover Chrome 50-69, because some versions are broken by SameSite=None,
        // and none in this range require it.
        // Note: this covers some pre-Chromium Edge versions,
        // but pre-Chromium Edge does not require SameSite=None.
        if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
        {
            return true;
        }

        return false;
    }
}

Add the following line to ConfigureServices() method in all 3 projects.

context.Services.AddSameSiteCookiePolicy();

Go to OnApplicationInitialization() method in AcmeBookStoreWebModule.cs add app.UseCookiePolicy(); in all 3 projects.

app.UseCookiePolicy(); // added this, Before UseAuthentication or anything else that writes cookies.

Create a new client for the production app

Update the appsettings.json from the DbMigrator to add the new client.

"AbpDocker_Web_Docker": {
  "ClientId": "AbpDocker_Web_Docker",
  "ClientSecret": "1q2w3e*",
  "RootUrl": "http://host.docker.internal:9005"
},

Update the IdentityServerDataSeedContributor in the Domain project and add the new client to the identity server.

//webDockerClient Client
var webDockerClientId = configurationSection["AbpDocker_Web_Docker:ClientId"];
if (!webClientId.IsNullOrWhiteSpace())
{
    var webClientRootUrl = configurationSection["AbpDocker_Web_Docker:RootUrl"].EnsureEndsWith('/');

    await CreateClientAsync(
        name: webDockerClientId,
        scopes: commonScopes,
        grantTypes: new[] { "hybrid" },
        secret: (configurationSection["AbpDocker_Web_Docker:ClientSecret"] ?? "1q2w3e*").Sha256(),
        redirectUri: $"{webClientRootUrl}signin-oidc",
        postLogoutRedirectUri: $"{webClientRootUrl}signout-callback-oidc",
        frontChannelLogoutUri: $"{webClientRootUrl}Account/FrontChannelLogout",
        corsOrigins: new[] { webClientRootUrl.RemovePostFix("/") }
    );
}

Run the DbMigrator project again to see the new client.

Create production config

Create the appsettings.Production.json in all the three projects.

HttpApi.Host

{
  "App": {
    "CorsOrigins": "https://*.AbpDocker.com"
  },
  "ConnectionStrings": {
    "Default": "Host=host.docker.internal;Port=5432;Database=AbpDocker;User ID=postgres;Password=postgres;"
  },
  "Redis": {
    "Configuration": "host.docker.internal"
  },
  "AuthServer": {
    "Authority": "http://host.docker.internal:9006",
    "RequireHttpsMetadata": "false",
    "SwaggerClientId": "AbpDocker_Swagger",
    "SwaggerClientSecret": "1q2w3e*"
  },
  "StringEncryption": {
    "DefaultPassPhrase": "WBN0szwYr7wL8Dou"
  }
}

IdentityServer

{
  "App": {
    "SelfUrl": "http://host.docker.internal:9006",
    "ClientUrl": "http://localhost:4200",
    "CorsOrigins": "https://*.AbpDocker.com,http://localhost:4200,https://localhost:44307,https://localhost:44375,http://host.docker.internal:9006,http://host.docker.internal:9005,http://host.docker.internal:9007",
    "RedirectAllowedUrls": "http://localhost:4200,https://localhost:44307,http://host.docker.internal:9006,http://host.docker.internal:9005,http://host.docker.internal:9007"
  },
  "ConnectionStrings": {
    "Default": "Host=host.docker.internal;Port=5432;Database=AbpDocker;User ID=postgres;Password=postgres;"
  },
  "Redis": {
    "Configuration": "host.docker.internal"
  }
}

Web

{
  "App": {
    "SelfUrl": "http://host.docker.internal:9005"
  },
  "RemoteServices": {
    "Default": {
      "BaseUrl": "http://host.docker.internal:9007/"
    }
  },
  "Redis": {
    "Configuration": "host.docker.internal"
  },
  "AuthServer": {
    "Authority": "http://host.docker.internal:9006",
    "RequireHttpsMetadata": "false",
    "ClientId": "AbpDocker_Web_Docker",
    "ClientSecret": "1q2w3e*"
  }
}

Add docker support

Do the following in all 3 projects.

  • Right Click -> Add -> Docker Support

    This will add Docker file into the project.

  • Right Click -> Add -> Container Orchestrator Support

    This will add the docker file to the docker-compose.yml

Add port mapping to the services to the expose the services.

Final docker-compose.yml will look like this.

version: '3.4'

services:
  web:
    image: abpdockerweb
    ports:
      - 9005:80
    build:
      context: .
      dockerfile: src/AbpDocker.Web/Dockerfile

  identityserver:
    image: abpdockeridentityserver
    ports:
      - 9006:80
    build:
      context: .
      dockerfile: src/AbpDocker.IdentityServer/Dockerfile

  httpapi:
    image: abpdockerhttpapihost
    ports:
      - 9007:80
    build:
      context: .
      dockerfile: src/AbpDocker.HttpApi.Host/Dockerfile

Build docker containers

Navigate to the directory where the docker-compose.yml file is present and then run the following command.

docker-compose build

Deploy the docker stack

docker stack deploy -c .\docker-compose.yml abpdocker

Run the app

Visit the web app in http://host.docker.internal:9005

Visit the identity server in http://host.docker.internal:9006

Visit the api in http://host.docker.internal:9007

Repo: https://github.com/antosubash/AbpWithDocker