How To Containerize An Asp.NetCore Api and MySQL DataBase with Docker Compose
Tags: docker, csharp, mysql
Published: 2021-01-02 15:41:50

In this blog post, I am going to be going through how to containerize both a.NetCore API and a MySql Database and connect both together using a docker-compose file. If you are new to Docker you can check out two of my previous posts. [containerize-an-aspnet-core-application-with-docker](https://dev.to/gbengelebs/containerize-an-aspnet-core-application-with-docker-4cmo) and [demystifying-docker](https://dev.to/gbengelebs/demystifying-docker-26id).

# Building the API

The API for the tutorial can be found on GitHub [here](https://github.com/GbengaElebsDev/TestApi.git). If You clone the Url and run a dotnet restore you will have the API I built for this Tutorial. It is essentially the skeletal project when you create a.NetCore WebApi project with a few changes which I will be going through here.

I added the UserController and created two endpoints the GetAllUsers to retrieve users from the database and the AddNewUser Endpoint to add a new user to the database; I used Dapper as my ORM to communicate with the Database. For clarity, I implemented the Database queries in the controller class just to keep it simple. As you can see the UserController is nothing too fancy.
{% gist https://gist.github.com/GbengaElebs/fa1ca96fff36fd8b7c109e21d60715a9 %}

I also added swagger to the startup class for easy API testing.

### CONFIGURATION
The configuration environment variables for our MySql Server will be supplied in our docker-compose file when it is initialized, and if the configuration variables do not exist from the docker-compose file it checks the app settings connection string and gets its configuration. 

![Config Variables](https://dev-to-uploads.s3.amazonaws.com/i/g144z49eikjiqblj1tiv.png)

# DOCKERIZING
In order to follow the next steps, you need to have installed Docker on your PC.[Install Docker](https://docs.docker.com/get-docker/).

### CREATING THE DOCKERFILE
This is a text document that is used by Docker to build a custom image for your Web API. I touched a lot more on this [here](https://dev.to/gbengelebs/containerize-an-aspnet-core-application-with-docker-4cmo).
{% gist https://gist.github.com/GbengaElebs/bf0ba0e07abf81bad25f6417ee557e72 %}

##### Let's go through this a bit
- First, it tells Docker to pull the asp.net runtime image and then switch to /app as its working directory, it should then listen to traffic from port 80 on TCP.

- The rest of the steps builds and publishes the app and then copy the published app to the /app/publish folder in the Docker container. If you have the Docker Extension Installed in VSCode. This docker file can automatically be generated for you.

### CREATING THE DOCKER COMPOSE FILE

The Compose file provides a way to document and configure all of the application's service dependencies (databases, web service APIs, etc). This is a YAML file named “docker-compose.yml” that you put in the root directory of your application. It consists of a series of entries defining each Docker container in your composition. The Docker Compose file for this project can be found below. I have added comments on every line to explain what each line does. When it comes to docker-compose files and YAML files in general spacing and indentation are crucial; you have to keep an eye for that.

{% gist https://gist.github.com/GbengaElebs/7f6fa7996e2e65bc770644fbc6ebe911 %}

#### Let us analyze the docker-compose file a bit.
* Version----Refers to the version of the docker-compose.
* Volumes----Because containers are designed to be stateless, once a container stops running all data stored in that container is not saved and we definitely do not want that for our database container. In order for your data to be persisted after the container has stopped or been destroyed it is essential that you persist your data somewhere outside the container but provide a reference to that path to docker. We specify our volume name called **datafile**
* Services-----This is where we define the application dependencies. In this application, we have two services API and a database.

  1.DATABASE SERVICE
    - image---We define the database image version we want so docker fetches that for us from docker hub.
    - port---here we tell docker to map port 3306 from the docker container to the host machine.
    - volumes----here we define our volumes. datafiles should be stored at this path but that path is actually a reference path to somewhere in our computer that docker has access to.
The second volume is a path to an SQL script to create our Users Table. Supposing you wanted to create a new Table or seed records to the database on initialization you could do it here. Here I created a new Table called Users on Usersdb.

{% gist https://gist.github.com/GbengaElebs/973d43d21398735d43fca40fa47ce447 %}


this line ` - "~/sql-scripts/setup.sql:/docker-entrypoint-initdb.d/1.sql" ` can be interpreted as copying the setup.sql file, located in sql-scripts folder, to the /docker-entrypoint-initdb.d/ folder located in the MySQL Docker container that we’re using and name it (1.sql). By default, all scripts located in this folder will be automatically run during container startup.

* environment variables.
![db Environment Variables](https://dev-to-uploads.s3.amazonaws.com/i/7760mxjp3jzyte8iyta9.png)

Remember those config keys we talked about earlier. You specify them here.
**MYSQL_ROOT_PASSWORD**: assigns a root password to the root user.
**MYSQL_USER**: creates a new user.
**MYSQL_PASSWORD**: Specifies new users' password.
**MYSQL_DATABASE**: creates a new database.
By default, the new user created will have admin rights to the database specified here.
To wrap up the Database service- it pulls the MySQL image from docker hub maps the ports, configures the volume for the DB, and then creates a new user on the database.

  2. API SERVICE
    - build: This means docker should build the docker file specified in this context.
    - depends_on: it depends on the database service defined above hereby linking the two containers.
    - ports: map port 5000 in the docker container to port 80 on the host machine.
    - restart: specifies how often containers should restart.
    - environment: You also specify that the **DBHOST** should be the database service.
> By default Compose sets up a single network for your app. Each container for a service joins the default network and is both reachable by other containers on that network, and discoverable by them at a hostname identical to the container name.
https://docs.docker.com/compose/networking/

The **DBHOST**  variable specifies the hostname for the MySQL database.

Now that we have gone through the docker file and the docker-compose. All we need to do is cd into the projects directory and run this command.
```
docker-compose -f docker-compose.yml up
```
This command tells Compose to start and run the entire app as defined in the (docker-compose.yml) file.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/m6a1ogl4n1k633z353at.png)

As you can see all the steps we defined are being run. If you want it to run in the background you can add the -d flag. Run a `docker ps -a` to see all the containers. our two containers are running.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ulz32oaf7mfk6ky4qhck.png)

Navigate to this URL. http://localhost:5000/swagger/index.html. To check if your containerized web API is running. Add a few users and call the GetUsers Endpoint. 
You can see the users you just added.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/9d5ro965h2t69umdp9uh.png)

If you want to access the database container from the command line you can run this command
```
docker exec -it testapi_database_1 bash
```
*testapi_database_1* is the database container name

Once you are inside your container, you can connect to your MySQL server and create a new Database as follows:
Input your password and check out your data.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/62kklirzq273jnqzh67c.png).
If you run this command
```
docker container inspect testapi_database_1
``` 
you can inspect your testapi_database_1 container
Scroll down you could see all the volumes you created.
```
"Mounts": [
            {
                "Type": "volume",
                "Name": "testapi_datafiles",
                "Source": "/var/lib/docker/volumes/testapi_datafiles/_data",
                "Destination": "/var/lib/mysql",
                "Driver": "local",
                "Mode": "rw",
                "RW": true,
                "Propagation": ""
            },
            {
                "Type": "bind",
                "Source": "/host_mnt/Users/mac/sql-scripts/setup.sql",
                "Destination": "/docker-entrypoint-initdb.d/1.sql",
                "Mode": "rw",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
```

## POSSIBLE ERRORS

1. ERROR: for database  Cannot start service database: Mounts denied: The path /sql-scripts/setup.sql is not shared from the host and is not known to Docker.
You can configure shared paths from Docker -> Preferences... -> Resources -> File Sharing.

It is because docker does not have access to that folder in your computer you can give it access by navigating to your docker desktop dashboard --------->Setting-------->Resources------>FileSharing and then add your own path.

2. ERROR: for testapi_database_1  Cannot start service database: Ports are not available: listen tcp 0.0.0.0:3306: bind: address already in use

Ensure you stop any other instance of the MySql Server you have running on your computer.

## Summary
In this tutorial, we have 
* Created an API. 
* Added a docker file to publish the API's image to a container.
* Created a docker-compose file to create the MySql container and link both containers.
* SuccessFully containerized and linked both our web API with our MySQL database.

Thank you for reading!!!

Follow me here and across my social media for more content like this [Twitter](https://twitter.com/ElegberunDaniel?s=09) [Linkedin](https://www.linkedin.com/in/olugbenga-elegberun/)