A complete MERN stack application containerised using Docker and Docker Compose.
This project demonstrates how to run a modern full-stack JavaScript application with:
- โ๏ธ React + Vite
- ๐ข Node.js
- ๐ Express.js
- ๐ MongoDB
- ๐ณ Docker
- ๐ Docker Compose
- ๐ Nodemon for backend development
- ๐พ Persistent MongoDB storage
The entire application can be started with a single Docker Compose command.
- Architecture
- Technology Stack
- Project Structure
- Prerequisites
- Getting Started
- Backend Setup
- Frontend Setup
- Docker Configuration
- Docker Compose
- Running the Application
- Accessing the Application
- Environment Variables
- Development Workflow
- Useful Docker Commands
- MongoDB Persistence
- Troubleshooting
- Production Considerations
- Future Improvements
- License
The application consists of three main services:
โโโโโโโโโโโโโโโโโโโโโโโโ
โ Browser โ
โโโโโโโโโโโโฌโโโโโโโโโโโโ
โ
โ :3000
โผ
โโโโโโโโโโโโโโโโโโโโโโโโ
โ React + Vite โ
โ Frontend โ
โโโโโโโโโโโโฌโโโโโโโโโโโโ
โ
โ API Requests
โผ
โโโโโโโโโโโโโโโโโโโโโโโโ
โ Node.js + Express โ
โ Backend โ
โโโโโโโโโโโโฌโโโโโโโโโโโโ
โ
โ MongoDB
โผ
โโโโโโโโโโโโโโโโโโโโโโโโ
โ MongoDB โ
โ Database โ
โโโโโโโโโโโโโโโโโโโโโโโโ
Docker Compose creates an internal network where services can communicate using their service names.
For example, the backend connects to MongoDB using:
mongodb://mongo:27017/mern_db
The hostname mongo refers to the MongoDB service defined in docker-compose.yml.
| Technology | Purpose |
|---|---|
| React | Frontend UI |
| Vite | Frontend build tool and development server |
| Node.js | JavaScript runtime |
| Express.js | Backend API framework |
| MongoDB | NoSQL database |
| Mongoose | MongoDB ODM |
| Nodemon | Backend development auto-reload |
| Docker | Application containerization |
| Docker Compose | Multi-container orchestration |
mern-app/
โ
โโโ backend/
โ โโโ Dockerfile
โ โโโ .dockerignore
โ โโโ package.json
โ โโโ package-lock.json
โ โโโ server.js
โ
โโโ frontend/
โ โโโ Dockerfile
โ โโโ .dockerignore
โ โโโ package.json
โ โโโ package-lock.json
โ โโโ vite.config.js
โ โโโ index.html
โ โ
โ โโโ public/
โ โ
โ โโโ src/
โ โโโ App.jsx
โ โโโ main.jsx
โ โโโ ...
โ
โโโ docker-compose.yml
โโโ .gitignore
โโโ README.md
Before starting, make sure you have the following installed:
Download and install Node.js from:
Check the installation:
node --version
npm --versionInstall Docker Desktop:
Verify:
docker --versionModern Docker Desktop includes Docker Compose.
Check:
docker compose versionNote: Modern Docker uses
docker composeinstead of the olderdocker-composecommand.
Clone the repository:
git clone <your-repository-url>Navigate into the project:
cd mern-appBuild and start all services:
docker compose up --buildDocker will:
- Build the backend image
- Build the frontend image
- Pull the MongoDB image
- Create the Docker network
- Create the MongoDB volume
- Start MongoDB
- Start the backend
- Start the frontend
Navigate into the backend directory:
cd backendInitialise the Node.js project:
npm init -yInstall dependencies:
npm install express mongooseInstall Nodemon:
npm install --save-dev nodemonMake sure the scripts contain:
{
"scripts": {
"start": "nodemon server.js"
}
}const express = require("express");
const mongoose = require("mongoose");
const app = express();
app.use(express.json());
const PORT = 5000;
const MONGO_URI = "mongodb://mongo:27017/mern_db";
mongoose
.connect(MONGO_URI)
.then(() => {
console.log("โ
MongoDB Connected");
})
.catch((error) => {
console.error("โ MongoDB Connection Error:", error);
});
app.get("/", (req, res) => {
res.json({
message: "๐ MERN Backend running in Docker!",
});
});
app.listen(PORT, "0.0.0.0", () => {
console.log(`โ
Server running on port ${PORT}`);
});Create:
backend/Dockerfile
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 5000
CMD ["npm", "start"]Create:
backend/.dockerignore
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
.env
Create the Vite React application:
cd frontendIf the directory is empty:
npm create vite@latest . -- --template reactInstall dependencies:
npm installCreate/update:
frontend/vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
host: "0.0.0.0",
port: 3000,
},
});The important part for Docker is:
host: "0.0.0.0"This allows the Vite development server to accept connections from outside the container.
Create:
frontend/Dockerfile
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]Create:
frontend/.dockerignore
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
.env
dist
Create the following file in the project root:
docker-compose.yml
services:
# -------------------------
# MongoDB
# -------------------------
mongo:
image: mongo:6
container_name: mongo
restart: unless-stopped
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
# -------------------------
# Backend
# -------------------------
backend:
build: ./backend
container_name: backend
restart: unless-stopped
ports:
- "5000:5000"
volumes:
- ./backend:/app
- /app/node_modules
depends_on:
- mongo
# -------------------------
# Frontend
# -------------------------
frontend:
build: ./frontend
container_name: frontend
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- ./frontend:/app
- /app/node_modules
depends_on:
- backend
# -------------------------
# Persistent Volumes
# -------------------------
volumes:
mongo-data:One of the most important concepts in Docker Compose is service-to-service communication.
The services are:
mongo
backend
frontend
Inside the Docker network, the backend can access MongoDB using:
mongodb://mongo:27017/mern_db
Do not use:
mongodb://localhost:27017/mern_db
for the backend's MongoDB connection when both are running in separate containers.
Why?
Inside the backend container:
localhost
means:
backend container
not the MongoDB container.
Docker Compose provides DNS resolution so:
mongo
automatically points to the MongoDB container.
docker compose up --builddocker compose up -ddocker compose up --builddocker compose stopdocker compose downdocker compose down -vAfter starting the application:
http://localhost:3000
http://localhost:5000
From the host:
mongodb://localhost:27017
From the backend container:
mongodb://mongo:27017/mern_db
docker psdocker ps -adocker imagesdocker network lsdocker volume lsBackend:
docker logs backendFrontend:
docker logs frontendMongoDB:
docker logs mongodocker logs -f backendBackend:
docker exec -it backend shMongoDB:
docker exec -it mongo mongoshdocker stop <container_id>docker rm <container_id>docker rmi <image_id>docker system pruneTo remove unused volumes as well:
docker system prune --volumesMongoDB uses a named Docker volume:
volumes:
- mongo-data:/data/dbThe volume is defined at the bottom of docker-compose.yml:
volumes:
mongo-data:This means MongoDB data survives container recreation.
For example:
docker compose downdoes not delete the MongoDB volume.
Starting the application again:
docker compose up -dwill reuse the existing MongoDB data.
However:
docker compose down -vremoves the volume and therefore deletes the stored MongoDB data.
This project is configured for development.
The backend mounts:
volumes:
- ./backend:/app
- /app/node_modulesThe frontend mounts:
volumes:
- ./frontend:/app
- /app/node_modulesThis allows files on your host machine to be synchronized with the containers.
Nodemon automatically restarts the server when files change.
Vite automatically updates the application when React files change.
Therefore, you can edit:
frontend/src/App.jsx
or:
backend/server.js
without manually rebuilding the containers for every code change.
For a real application, MongoDB credentials and configuration should not be hardcoded.
Instead of:
const MONGO_URI = "mongodb://mongo:27017/mern_db";use:
const MONGO_URI = process.env.MONGO_URI;Then create:
backend/.env
Example:
PORT=5000
MONGO_URI=mongodb://mongo:27017/mern_dbFor production, use a secured MongoDB connection string.
Never commit sensitive .env files to Git.
Add this to .gitignore:
.env
.env.*
!.env.example
After starting the containers, open:
http://localhost:5000
You should receive:
{
"message": "๐ MERN Backend running in Docker!"
}You can also test the API using tools such as Postman or curl.
Example:
curl http://localhost:5000If you see an error such as:
port is already allocated
check which process is using the port.
Windows:
netstat -ano | findstr :3000or:
netstat -ano | findstr :5000You can either stop the process or change the port mapping.
For example:
ports:
- "3001:3000"The application would then be accessible at:
http://localhost:3001
Make sure Vite is configured with:
server: {
host: "0.0.0.0",
port: 3000
}Without host: "0.0.0.0", the development server may only listen inside the container.
Check MongoDB:
docker psThen check its logs:
docker logs mongoCheck backend logs:
docker logs backendMake sure the connection string uses:
mongodb://mongo:27017/mern_db
and not:
mongodb://localhost:27017/mern_db
Restart the services:
docker compose restartIf necessary, rebuild:
docker compose down
docker compose up --buildIf dependencies become corrupted, remove the containers and rebuild:
docker compose down
docker compose build --no-cache
docker compose upCheck whether the volume exists:
docker volume lsAvoid using:
docker compose down -vunless you intentionally want to delete MongoDB data.
This repository is primarily configured for development.
The frontend currently runs:
npm run devand the backend uses:
nodemonThis is convenient during development but is not the ideal production setup.
For production, a more appropriate architecture would be:
Internet
โ
โผ
โโโโโโโโโโโโโโโโโ
โ Nginx โ
โ Reverse Proxy โ
โโโโโโโโโฌโโโโโโโโ
โ
โโโโโโโโโโโดโโโโโโโโโโ
โ โ
โผ โผ
React Static Express API
Production Build Backend
โ โ
โ โผ
โ MongoDB
โ
โผ
Browser
The React application should generally be built:
npm run buildand served as static files rather than running the Vite development server.
The backend should also run without Nodemon.
For example:
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
}Before deploying this application to production:
- Do not expose MongoDB publicly unless required
- Use environment variables for secrets
- Use strong MongoDB credentials
- Use HTTPS
- Use a reverse proxy such as Nginx
- Do not use Nodemon in production
- Build the React application for production
- Restrict exposed Docker ports
- Configure CORS properly
- Add API authentication where required
- Add request validation
- Add rate limiting
- Configure database backups
- Keep Docker images updated
- Do not commit
.envfiles
Remove stopped containers:
docker container pruneRemove unused images:
docker image pruneRemove unused networks:
docker network pruneRemove unused volumes:
docker volume pruneRemove unused Docker resources:
docker system pruneFor a more aggressive cleanup:
docker system prune -aAfter running:
docker compose up -dyou should have three containers:
| Container | Technology | Port |
|---|---|---|
frontend |
React + Vite | 3000 |
backend |
Node.js + Express | 5000 |
mongo |
MongoDB | 27017 |
The communication flow is:
Browser
โ
โ localhost:3000
โผ
Frontend
โ
โ API
โผ
Backend
โ
โ mongodb://mongo:27017
โผ
MongoDB
This project is useful for learning several important Docker concepts.
Each application component runs in its own container.
Frontend โ Container
Backend โ Container
MongoDB โ Container
Dockerfiles define how application images are built.
Dockerfile โ Docker Image โ Container
Volumes provide persistent storage.
MongoDB Container
โ
โผ
mongo-data volume
Docker Compose automatically creates a network allowing services to communicate.
frontend โโโโโโ
โ
backend โโโโโโโผโโ Docker Network
โ
mongo โโโโโโโโโ
Docker Compose manages multiple services together.
Instead of running:
docker run ...
docker run ...
docker run ...you can run:
docker compose upThis project can be extended with:
- ๐ JWT authentication
- ๐ค User registration and login
- ๐ MongoDB models
- ๐ REST API
- ๐ก WebSockets
- ๐งช Automated testing
- ๐ฆ Multi-stage Docker builds
- ๐ Nginx reverse proxy
- ๐ HTTPS with SSL
- ๐ CI/CD with GitHub Actions
- โ๏ธ Cloud deployment
- ๐ Application monitoring
- ๐ API documentation with Swagger
- ๐ Secret management
- ๐๏ธ MongoDB backup strategy
- โก Redis caching
If everything is already configured, simply run:
git clone <your-repository-url>
cd mern-app
docker compose up --buildThen open:
Frontend โ http://localhost:3000
Backend โ http://localhost:5000
To stop:
docker compose downFor official documentation:
- Docker Documentation
- Docker Compose Documentation
- Node.js Documentation
- Express.js Documentation
- React Documentation
- Vite Documentation
- MongoDB Documentation
- Mongoose Documentation
Contributions are welcome!
- Fork the repository
- Create a feature branch
git checkout -b feature/my-feature- Make your changes
- Commit your changes
git commit -m "Add my feature"- Push the branch
git push origin feature/my-feature- Open a Pull Request
This project is available under the MIT License.
You are free to use, modify, and distribute the project according to the terms of the license.
If this project helped you understand how to Dockerise a MERN application, consider giving the repository a โญ on GitHub.
Happy Coding! ๐๐ณ