Docker is a containerization platform that uses OS-level virtualization to package software applications with their dependencies. It allows to build,test and deploy the application quickly.
Docker uses the concept of docker images and containers that can be multiple and need to be handled. In order to make efficient and quick utilization, it uses the concept of docker-compose.
MERN stack represents the full-stack application, stands for MongoDb(NoSQL) database, Express /Node JS for backend, RactJs/NextJs for frontend. We will use Next Js for frontend.
In this article, we'll explain how Docker compose works and helps in dockerization and containarization. And deployment of the MERN stack application using it.
We will cover:
Docker Compose
How Docker Compose works and it's benefits
Deployment of MERN stack using Docker-compose
- Docker Compose
Docker Compose is a tool provided by Docker that allows you to define and run multi-container Docker applications. It uses a YAML file to configure the services, networks, and volumes required for your application's containers to communicate and work together. Lets' discuss the basic format and structure of docker-compose file.
version: '3'
services:
web:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: my_database
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
In this example:
version: '3'
specifies the version of the Compose file syntax being used.Under
services
, each service (container) is defined. In this case, there are two services:web
anddb
.For each service, you define the Docker image to use, any ports to expose, environment variables, and volumes to mount.
Under
volumes
, named volumes are defined. These are used to persist data between container restarts.
To use Docker Compose with this file, you would navigate to the directory containing the docker-compose.yml
file and run commands such as docker-compose up
to start the containers defined in the file, or docker-compose down
to stop and remove the containers. There are many other commands and options available as well, which you can explore in the Docker Compose documentation.
- How docker-compse works and it's benefits
Docker Compose is a tool that simplifies the management of multi-container Docker applications. It works by allowing you to define your application's services, networks, and volumes in a single YAML file called docker-compose.yml
. With this file, you can specify how your application's containers should be configured and how they should interact with each other.
Here's how Docker Compose works and its benefits:
Service Definitions: In the
docker-compose.yml
file, you define the services that make up your application. Each service represents a container. You can specify which Docker image to use, the ports to expose, environment variables, volumes, networks, and other configuration options.Networking: Docker Compose automatically creates a network for your application, allowing containers to communicate with each other using service names as hostnames. This simplifies inter-container communication without the need to manually manage networking configurations.
Volume Management: Docker Compose allows you to define volumes to persist data generated by your containers. These volumes can be shared among containers or mounted from the host machine, providing flexibility in managing data storage.
Orchestration: Docker Compose can start, stop, and manage multiple containers simultaneously based on the configuration specified in the
docker-compose.yml
file. This simplifies the deployment and scaling of multi-container applications.Environment Management: Docker Compose allows you to set environment variables for your services, making it easy to customize the behavior of your containers based on different environments (e.g., development, testing, production).
Dependency Management: Docker Compose can define dependencies between services, ensuring that dependent services are started before the services that rely on them. This helps in orchestrating complex application setups with multiple interconnected components.
Benefits of using Docker Compose include:
Simplicity: Docker Compose provides a simple and intuitive way to define, manage, and deploy multi-container applications using a single configuration file.
Portability: Docker Compose configurations are portable and can be shared across development environments, making it easy to maintain consistency between development, testing, and production environments.
Efficiency: Docker Compose automates the setup and management of multi-container applications, saving time and effort compared to manual configuration and management.
Scalability: Docker Compose can scale your application by adding additional instances of services or by distributing containers across multiple hosts using Docker Swarm or Kubernetes.
- Deployment of MERN stack using Docker-compose
Now, here we will create the MERN stack app, NextJs for frontend and Express Js for backend.
Make sure that you have installed the docker and docker compose and node js
docker --version
docker-compose --version
node --version
---------------------------------------------------------------
sudo apt install docker.io docker-compose nodejs
Folder structure
Let's setup the frontend first, create the latest
npx create-next-app@latest
create the .env for enviromental variables and Dockerfile for docker build .dockerignore for files that need to be ignored while building docker images
Dockerfile
# Use the official Node.js image as base
FROM node:20-alpine
# Set the working directory in the container
WORKDIR /frontend
# Copy package.json and package-lock.json to the working directory
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy the rest of the application code
COPY . .
RUN npm run build
COPY .next ./.next
# Expose port 3000
EXPOSE 3000
# Command to run the application
CMD ["npm", "run", "dev"]
.dockerignore
node_modules
.gitignore
.dist
.env
Similarly, setup for the backend express js
npm init
touch index.js
npm install express dotenv mongoose
package.json
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "sahadev dahit",
"license": "ISC",
"dependencies": {
"dotenv": "^16.4.5",
"express": "^4.18.2",
"mongoose": "^8.2.0"
}
}
after creating index.js let's use the following code
const express = require("express");
const mongoose = require('mongoose');
require("dotenv").config();
const app = express();
const port = process.env.PORT || 4000; // Use process.env.PORT or default to 4000
const secret_one = process.env.secret_one;
const secret_two = process.env.secret_two;
const secret_three = process.env.secret_three;
console.log("mongo url "+process.env.MONGO_URL);
const dbConnect = async () => {
try {
await mongoose.set("strictQuery", false);
const connected = await mongoose.connect(process.env.MONGO_URL);
console.log(`Mongodb connected ${connected.connection.host}`);
} catch (error) {
console.log(`Error: ${error.message}`);
process.exit(1);
}
};
dbConnect()
// Define a route handler for the root path
app.get("/", (req, res) => {
res.send("Hello World! Welcome to docekrization & contanarization");
});
// List all the secret
app.get("/env", (req, res) => {
res.status(200).json({
first: secret_one,
second: secret_two,
third: secret_three,
});
});
// Start the server
app.listen(port, () => {
console.log(`Server is listening at http://localhost:${port}`);
});
inside backend .env
MONGO_URL='mongodb://root:secret@mongo:27017/products?authSource=admin'
PORT=4000
secret_one="From secret One"
secret_two="From secret Two"
secret_three="From secret Three"
Now create the .dockerignore
node_modules
.gitignore
.dist
.env
And also create the Dockerfile
# Use the official Node.js image
FROM node:20-alpine
# Set the working directory in the container
WORKDIR /backend
# Copy package.json and package-lock.json to the working directory
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy the rest of the application code
COPY . .
# Expose port 4000
EXPOSE 4000
# Command to run the application
CMD ["node", "index.js"]
Now setup the docker-compose.yaml
create docker-compose.yml
version: '3'
services:
mongo:
container_name: mongodb
image: mongo:latest
restart: always
ports:
- "27017:27017"
env_file:
- ./.env
volumes:
- "/mongo-data"
networks:
- app_network
backend:
container_name: backend_app
restart: always
build:
context: ./backend
dockerfile: Dockerfile
env_file:
- ./backend/.env
ports:
- "4000:4000"
networks:
- app_network
depends_on:
- mongo
frontend:
container_name: frontend_app
restart: always
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
networks:
- app_network
networks:
app_network:
driver: bridge
also .env file which contains the enviroment variables for docker-compose
MONGO_INITDB_ROOT_USERNAME=root
MONGO_INITDB_ROOT_PASSWORD=secret
MONGO_INITDB_DATABASE=products
This Docker Compose file defines a multi-container application consisting of a MongoDB database (`mongo` service), a backend application (`backend` service), and a frontend application (`frontend` service).
Let's break down the Docker Compose file:
MongoDB Service (
mongo
):This part defines a MongoDB database.
It specifies the image to use for MongoDB, which is the latest version available.
MongoDB listens on port 27017 inside the container, which is mapped to port 27017 on the host machine.
It loads environment variables from a
.env
file.It creates a volume named
/mongo-data
to persist MongoDB data.The service is connected to a custom network named
app_network
.
Backend Service (
backend
):This part defines a backend application.
It specifies the build context for the backend code and loads environment variables from a
.env
file.The backend application listens on port 4000 inside the container, mapped to port 4000 on the host machine.
It depends on the MongoDB service to ensure the database is ready before starting.
The service is connected to the same
app_network
as MongoDB.
Frontend Service (
frontend
):This part defines a frontend application.
It specifies the build context for the frontend code.
The frontend application listens on port 3000 inside the container, mapped to port 3000 on the host machine.
It is also connected to the
app_network
.
Networks:
- A custom network named
app_network
is defined to allow communication between containers.
- A custom network named
Environment Variables for MongoDB Initialization:
- Environment variables are set for initializing the MongoDB database with a root username, password, and initial database name.
In summary, this Docker Compose file sets up a development environment with MongoDB, a backend server, and a frontend server, all connected on a custom network. It ensures proper initialization order, where the backend depends on the availability of MongoDB.
sudo docker-compose up -d
# after sucessful sun
sudo docker images
sudo docker ps
now make sure that the mongodb and backend is running
sudo docker logs <container_id>
Here, mongodb and backend service is running properly. And in docker-compose file we are creating the mongo from dockerhub and backend service depends on the mongo service before initialization.
Now check the frontend and backend in their respective port
In this way, we can deploy three-tier full-stack MERN Stack application in Docker using the docker-compose in an efficient way.
Thanks for reading.............................................