Here’s a step-by-step guide to containerize your full-stack MERN application, building each stack component locally first and then moving to Docker Compose.
Step 1: Clone Your GitHub Repository and Navigate to Local Directories
- Clone your GitHub repository: Copy your code from your github repository as shown below
Go to your terminal and run:
bash
``
git clone https://github.com/Uwadon1/dockerize-a-mern-stack-app.git
Next you’ll have to move into the newly cloned directory
cd dockerize-a-mern-stack-app
- Directory structure check: Ensure your project folders has the Frontend/Web-tier, Backend/app-tier and Database. If it has, our final set would most likely look like this, please note because we are deploying our database directly from the backend:
`
project-directory/ ├── frontend/ ├── backend/ ├── database/ └── docker-compose.yml (this will be created later)
Best Practices: Consider using a .dockerignore file to exclude unnecessary files from the image
Step 2: Create Dockerfiles for Each Stack Component
- Frontend (React)
- Navigate to the
frontend
folder and create aDockerfile
:
- Navigate to the
`dockerfile
# Dockerfile for frontend
Use the official image as a parent image
Description: Dockerfile for the client side of the MERN stack application
Use the official image as a parent image
FROM node:18.9.1
Set the working directory
WORKDIR /app
Copy the file from your host to your current location
COPY package.json .
Run the command inside your image filesystem
RUN npm install
Inform Docker that the container is listening on the specified port at runtime
EXPOSE 5173
Copy the rest of your app's source code from your host to your image filesystem
COPY . .
Run the specified command within the container
CMD ["npm", "run", "dev"]
- Explanation: This Dockerfile installs dependencies, builds the frontend code, and uses
serve
to host the static files. Dockerfile Breakdown This Dockerfile builds a container image for a React frontend application, leveraging the Node.js environment. Let's break down each line:- FROM node:18.9.1: This line specifies the base image for the container. node:18.9.1 indicates that we're using the official Node.js 18.9.1 image as the starting point.
- WORKDIR /app: This sets the working directory within the container to /app. This is where all subsequent commands will be executed.
- COPY package.json .: This copies the package.json file from the host machine to the /app directory within the container. This file contains the project's dependencies and configuration.
- RUN npm install: Executes the npm install command within the container to install all the dependencies listed in the package.json file.
- EXPOSE 5173: Informs Docker that the container will listen on port 5173. This port is often used by development servers like Create React App.
- COPY . .: Copies all the remaining files and directories from the host machine to the /app directory within the container. This includes the source code for your React application.
- CMD ["npm", "run", "dev"]: Specifies the default command to be executed when the container starts. In this case, it runs the dev script defined in the package.json file. This script typically starts the development server for your React app.
Building and Running the Image: To build the Docker image, you would use the following command:
Bash
docker build -t mern-frontend .
This command will create an image named mern-frontend based on the Dockerfile in the current directory. NB: Please ensure your docker desktop is running before you build your docker image.
As shown below, you can see that our front end image has been built successfully
Before we run, we need to create a network so that our containers can communicate among themselves seamlessly. To create our docker network, we would use:
Bash
docker network create mern
Next, we will have to run our container, To run our container, we would use:
Bash
docker run --name=frontend --network=mern -d -p 5173:5173 mern-frontend
This command will start a container based on the mern-frontend image, mapping port 5173 of the container to port 5173 of your host machine. This allows you to access your React app in the browser at http://localhost:5173.
- Backend (Node.js & Express)
It is best practice to always start a database container first, to avoid error.
Database (MongoDB)
- For MongoDB, you don’t need a Dockerfile, as MongoDB has an official image available on Docker Hub.
To run our database container, we would use:
Bash
docker run --name=mongodb --network=mern -d -p 27017:27017 -v ~/opt/data:data/db mongo:latest
-
Navigate to the
backend
folder and create aDockerfile
:`
dockerfile
# Dockerfile for backend
FROM node:18.9.1
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 5050
CMD ["npm", "start"]
- Explanation: This sets up the backend service to listen on port 5000 and use
npm start
to initiate. Dockerfile Breakdown for the Backend This Dockerfile is designed to build a container image for a Node.js backend application. Let's break down each line:- FROM node:18.9.1: This line specifies the base image for the container. node:18.9.1 indicates that we're using the official Node.js 18.9.1 image as the starting point.
- WORKDIR /app: Sets the working directory within the container to /app. This is where all subsequent commands will be executed.
- COPY package.json .: Copies the package.json file from the host machine to the /app directory within the container. This file contains the project's dependencies and configuration.
- RUN npm install: Executes the npm install command within the container to install all the dependencies listed in the package.json file.
- COPY . .: Copies all the remaining files and directories from the host machine to the /app directory within the container. This includes the source code for your Node.js backend application.
- EXPOSE 5050: Informs Docker that the container will listen on port 5050. This is the port that your Node.js server will be listening on.
- CMD ["npm", "start"]: Specifies the default command to be executed when the container starts. In this case, it runs the start script defined in the package.json file. This script typically starts your Node.js server. Building and Running the Image: To build the Docker image, you would use the following command: Bash docker build mern-backend .
This command will create an image named mern-backend based on the Dockerfile in the current directory.
To run the container, you would use:
Bash
docker run --name=backend --network=mern -d -p 5050:5050 mern-backend
This command will start a container based on the mern-backend image, mapping port 5050 of the container to port 5050 of your host machine. This allows you to access your Node.js backend application at http://localhost:5050.
Haven built our containers and deployed our MERN stack locally, which really isn’t best practice, I’ll like to show how you can run all the commands in one single file and with just one command.:
Create a docker-compose.yml
File
In your project root directory, create a docker-compose.yml
to orchestrate all services together.
`yaml
services:
frontend:
build: ./mern/frontend
ports:
- "5173:5173"
networks:
- mern_network
environment:
REACT_APP_API_URL: http://backend:5050
backend:
build: ./mern/backend
ports:
- "5050:5050"
networks:
- mern_network
environment:
MONGO_URI: mongodb://mongo:27017/mydatabase
depends_on:
- mongodb
mongodb:
image: mongo:latest
ports:
- "27017:27017"
networks:
- mern_network
volumes:
- mongo-data:/data/db
networks:
mern_network:
driver: bridge
volumes:
mongo-data:
driver: local # Persist MongoDB data locally
Run Docker Compose
- In the root project directory, run:
`bash docker-compose up -d `
This setup should provide a fully functional MERN application running in Docker!
Let me know if you run into any issues or if you'd like more detail on specific parts of this process.
- Access your services:
- Frontend:
http://localhost:3000
- Backend:
http://localhost:5000
- MongoDB is accessible to the backend container at
database:27017
.
- Frontend:
Verify the Connection Between Services
Confirm that:
- The frontend can send requests to the backend.
- The backend connects to MongoDB using the
MONGO_URI
.
Manage and Stop Containers
- To stop containers, press
Ctrl + C
. - To stop and remove containers, networks, and volumes created by
docker-compose up
, run:`bash docker-compose down `
Step 3: Cleaning Up Your Docker Environment (Optional)
When you're done working with the application, you can clean up your docker environments, using any of these commands:
`bash
docker image prune -f
`
This command removes all unused images.
`bash
docker container prune -f
`
This command removes all unused containers.
`bash
docker network prune -f
`
This command removes all unused network.
`bash
docker compose down –volumes
`
This command removes the associated volumes, freeing up disk space.
`bash
docker system prune -f
`
This command removes all unused resources at once.
Note: The -f flag forces the removal without prompting for confirmation. Use it with caution, as it will remove resources without confirmation.
By following these steps and using the appropriate commands, you can effectively clean up your Docker environment and keep it organized.
Additional Considerations
- Environment Variables: In production, you should use Docker secrets or environment variable files (
.env
) to handle sensitive data like database credentials. - Scaling: You can scale services (like the backend) by modifying your
docker-compose.yml
:`yaml backend: ... deploy: replicas: 3 `
- Multi-stage Builds: For more complex setups, consider using multi-stage builds in your Dockerfiles to optimize the size of your Docker images.
Conclusion
By following this guide, you now have a fully Dockerized three-tier web application, with the frontend, backend, and database running in isolated containers, all connected via Docker Compose. You can easily scale, share, and deploy your application using this setup.