DEV Community

Cover image for Setting up Adonis.js with Docker & MySQL
Caleb Mathew
Caleb Mathew

Posted on

Setting up Adonis.js with Docker & MySQL

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

Setup

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

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

cd docker-adonis-starter
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Clone the adonis starter template:

git clone --dissociate https://github.com/adonisjs/adonis-api-app services/api
Enter fullscreen mode Exit fullscreen mode

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

touch services/src/api/Dockerfile
Enter fullscreen mode Exit fullscreen mode

We will be pulling a pre-built image and setting up our installation. Add these lines to our 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
Enter fullscreen mode Exit fullscreen mode

Create a PM2 config file so we can specify how many instances/clusters we need:

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

docker build -t <your username>/docker-adonis-api .
Enter fullscreen mode Exit fullscreen mode

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

docker run -p 1379:3333 -d docker-adonis-api
Enter fullscreen mode Exit fullscreen mode

If you visit 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.

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
Enter fullscreen mode Exit fullscreen mode

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.

networks:
  - app-network
depends_on:
  - adonis-mysql
  - adonis-mysql-testing
Enter fullscreen mode Exit fullscreen mode

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.

  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
Enter fullscreen mode Exit fullscreen mode

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

networks:
  app-network:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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

docker-compose up --build -d
Enter fullscreen mode Exit fullscreen mode

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:

docker-compose exec docker-adonis-api sh
Enter fullscreen mode Exit fullscreen mode

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

node ace migration:run && adonis seed
Enter fullscreen mode Exit fullscreen mode

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

Top comments (4)

Collapse
 
alaomichael profile image
Michael Alao

Thank you for this article.
Please can you send me the file structure.

I have the following error in my terminal:
Error response from daemon: invalid mount config for type "volume": invalid mount path: 'services/api/node_modules' mount path must be absolute

Collapse
 
wemersonrv profile image
Wemerson Couto Guimarães

Hello. Great article.

I'm trying to make it to work locally on my development, so how can i configure it to run only the adonis CLI instead PM2 ? ("adonis serve --dev")

Ah of course, i don't want to run npm install on build, i need to run it every time new package was added to package.json; so need to run this inside the container from host.

Collapse
 
computamike profile image
Mike Hingley

this is timely - I'm in the process of 'dockerizing' an adonisjs app - but I'm wondering about the PM2 stuff - because I think you get the same benefits of process management (restart if crashed etc) from docker restart policies. I'm "borrowing" part of the guide but I'll see if I can get it working using docker restart policies.

Collapse
 
kaymathew profile image
Caleb Mathew

If you've got any questions, I'd be quite happy to give answers.