Containerizing a MERN application helps streamline development, ensure consistent environments, and enable smooth production deployments. In this guide, weβll walk through Dockerizing both the Node.js backend and React/Next.js frontend, orchestrating services with Docker Compose, and finally configuring an Nginx reverse proxy for production.
π§± Why Use Multi-Stage Docker Builds?
Traditional Docker builds often produce large images containing dependencies and files needed only during development. Multi-stage builds solve this by:
- Separating build dependencies from the runtime environment
- Significantly reducing the final image size
- Improving security by excluding tooling from production
π§ Environment Variables Overview
Backend (Node.js)
Environment variables determine how the Express server connects to MongoDB:
const MONGO_USER = process.env.MONGO_USER || "admin";
const MONGO_PASS = process.env.MONGO_PASS || "admin";
const MONGO_HOST = process.env.MONGO_HOST || "localhost";
const MONGO_PORT = process.env.MONGO_PORT || "27017";
const MONGO_DB = process.env.MONGO_DB || "notesdb";
const PORT = process.env.PORT || 5000;
Frontend (Next.js / React)
These are embedded during the build:
const API_HOST = process.env.NEXT_PUBLIC_BACKEND_HOST || "http://localhost:5000";
const BG_COLOR = process.env.NEXT_PUBLIC_BG_COLOR || "#f5f5f5";
Note: Variables prefixed with
NEXT_PUBLIC_are compiled into the build.
Changing them later requires rebuilding the frontend unless runtime config is implemented.
ποΈ Dockerizing the Node.js Backend
# Base image for dependencies
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Final minimal image
FROM node:20-alpine AS final
WORKDIR /app
RUN addgroup -S app && adduser -S app -G app
COPY --from=build /app/node_modules ./node_modules
COPY . .
USER app
EXPOSE 5000
CMD ["node", "server.js"]
π¨ Dockerizing the Next.js Frontend
# Build stage
FROM node:18-bookworm-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ARG NEXT_PUBLIC_BG_COLOR="red"
RUN NEXT_PUBLIC_BG_COLOR=${NEXT_PUBLIC_BG_COLOR} npm run build
# Production stage
FROM node:18-bookworm-slim AS final
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
CMD ["npm", "run", "start"]
This Dockerfile builds a Next.js app in a multi-stage process, optimizing the final image by copying only necessary files. It sets a customizable background color via an argument during build.
For production, define backend host at build time:
ARG NEXT_PUBLIC_BACKEND_HOST="http://your-server-ip"
RUN NEXT_PUBLIC_BACKEND_HOST=${NEXT_PUBLIC_BACKEND_HOST} npm run build
π§© Orchestrating Everything with Docker Compose
services:
mongodb:
image: mongo:7
container_name: mongodb
restart: unless-stopped
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: admin
MONGO_INITDB_DATABASE: notesdb
volumes:
- mongo-data:/data/db
ports:
- "27017:27017"
networks:
- mern-net
nodejs:
build:
context: ./backend
image: smoothcoode/mernbackend #change this to you username/imagename
container_name: nodeapp
restart: unless-stopped
ports:
- "5000:5000"
environment:
- MONGO_HOST=mongodb
depends_on:
- mongodb
networks:
- mern-net
frontend:
build:
context: ./frontend
image: smoothcoode/mernfrontend #change this to you username/imagename
ports:
- "3000:3000"
restart: unless-stopped
depends_on:
- nodejs
networks:
- mern-net
volumes:
mongo-data:
networks:
mern-net:
driver: bridge
π Setting Up Nginx Reverse Proxy (Production)
server {
listen 80;
server_name _;
# Frontend
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_redirect http://localhost:3000/ http://$host/;
}
# Backend API
location /api/ {
proxy_pass http://localhost:5000;
proxy_set_header Host $host;
}
}
β Conclusion
By using multi-stage Docker builds and Docker Compose, we create a lightweight, secure, and scalable deployment workflow for MERN applications. Adding Nginx allows us to cleanly route frontend web traffic and API requests in production.
You now have:
- Dockerized Node.js and Next.js applications
- A persistent MongoDB container
- A unified environment via Docker Compose
- A production-ready reverse proxy setup



Top comments (0)