Setting up Adonis.js with Docker & MySQL
Tags: javascript, node, devops, webdev
Published: 2021-02-09 06:42:09

_This will probably be the millionth Docker tutorial but I felt I had to do it as resources dedicated to my particular use case (setting up Adonis.js on a Windows machine with MySQL) were particularly difficult to find._

## Assumptions

Diving into this tutorial, I will be making some basic assumptions:

- You are familiar with running Adonis apps on a Windows setup.
- You have a basic understanding of Docker (You don't have to be a DevOps whiz though)

## Installation

To keep this tutorial concise as possible, we will not be covering Docker installation here because the [fine folks at Docker already did](https://docs.docker.com/get-started/)

## Setup

We will create a directory to house our various services. Visit your preferred projects folder, open a command window and run:

```bash
mkdir -p docker-adonis-starter\services\api

cd docker-adonis-starter
```

These commands will create directories for our `api` service. We also need to add a starter template for the `api` and a `docker-compose.yml` to configure our various containers:

```
touch docker-compose.yml
```

Clone the adonis starter template:

```bash
git clone --dissociate https://github.com/adonisjs/adonis-api-app services/api
```

We also need to setup our `Dockerfile` to allow us properly setup our `api` service:

```bash
touch services/src/api/Dockerfile
```

We will be pulling a pre-built image and setting up our installation. Add these lines to our `Dockerfile`:

```Dockerfile
# We'll use the Node slim image as a base cos it's light and nice
FROM node:10-alpine

WORKDIR /usr/src/services/api

# Copy package.json & package-lock.json to the root of the api dir
COPY package*.json ./

# Create an .env file by copying the .env.example file
COPY .env.example .env

# Add node_modules to the envionmental path variable so we can run binaries easily
ENV PATH /usr/src/services/api/node_modules/.bin:$PATH

USER root

# Install the good ol' NPM modules and get Adonis CLI in the game
RUN npm install --no-optional

# We'll use PM2 as a process manager for our Node server
RUN npm i -g pm2

# Copy everything to the root of the API service docker volume, and expose port to the outside world
COPY --chown=node:node . .

# Let all incoming connections use the port below
EXPOSE 1379

CMD npm run pm2:start
```
Create a `PM2` config file so we can specify how many instances/clusters we need:

```Javascript
var pm2Config = {
  apps: [
    {
      name: "server",
      script: "server.js",
      exec_mode: "cluster_mode",
      instances: 1,
      watch: false,
      ignore_watch: ["node_modules", ".git", "tmp", "./Dockerfile"],
    },
  ],
};

module.exports = pm2Config;
```

Don't forget to add your `.dockerignore` file with the content below so we avoid copying these to our Docker image.

```
node_modules
npm-debug.log
Dockerfile*
docker-compose*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode
```

We will build this into a Docker image and tag it so we can run it independently and verify that our Dockerfile is all good. Remember to replace `<your username>` in the placeholder below.

```bash
docker build -t <your username>/docker-adonis-api .
```

It is time to run our Docker image and confirm it works as expected. We will make connections available on the exposed port

```bash
docker run -p 1379:3333 -d docker-adonis-api
```

If you visit [http://localhost:1379](http://localhost:1379), you should see a nice welcome page from Adonis.

Good work! Now we need to create a `docker-compose.yml` file at the root of our working directory so we can configure our Docker containers and services.

```yml
version: "3.2"

services:
  docker-adonis-api:
    image: docker-adonis-api
    container_name: docker-adonis-api
    restart: unless-stopped

    # We specify the image for this service and where we can build that image from
    build:
      context: ./services/api
      dockerfile: Dockerfile

    # We can use these volumes to specify where we want our files to be accessible at.
    # It's best to house all node modules on a separate volume
    volumes:
      - "./services/api:/usr/src/services/api"
      - "./services/api/node_modules"
    ports:
      - "1379:3333"

    # For Windows, we need to enable Chokidar polling so our file changes reflect in real-time.
    environment:
      - CHOKIDAR_USEPOLLING=1
```

We need to create a network so our api can communicate with other services. We also plan on using `MySQL` as our primary database so we will also configure a `MySQL` service as a dependency of our app. Add these lines just below the `ports` entry in the `docker-compose.yml`.

```yml
networks:
  - app-network
depends_on:
  - adonis-mysql
  - adonis-mysql-testing
```

I personally like maintaining separate testing and staging databases in addition to the main thing so I am adding a test database container as a dependency.

We will make an update to our `docker-compose.yml` and add the database instances. Connections will be made on port `1380` and we will add a health check entry so our app _only starts running_ when the MySQL connection is ready to accept requests.

```yml
  adonis-mysql:
    # We'll use the MySQL 5.7 image as our base image. Less likely to spring unexpected surprises.
    image: mysql:5.7
    restart: always
    container_name: adonis-mysql
    healthcheck:
      test: ["CMD", "curl", "-fk", "tcp://localhost:3306"]
      interval: 300s
      timeout: 400s
      retries: 10
    ports:
      - 3306:3306
    expose:
      - "3306"
    # It is best to bind sockets on Windows.
    command: --innodb_use_native_aio=0 --socket=/tmp/mysql.sock --bind_address=0.0.0.0

    # We'd like to be able to access our data files on our local filesystem
    volumes:
      - ./db:/var/lib/mysql

    # It is recommended to not do this in production as it is wildly insecure
    environment:
      # So you don't have to use root, but you can if you like
      MYSQL_USER: 'mr-adonis'
      # You can use whatever password you like
      MYSQL_PASSWORD: 'password'
      MYSQL_DATABASE: 'adonis-mysql'
      # Password for root access
      MYSQL_ROOT_PASSWORD: 'password'
      
  adonis-mysql-testing:
    image: mysql:5.7
    container_name: adonis-mysql-testing
    healthcheck:
      test: ["CMD", "curl", "-fk", "tcp://localhost:3306"]
      interval: 300s
      timeout: 400s
      retries: 10
    ports:
      - 1381:3306
    expose:
      - "3306"
    command: --innodb_use_native_aio=0 --socket=/tmp/mysql.sock --bind_address=0.0.0.0
    volumes:
      - ./db-testing:/var/lib/mysql
    environment:
      # So you don't have to use root, but you can if you like
      MYSQL_USER: 'mr-adonis'
      # You can use whatever password you like
      MYSQL_PASSWORD: 'password'
      # Password for root access
      MYSQL_ROOT_PASSWORD: 'password'
      MYSQL_DATABASE: "adonis-mysql-test"
      MYSQL_LOG_CONSOLE: "true"
    restart: always
```

Let us remember to configure the network we use for intra-service communication. Add this block at the bottom just below `services`

```yml
networks:
  app-network:
    driver: bridge
```

We'll setup the environmental variables needed to connect our app to our database, so we need to modify the `.env` file we created earlier at `services/api` and add our credentials we newly setup:

```env
HOST=0.0.0.0
PORT=3333
DB_CONNECTION=mysql
DB_HOST=adonis-mysql
DB_PORT=3306
DB_USER=root
DB_PASSWORD=password
DB_DATABASE=adonis-mysql
```

Let's rebuild and spin up the containers we defined with one commmand 

```bash
docker-compose up --build -d
```

Now we have our MySQL containers ready, we need to run migrations to create the database tables and we also need to seed our tables with some data. To accomplish that, let us gain shell access into the `docker-adonis-api` container by executing the command below:

```bash
docker-compose exec docker-adonis-api sh
```

Now run the below commands and we should have our database ready for use and. 

```
node ace migration:run && adonis seed
```

We exit the shell and with that congratulations in order as we have successfully set up an Adonis application with a running MySQL instance available to us at a short notice. 

## Conclusion

Docker Compose is a fantastic tool for helping achieve a smoother workflow and a more reliable development environment.

This just scratches the surface of what we could achieve but you could allow your creativity soar.

A fun exercise could be adding a Swagger service to the `docker-compose.yml` and getting it to run.

## Resources

- [Github Repository for Docker Adonis Starter](https://github.com/creatrixity/docker-adonis-starter)
- [Official Docker starter guide](https://docs.docker.com/get-started/)
- [Adonis Installation Guide](https://adonisjs.com/docs/4.1/installation#_system_requirements)