Dockerize NodeJs MySQL
In this tutorial I am going to explain you how to use docker compose to dockerize your Nodejs MySQL REST API for CRUD operations. I am not going to tell you here how to build the REST CRUD application but you can always go back and check the detail tutorial on this here. I am only going to show you how to dockerize your app using docker compose in Linux environment.
It is a best practice for a container to have only one process with single responsibility. But as I am going to dockerize Nodejs app and MySQL server for storing data for the app, so I need two containers – one for running the Nodejs app and another one for running the MySQL database server.
So these two containers are running independently and to establish communication between these two containers you need docker compose. I am also going to show you how to install docker compose under Linux environment on CentOS operating system.
Using docker compose is basically a three-step process:
- Define your app’s environment with a Dockerfile so it can be reproduced anywhere.
- Define services that make up your app in docker-compose.yml so they can be run together in an isolated environment.
- Run docker-compose up and compose starts and runs your entire app.
Docker Compose Installation
Use the following command to install Docker Compose in your system. I am installing it under Linux environment CentOS operating system.
You can check for the latest release version and also use.
$ sudo curl -L "https://github.com/docker/compose/releases/download/x.xx.x/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
It may ask for password and you can enter for your user’s password.
Next set the permission to make the binary executable using the following command:
$ sudo chmod +x /usr/local/bin/docker-compose
Finally verify that the installation was successful by checking the version using the following command.
For Docker version 2.x.x you use the below command:
$ docker compose version
It will show the following information:
Docker Compose version v2.20.2
For Docker version 1.x.x you use the below command:
$ docker-compose --version
It will show you the following information:
docker-compose version 1.26.2, build eefe0d31
Please make sure you follow the directory structure as given in the source code.
Project Directory Setup
The first thing is to make sure that you have setup the required folders for your app. I am going to create a root folder and inside this root folder I will create two more folders – one for app’s root directory where all your project files will be kept and another one for putting database script.
The top level root folder is nodejs_mysql_rest_api_crud_docker which you can create using the command on your Unix terminal:
$ sudo mkdir nodejs_mysql_rest_api_crud_docker
Inside the above directory I have two more directories – app and db.
Inside the app directory you need to src, config, app.js and package.json files/folders. Inside your db folder you need to have the items.sql script.
I am not going to write any code here but you can always download it from here.
Next you need to create a Dockerfile under your app directory with the following content.
For NodeJs version 18 use the following content:
FROM node:18 COPY . /app WORKDIR /app RUN npm install RUN npm install express RUN npm install mysql COPY . . CMD npm start CMD ["node","app.js"]
For Nodejs 12 use the following content:
FROM node:12 COPY . /app WORKDIR /app RUN npm install RUN npm install express RUN npm install mysql COPY . . RUN apt-get update && apt-get install -y netcat COPY wait.sh /wait.sh RUN chmod +x /wait.sh
Depending on the NodeJs version you need to replace the xx in
I am copying all files/folders from the current directory to app directory. Setting working directory as app directory using WORKDIR command.
The second solution is to use the below line in your docker-compose.yml/yaml file and this file is kept under the folder nodejs_mysql_rest_api_crud_docker.
I have used the above line in my docker compose file, but you can try to implement the first solution in your app.
Next I have written
COPY . . to build the app source.
After that I have installed nc (netcat) package to execute the nc command for checking a particular host/port is up or not. For my case I am checking whether MySQL server is up or not. If MySQL server is not up and by this time the Nodejs application is up then it will throw error because it will try to connect to MySQL server.
So I have written one shell script for this and it is written into wait.sh file which is kept under app directory. This wait.sh file is not required for NodeJs 18 and Docker 2 version because it will retry to connect to the MySQL database until MySQL database gets connected successfully.
#!/bin/sh while ! nc -z db 3306 ; do echo "Waiting for the MySQL Server" sleep 3 done node app.js
In the above shell script you can see that I am waiting for the MySQL server to be startup before starting the Nodejs app. I am also sleeping 3 seconds before checking again whether MySQL server is up or not.
Create a .dockerignore file in the same directory as your Dockerfile with following content:
This will prevent your local modules and debug logs from being copied onto your Docker image and possibly overwriting modules installed within your image.
I already explained why do you need docker compose. So here I am going to show you the whole content for docker-compose.yml/yaml file.
I have used depend-on to arrange the services in order but it does not guarantee that the services kept in order will be executing first and here the situation where you need to control the execution of your services using shell script.
version: "3" #optional for docker version 2 services: nodejs: image: rest-app restart: on-failure build: context: ./app dockerfile: Dockerfile depends_on: - db ports: - "4000:4000" #entrypoint: ["./wait.sh"] #not required for nodejs 18 and docker 2 db: image: mysql:8.0.21/8.0.33 restart: on-failure ports: - "33000:3306" environment: MYSQL_ROOT_PASSWORD: root volumes: - ./db:/docker-entrypoint-initdb.d/:ro
The entrypoint keyword is used to here to execute the shell script file, i.e., wait.sh.
The restart I have used here if app fails to start then only restart the app.
I have mentioned which directory to look for Dockerfile in the app for building the image.
Testing the Application
Well, I am finished with my coding and the required docker configurations.
Execute the command
$ docker-compose up (Docker version 1.x.x) or
$ docker compose up (Docker version 2.x.x) to bring up all services. Wait for few moments, the docker will pull the required images and build the apps and starts up all apps.
If you think that something went wrong in your apps then you can remove all images using the command
$ docker-compose down --rmi all for Docker 1.x.x or
$ docker compose down --rmi all for Docker 2.x.x.
You can also execute command
$ docker-compose up --build for Docker 1.x.x or
$ docker compose up --build for Docker 2.x.x to re-build your images.
When your apps are up and running you can use
links command to access your API endpoints. Even you can access using REST client.
Once your app and MySQL server are up and running, you will see the output similar to the following image:
The app can be access via your server IP as shown in the following image. The below image displays the root path or home page of the app.
Next you can use the Postman tool or any REST client to test your application, only thing you need to use your server IP instead of localhost for the host.
I am not going to show you how to test the REST CRUD operations but it is similar to the example given in Nodejs MySQL REST API for CRUD operations.
You can also access your MySQL server which is running inside the docker container using the following command:
$ mysql --host=127.0.0.1 --port=33000 -u root -p
When it prompts for password, enter your password to get the MySQL client terminal. The port which you see here 33000 is the external port but the port 3306 is used inside the container.
Why I am using different port because 3306 port is already used by MySQL server which is accessible publicly and will be a conflict with the container’s port if I use the same port outside the container.