Docker Compose: React, Node.js, MySQL example

Docker provides lightweight containers to run services in isolation from our infrastructure so we can deliver software quickly. In this tutorial, I will show you how to dockerize React, Nodejs Express and MySQL example using Docker Compose.

Related Posts:
React + Node.js + Express + MySQL example: CRUD App
React Redux + Node.js + Express + MySQL example: CRUD App
React + Node.js Express: User Authentication with JWT example
Integrate React with Node.js Express on same Server/Port
Docker Compose: React + Node.js Express + MongoDB


React, Node.js, MySQL with Docker Overview

Assume that we have a fullstack React + Nodejs Application working with MySQL database.
The problem is to containerize a system that requires more than one Docker container:

  • React for UI
  • Node.js Express for API
  • MySQL for database

Docker Compose helps us setup the system more easily and efficiently than with only Docker. We’re gonna following these steps:

  • Setup Nodejs App working with MySQL database.
  • Create Dockerfile for Nodejs App.
  • Setup React App.
  • Create Dockerfile for React App.
  • Write Docker Compose configurations in YAML file.
  • Set Environment variables for Docker Compose
  • Run the system.

Directory Structure:

docker-compose-react-nodej-mysql-example-structure

Setup Nodejs App

You can read and get Github source code from one of following tutorials:
Build Node.js Rest APIs with Express, Sequelize & MySQL
Node.js Express: Token Based Authentication & Authorization

Using the code base above, we put the Nodejs project in bezkoder-api folder and modify some files to work with environment variables.

Firstly, let’s add dotenv module into package.json.

{
  ...
  "dependencies": {
    "dotenv": "^10.0.0",
    ...
  }
}

Next we import dotenv in server.js and use process.env for setting up CORS and port.

require("dotenv").config();
...
var corsOptions = {
  origin: process.env.CLIENT_ORIGIN || "http://localhost:8081"
};

app.use(cors(corsOptions));
..
// set port, listen for requests
const PORT = process.env.NODE_DOCKER_PORT || 8080;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}.`);
});

Then we change modify database configuration and initialization.

app/config/db.config.js

module.exports = {
  HOST: process.env.DB_HOST,
  USER: process.env.DB_USER,
  PASSWORD: process.env.DB_PASSWORD,
  DB: process.env.DB_NAME,
  port: process.env.DB_PORT,
  dialect: "mysql",
  pool: {
    max: 5,
    min: 0,
    acquire: 30000,
    idle: 10000
  }
};

app/models/index.js

const dbConfig = require("../config/db.config.js");

const Sequelize = require("sequelize");
const sequelize = new Sequelize(dbConfig.DB, dbConfig.USER, dbConfig.PASSWORD, {
  host: dbConfig.HOST,
  dialect: dbConfig.dialect,
  port: dbConfig.port,
  operatorsAliases: false,

  pool: {
    max: dbConfig.pool.max,
    min: dbConfig.pool.min,
    acquire: dbConfig.pool.acquire,
    idle: dbConfig.pool.idle
  }
});

...

We also need to make a .env sample file that shows all necessary arguments.

bezkoder-api/.env.sample

DB_HOST=localhost
DB_USER=root
DB_PASSWORD=123456
DB_NAME=testdb
DB_PORT=3306

NODE_DOCKER_PORT=8080

CLIENT_ORIGIN=http://127.0.0.1:8081

Create Dockerfile for Nodejs App

Dockerfile defines a list of commands that Docker uses for setting up the Node.js application environment. So we put the file in bezkoder-api folder.

Because we will use Docker Compose, we won’t define all the configuration commands in this Dockerfile.

bezkoder-api/Dockerfile

FROM node:14

WORKDIR /bezkoder-api
COPY package.json .
RUN npm install
COPY . .
CMD npm start

Let me explain some points:

  • FROM: install the image of the Node.js version.
  • WORKDIR: path of the working directory.
  • COPY: copy package.json file to the container, then the second one copies all the files inside the project directory.
  • RUN: execute a command-line inside the container: npm install to install the dependencies in package.json.
  • CMD: run script npm start after the image is built.

Setup React App

You can read and get Github source code from one of following tutorials:
React CRUD example to consume Web API
React Typescript CRUD example to consume Web API
React Redux CRUD App example with Rest API
React Hooks CRUD example to consume Web API
React Table example: CRUD App with react-table v7
React Material UI examples with a CRUD Application
React JWT Authentication & Authorization example
React + Redux: JWT Authentication & Authorization example

Using the code base above, we put the React project in bezkoder-ui folder and do some work.

Firstly, let’s remove .env file because we’re gonna work with environment variable from Docker.

Then we open http-common.js for updating baseURL of axios instance with process.env.REACT_APP_API_BASE_URL.

import axios from "axios";

export default axios.create({
  baseURL: process.env.REACT_APP_API_BASE_URL || 'http://localhost:8080/api',
  headers: {
    "Content-type": "application/json"
  }
});

Create Dockerfile for React App

We’re gonna deploy the React app behind an Nginx server.

Same as Nodejs, we put Dockerfile inside bezkoder-ui folder.

bezkoder-upi/Dockerfile

# Stage 1
FROM node:14 as build-stage

WORKDIR /bezkoder-ui
COPY package.json .
RUN npm install
COPY . .

ARG REACT_APP_API_BASE_URL
ENV REACT_APP_API_BASE_URL=$REACT_APP_API_BASE_URL

RUN npm run build

# Stage 2
FROM nginx:1.17.0-alpine

COPY --from=build-stage /bezkoder-ui/build /usr/share/nginx/html
EXPOSE $REACT_DOCKER_PORT

CMD nginx -g 'daemon off;'

There are two stage:
– Stage 1: Build the React application

  • FROM: install the image of the Node.js version.
  • WORKDIR: path of the working directory.
  • COPY: copy package.json file to the container, then the second one copies all the files inside the project directory.
  • RUN: execute a command-line inside the container: npm install to install the dependencies in package.json.
  • ARG and ENV: get argument and set environment variable (prefix REACT_APP_ is required).
  • run script npm run build after the image is built, the product will be stored in build folder.

– Stage 2: Serve the React application with Nginx

  • install the image of the nginx alpine version.
  • copy the react build from Stage 1 into /usr/share/nginx/html folder.
  • expose port (should be 80) to the Docker host.
  • daemon off; directive tells Nginx to stay in the foreground.

Write Docker Compose for React, Node.js and MySQL

On the root of the project directory, we’re gonna create the docker-compose.yml file. Follow version 3 syntax defined by Docker:

version: '3.8'

services: 
    mysqldb:
    bezkoder-api:
    bezkoder-ui:

volumes:

networks:
  • version: Docker Compose file format version will be used.
  • services: individual services in isolated containers. Our application has three services: bezkoder-ui (React), bezkoder-api (Nodejs) and mysqldb (MySQL database).
  • volumes: named volumes that keeps our data alive after restart.
  • networks: facilitate communication between containers

Let’s implement the details.

docker-compose.yml

version: '3.8'

services:
  mysqldb:
    image: mysql:5.7
    restart: unless-stopped
    env_file: ./.env
    environment:
      - MYSQL_ROOT_PASSWORD=$MYSQLDB_ROOT_PASSWORD
      - MYSQL_DATABASE=$MYSQLDB_DATABASE
    ports:
      - $MYSQLDB_LOCAL_PORT:$MYSQLDB_DOCKER_PORT
    volumes:
      - db:/var/lib/mysql
    networks:
      - backend
  
  bezkoder-api:
    depends_on:
      - mysqldb
    build: ./bezkoder-api
    restart: unless-stopped
    env_file: ./.env
    ports:
      - $NODE_LOCAL_PORT:$NODE_DOCKER_PORT
    environment:
      - DB_HOST=mysqldb
      - DB_USER=$MYSQLDB_USER
      - DB_PASSWORD=$MYSQLDB_ROOT_PASSWORD
      - DB_NAME=$MYSQLDB_DATABASE
      - DB_PORT=$MYSQLDB_DOCKER_PORT
      - CLIENT_ORIGIN=$CLIENT_ORIGIN
    networks:
      - backend
      - frontend

  bezkoder-ui:
    depends_on:
      - bezkoder-api
    build:
      context: ./bezkoder-ui
      args:
        - REACT_APP_API_BASE_URL=$CLIENT_API_BASE_URL
    ports:
      - $REACT_LOCAL_PORT:$REACT_DOCKER_PORT
    networks:
      - frontend  

volumes: 
  db:

networks:
  backend:
  frontend:

mysqldb:

  • image: official Docker image
  • restart: configure the restart policy
  • env_file: specify our .env path that we will create later
  • environment: provide setting using environment variables
  • ports: specify ports will be used
  • volumes: map volume folders
  • networks: join backend network

bezkoder-api:

  • depends_on: dependency order, mysqldb is started before bezkoder-api
  • build: configuration options that are applied at build time that we defined in the Dockerfile with relative path
  • environment: environmental variables that Node application uses
  • networks: join both backend and frontent networks

bezkoder-ui:

  • depends_on: start after bezkoder-api
  • build-args: add build arguments – environment variables accessible only during the build process
  • networks: join only frontent network

You should note that the host port (LOCAL_PORT) and the container port (DOCKER_PORT) is different. Networked service-to-service communication uses the container port, and the outside uses the host port.

Docker Compose Environment variables

In the service configuration, we used environmental variables defined inside the .env file. Now we start writing it.

.env

MYSQLDB_USER=root
MYSQLDB_ROOT_PASSWORD=123456
MYSQLDB_DATABASE=bezkoder_db
MYSQLDB_LOCAL_PORT=3307
MYSQLDB_DOCKER_PORT=3306

NODE_LOCAL_PORT=6868
NODE_DOCKER_PORT=8080

CLIENT_ORIGIN=http://127.0.0.1:8888
CLIENT_API_BASE_URL=http://127.0.0.1:6868/api

REACT_LOCAL_PORT=8888
REACT_DOCKER_PORT=80

Run React, Nodejs, MySQL with Docker Compose

We can easily run the whole with only a single command:
docker-compose up

Docker will pull the MySQL and Node.js images (if our machine does not have it before).

The services can be run on the background with command:
docker-compose up -d

$ docker-compose up -d
Creating network "react-node-mysql_backend" with the default driver
Creating network "react-node-mysql_frontend" with the default driver
Pulling mysqldb (mysql:5.7)...
5.7: Pulling from library/mysql
e1acddbe380c: Pull complete
bed879327370: Pull complete
03285f80bafd: Pull complete
ccc17412a00a: Pull complete
1f556ecc09d1: Pull complete
adc5528e468d: Pull complete
1afc286d5d53: Pull complete
4d2d9261e3ad: Pull complete
ac609d7b31f8: Pull complete
53ee1339bc3a: Pull complete
b0c0a831a707: Pull complete
Digest: sha256:7cf2e7d7ff876f93c8601406a5aa17484e6623875e64e7acc71432ad8e0a3d7e
Status: Downloaded newer image for mysql:5.7
Building bezkoder-api
Sending build context to Docker daemon   21.5kB
Step 1/6 : FROM node:14
 ---> 256d6360f157
Step 2/6 : WORKDIR /bezkoder-api
 ---> Running in 02b51e5de09a
Removing intermediate container 02b51e5de09a
 ---> d61270248501
Step 3/6 : COPY package.json .
 ---> 3e30f486372a
Step 4/6 : RUN npm install
 ---> Running in 1d47f7f97db4

added 88 packages from 144 contributors and audited 88 packages in 9.573s
found 0 vulnerabilities

Removing intermediate container 1d47f7f97db4
 ---> ed2adf47e50c
Step 5/6 : COPY . .
 ---> 99b5d613aa44
Step 6/6 : CMD npm start
 ---> Running in fe57b7105262
Removing intermediate container fe57b7105262
 ---> fdb8c0c4944d
Successfully built fdb8c0c4944d
Successfully tagged react-node-mysql_bezkoder-api:latest
WARNING: Image for service bezkoder-api was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Building bezkoder-ui
Sending build context to Docker daemon  67.07kB
Step 1/12 : FROM node:14 as build-stage
 ---> 256d6360f157
Step 2/12 : WORKDIR /bezkoder-ui
 ---> Running in f2c903735326
Removing intermediate container f2c903735326
 ---> 0c7a0d1b82e8
Step 3/12 : COPY package.json .
 ---> df4b981a8b11
Step 4/12 : RUN npm install
 ---> Running in 037e2cd52d22
 
added 1661 packages from 793 contributors and audited 1666 packages in 115.777s

94 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Removing intermediate container 037e2cd52d22
 ---> d9da72b94ce6
Step 5/12 : COPY . .
 ---> fcad3fec4a85
Step 6/12 : ARG REACT_APP_API_BASE_URL
 ---> Running in a3f97d986b13
Removing intermediate container a3f97d986b13
 ---> 49bca49aecfe
Step 7/12 : ENV REACT_APP_API_BASE_URL=$REACT_APP_API_BASE_URL
 ---> Running in afde27025d4d
Removing intermediate container afde27025d4d
 ---> 1fb37fb59a2b
Step 8/12 : RUN npm run build
 ---> Running in 18972278f811

> [email protected] build /bezkoder-ui
> react-scripts build

Creating an optimized production build...
Compiled successfully.

File sizes after gzip:

  52.78 KB  build/static/js/2.c9e8967b.chunk.js
  22.71 KB  build/static/css/2.fa6c921b.chunk.css
  2.39 KB   build/static/js/main.e0402e2b.chunk.js
  776 B     build/static/js/runtime-main.99b514f4.js
  144 B     build/static/css/main.9c6cdb86.chunk.css

The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.

The build folder is ready to be deployed.
You may serve it with a static server:

  npm install -g serve
  serve -s build

Find out more about deployment here:

  bit.ly/CRA-deploy

Removing intermediate container 18972278f811
 ---> ffd4fac12fb1
Step 9/12 : FROM nginx:1.17.0-alpine
 ---> bfba26ca350c
Step 10/12 : COPY --from=build-stage /bezkoder-ui/build /usr/share/nginx/html
 ---> 67a2ac7539dc
Step 11/12 : EXPOSE $REACT_DOCKER_PORT
 ---> Running in 502069bd40c4
Removing intermediate container 502069bd40c4
 ---> dbd7c57727ff
Step 12/12 : CMD nginx -g 'daemon off;'
 ---> Running in 3fc54ee28b01
Removing intermediate container 3fc54ee28b01
 ---> cd8182c8bcb5
Successfully built cd8182c8bcb5
Successfully tagged react-node-mysql_bezkoder-ui:latest
WARNING: Image for service bezkoder-ui was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating react-node-mysql_mysqldb_1 ... done
Creating react-node-mysql_bezkoder-api_1 ... done
Creating react-node-mysql_bezkoder-ui_1  ... done

Now you can check the current working containers:

$ docker ps
CONTAINER ID   IMAGE                           COMMAND                  CREATED         STATUS         PORTS                                                  NAMES
a4acddb44601   react-node-mysql_bezkoder-ui    "/bin/sh -c 'nginx -…"   2 minutes ago   Up 2 minutes   0.0.0.0:8888->80/tcp, :::8888->80/tcp                  react-node-mysql_bezkoder-ui_1
b34c333c3dcc   react-node-mysql_bezkoder-api   "docker-entrypoint.s…"   2 minutes ago   Up 2 minutes   0.0.0.0:6868->8080/tcp, :::6868->8080/tcp              react-node-mysql_bezkoder-api_1
adda50c55a5c   mysql:5.7                       "docker-entrypoint.s…"   2 minutes ago   Up 2 minutes   33060/tcp, 0.0.0.0:3307->3306/tcp, :::3307->3306/tcp   react-node-mysql_mysqldb_1

And Docker images:

$ docker images
REPOSITORY                      TAG      IMAGE ID       CREATED         SIZE
react-node-mysql_bezkoder-ui    latest   cd8182c8bcb5   3 minutes ago   22MB
react-node-mysql_bezkoder-api   latest   fdb8c0c4944d   6 minutes ago   965MB
mysql                           5.7      6c20ffa54f86   7 minutes ago   448MB

Test the React UI:

docker-compose-react-nodej-mysql-example-test-ui

MySQL Database:

docker-compose-react-nodej-mysql-example-test-database

And Node.js Express API:

docker-compose-react-nodej-mysql-example-test-api

Stop the Application

Stopping all the running containers is also simple with a single command:
docker-compose down

$ docker-compose down
Stopping react-node-mysql_bezkoder-ui_1  ... done
Stopping react-node-mysql_bezkoder-api_1 ... done
Stopping react-node-mysql_mysqldb_1      ... done
Removing react-node-mysql_bezkoder-ui_1  ... done
Removing react-node-mysql_bezkoder-api_1 ... done
Removing react-node-mysql_mysqldb_1      ... done
Removing network react-node-mysql_backend
Removing network react-node-mysql_frontend

If you need to stop and remove all containers, networks, and all images used by any service in docker-compose.yml file, use the command:
docker-compose down --rmi all

Conclusion

Today we’ve successfully created Docker Compose file for React, Nodejs and MySQL application. Now we can deploy fullstack React + Nodejs Express and MySQL with Docker on a very simple way: docker-compose.yml.

You can apply this way to one of following project:
React + Node.js + Express + MySQL example: CRUD App
React Redux + Node.js + Express + MySQL example: CRUD App
React + Node.js Express: User Authentication with JWT example

Or using another Database:
Docker Compose: React + Node.js Express + MongoDB

Happy Learning! See you again.

Source Code

The source code for this tutorial can be found at Github.

You can deploy the container on Digital Ocean with very small budget: 5$/month.
Using referral link below, you will have 200$ in credit over 60 days. After that, you can stop the VPS with no cost.

DigitalOcean Referral Badge