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
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:
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:
touch services/src/api/Dockerfile
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
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;
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.
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
docker run -p 1379:3333 -d docker-adonis-api
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
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
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
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
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
Let's rebuild and spin up the containers we defined with one commmand
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:
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.
Top comments (4)
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
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.
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.
If you've got any questions, I'd be quite happy to give answers.