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
Contents
- React, Node.js, MySQL with Docker Overview
- Setup Nodejs App
- Create Dockerfile for Nodejs App
- Setup React App
- Create Dockerfile for React App
- Write Docker Compose for React, Node.js and MySQL
- Docker Compose Environment variables
- Run React, Nodejs, MySQL with Docker Compose
- Stop the Application
- Conclusion
- Source Code
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:
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 scriptnpm 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
andENV
: get argument and set environment variable (prefixREACT_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) andmysqldb
(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 imagerestart
: configure the restart policyenv_file
: specify our .env path that we will create laterenvironment
: provide setting using environment variablesports
: specify ports will be usedvolumes
: map volume foldersnetworks
: joinbackend
network
– bezkoder-api:
depends_on
: dependency order, mysqldb is started before bezkoder-apibuild
: configuration options that are applied at build time that we defined in the Dockerfile with relative pathenvironment
: environmental variables that Node application usesnetworks
: join bothbackend
andfrontent
networks
– bezkoder-ui:
depends_on
: start afterbezkoder-api
build-args
: add build arguments – environment variables accessible only during the build processnetworks
: join onlyfrontent
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:
MySQL Database:
And Node.js Express 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.