Search code examples
dockerasp.net-core-3.1ef-core-3.1debian-busterpomelo-entityframeworkcore-mysql

Cannot connect Asp.net Core WebApi in Docker with EFCore Mysql provider to non Docker Mysql on host server


I have an Asp.Net Core 3.1.3 WebApi with EfCore 3.1.3 and Pomelo.EntityFrameworkCore.MySql as provider This WebApi is built and deployed in Docker.

The host server is Debian 10, it is configured with UFW as firewall.

I have a Mysql Server installed on the host server.

I set the bind address to 0.0.0.0 for Mysql

/etc/mysql/mysql.conf.d/mysqld.cnf

[mysqld]
pid-file        = /var/run/mysqld/mysqld.pid
socket          = /var/run/mysqld/mysqld.sock
datadir         = /var/lib/mysql
log-error       = /var/log/mysql/error.log
bind-address    = 0.0.0.0

I created a rule in UFW to allow only localhost and docker0 bridge ip address enter image description here

enter image description here

In my docker-compose config, I added the ip address of my host server, but the connection to MySql fails

info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
Entity Framework Core 3.1.3 initialized 'ApplicationContext' using provider 'Pomelo.EntityFrameworkCore.MySql' with options: using lazy-loading proxies ServerVersion 8.0.19 MySql 
fail: Microsoft.EntityFrameworkCore.Database.Connection[20004]
An error occurred using the connection to database '<db_name>' on server '<host_ip_address>'.
info: Microsoft.EntityFrameworkCore.Infrastructure[10404]
A transient exception has been encountered during execution and the operation will be retried after 0ms.
MySql.Data.MySqlClient.MySqlException (0x80004005): Connect Timeout expired.
---> System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Net.Sockets.Socket'.

In Startup.cs, here is the config for EfCore:

services.AddDbContext<ApplicationContext>(opts =>
      {
        opts
        .UseLazyLoadingProxies()
        .UseMySql(connectionString, mySqlOptions =>
                    mySqlOptions
                      .ServerVersion(new Version(dbOptions.Version), ServerType.MySql)
                      .CharSet(CharSet.Utf8Mb4)
                      .EnableRetryOnFailure(3))
        .EnableDetailedErrors();
      });

To build the connection string, I use MySql.Data.MySqlClient.MySqlConnectionStringBuilder:

var builder = new MySql.Data.MySqlClient.MySqlConnectionStringBuilder
      {
        Server = dbOptions.Server,
        Database = dbOptions.Name,
        UserID = dbOptions.User,
        Password = dbOptions.Password,       
        PersistSecurityInfo = true
      };

      return builder.ToString();

I generate the migration script and deployed it on the server, since Pomelo sets Database Name to Empty String when calling dbContext.Database.Migrate();

In the dockerfile, I expose ports 80 and 443.

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
...

I sent Database params as Environment variables in the docker-compose file.

I did some data seeding to add users and roles from Identity.

var roleManager = provider.GetRequiredService<RoleManager<RoleModel>>();
roleManager.CreateAsync(new RoleModel(roleName)).GetAwaiter().GetResult();
...

What did I miss?


Solution

  • I did not find any other solution than to deploy my docker container using host network mode in my docker-compose.

    docker-compose.yml

    services:
      <service_name>:
        image: <image_name>
        network_mode: "host" #important here
        ports:
          - "5000:5000"
          - "5001:5001"
        environment:
          ASPNETCORE_Kestrel__Certificates__Default__Password: "${ASPNETCORE_KESTREL_CERTIFICATE_PASSWORD}"
          ASPNETCORE_Kestrel__Certificates__Default__Path: "${ASPNETCORE_KESTREL_CERTIFICATE_PATH}"
          ASPNETCORE_ENVIRONMENT: Production
          "Database:Server": "${Database_Server}"
          "Database:Version": "${Database_Version}"
          "Database:Name": "${Database_Name}"
          "Database:User": "${Database_User}"
          "Database:Password": "${Database_Password}"
        build:
          context: ./Src
          dockerfile: Dockerfile-build
        volumes:
          - ${CERTIFICATE_PATH}:/https
          - ${LOGS_PATH}:/app/logs
    

    In that case, I just need to use the ip address of the host server to connect to MySql (with all Ufw, MySql configuration which come with that).