.NET and MySQL in Docker

In the interests of full disclosure, I know very little (very little indeed!) about .NET. But I do enjoy figuring things out. In this post I’ve documented what I learned when trying to connect a simple .NET application to MySQL using Docker Compose.

We’re going to try to do this using Docker as far as possible, which will allow me to avoid having to set up .NET on my local machine.

📢 I’m not going to be remotely concerned about security. I just want something that works.

Create a Console Application

Launch a BASH shell in a .NET SDK container.

docker run -it -v $(pwd):/app mcr.microsoft.com/dotnet/sdk:8.0 /bin/bash

Create a new console application.

dotnet new console -n docker-mysql

That will set up a docker-mysql folder with the required project structure.

Add the MySql.Data package.

dotnet add package MySql.Data

If you take a look at the resulting docker-mysql.csproj file you’ll see that it includes MySql.Data.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>docker_mysql</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="MySql.Data" Version="8.2.0" />
  </ItemGroup>

</Project>

Create a simple application in Program.cs.

using System;
using MySql.Data.MySqlClient;

namespace DockerMySQL {
    class Program {
        static void Main(string[] args) {
            string connectionString = "server=database;user=root;password=test123;database=mysql";

            System.Threading.Thread.Sleep(5000);

            try {
                using (MySqlConnection connection = new MySqlConnection(connectionString)) {
                    connection.Open();
                    connection.Close();
                }
            } catch (Exception ex) {
                Console.WriteLine(ex.ToString());
                Environment.Exit(1);
            }

            Console.WriteLine("Connected to MySQL!");
        }
    }
}

Docker Image

We’re going to build the .NET application in a Docker image. Here’s the Dockerfile.

# Stage 1: Build application.
#
FROM mcr.microsoft.com/dotnet/sdk:latest AS build
WORKDIR /app
COPY docker-mysql /app
RUN dotnet restore docker-mysql.csproj
RUN dotnet build docker-mysql.csproj -c Release -o /app/build

FROM build AS publish
RUN dotnet publish docker-mysql.csproj -c Release -o /app/publish

# Stage 2: Create runtime image.
#
FROM mcr.microsoft.com/dotnet/runtime:latest AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "docker-mysql.dll"]

Now build the image.

docker build -t docker-mysql .

Docker Compose

Finally we need to set up Docker Compose so that it launches MySQL and our .NET app.

services:
  client:
    image: docker-mysql
    container_name: client
    build: 
      context: .
      dockerfile: Dockerfile
    depends_on:
      - database
    restart: always
  
  database: 
    image: mysql
    container_name: mysql
    volumes:
      - db-volume:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: test123

volumes:
  db-volume:

We created a Docker volume, db-volume, which is where the database would persist any content.

The MySQL service is labelled database, so this is the name that’s used for server in the .NET connection string. The connection string also specifies that the application will connect to the database as the root user and gives the same password provided in docker-compose.yml.

Despite the fact that the client service depends on the database service, this does not guarantee that the database will be ready to accept connections when the client application starts. Ideally you should build the application so that it retries connecting to the database. Since I’m lazy I just put in a brief pause before it tries to connect.

With all of that in place we can bring up Docker Compose.

docker-compose up

That generates the following (aggressively redacted) output.

Creating mysql ... done
Creating client ... done
Attaching to mysql, client
mysql       | 2024-01-15 07:25:46+00:00 Entrypoint script for MySQL Server 8.2.0-1.el8 started.
mysql       | 2024-01-15 07:25:46+00:00 Switching to dedicated user 'mysql'
mysql       | 2024-01-15 07:25:46+00:00 Entrypoint script for MySQL Server 8.2.0-1.el8 started.
mysql       | 2024-01-15T07:25:46.69624 MySQL Server - start.
mysql       | 2024-01-15T07:25:46.88333 /usr/sbin/mysqld (mysqld 8.2.0) starting as process 1
mysql       | 2024-01-15T07:25:46.88711 InnoDB initialization has started.
mysql       | 2024-01-15T07:25:46.96072 InnoDB initialization has ended.
mysql       | 2024-01-15T07:25:47.08986 CA certificate ca.pem is self signed.
mysql       | 2024-01-15T07:25:47.10250 /usr/sbin/mysqld: ready for connections.
mysql       | 2024-01-15T07:25:47.10250 X Plugin ready for connections.
client      | Connected to MySQL!
Stopping client ... done
Stopping mysql  ... done

As you can see from the single client log, the application successfully connects to the database.