Back to all posts

Containerizing a MERN Stack App with Docker: A Production Guide

Technology3 min read

Containerizing a MERN Stack App with Docker: A Production Guide

Deploying a MERN (MongoDB, Express, React, Node.js) stack application can be a complex endeavor. Dealing with different environments, ensuring dependency versions match, and configuring networks between your frontend, backend, and database often lead to the dreaded "it works on my machine" syndrome. Docker is the definitive solution to this problem.

Why Docker for MERN?

Docker allows you to package your application and all its dependencies into standardized units called containers. For a MERN stack, this means you can have a separate container for your React frontend, your Express/Node.js backend, and your MongoDB database, all running in isolated environments but communicating flawlessly through a dedicated Docker network.

Step 1: The Backend (Node.js/Express) Dockerfile

First, we need to containerize the backend API. Create a Dockerfile in your backend directory:

dockerfile
FROM node:18-alpine WORKDIR /usr/src/app COPY package*.json ./ RUN npm ci --only=production COPY . . EXPOSE 5000 CMD ["npm", "start"]

Notice that we use node:18-alpine. Alpine images are significantly smaller, which speeds up build times and reduces the attack surface area for security vulnerabilities. We also use npm ci instead of npm install for reliable, reproducible builds based strictly on the package-lock.json.

Step 2: The Frontend (React/Next.js) Dockerfile

For the frontend, the approach differs slightly depending on whether you are using vanilla React (Create React App/Vite) or Next.js. For a Next.js application, your Dockerfile might look like this:

dockerfile
FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM node:18-alpine AS runner WORKDIR /app ENV NODE_ENV production COPY --from=builder /app/next.config.js ./ COPY --from=builder /app/public ./public COPY --from=builder /app/.next ./.next COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/package.json ./package.json EXPOSE 3000 CMD ["npm", "start"]

This utilizes a Multi-Stage Build. The first stage (builder) installs all dependencies and compiles the Next.js application. The second stage (runner) only copies over the compiled assets and production dependencies, resulting in a lean final image.

Step 3: Orchestrating with Docker Compose

Now that we have Dockerfiles for our services, we need a way to run them together along with MongoDB. We use docker-compose.yml for this:

yaml
version: '3.8' services: backend: build: ./backend ports: - "5000:5000" environment: - MONGODB_URI=mongodb://mongo:27017/myapp depends_on: - mongo frontend: build: ./frontend ports: - "3000:3000" environment: - NEXT_PUBLIC_API_URL=http://localhost:5000 mongo: image: mongo:6.0 ports: - "27017:27017" volumes: - mongo_data:/data/db volumes: mongo_data:

Key Takeaways for Production

When moving this to a production environment (like AWS ECS or DigitalOcean App Platform), remember these essential rules:

  • Never hardcode secrets: Always pass environment variables (like Database URIs and API keys) at runtime using the environment variables in your orchestrator, never bake them into the Docker image itself.

  • Persist your database data: In the compose file above, we used a Docker Volume (mongo_data). If you don't use volumes, your database will reset every time the MongoDB container restarts!

  • Use a reverse proxy: In a real production environment, you should place Nginx or Traefik in front of your containers to handle SSL termination (HTTPS) and routing.

Containerizing your applications ensures that your deployments are predictable, scalable, and resilient. At VexioApp, Docker is a foundational tool in our CI/CD pipelines, allowing us to ship code faster and with zero downtime.

Read Next

Next.js 14 and Beyond: The Power of React Server Components

Read Article

Scaling Node.js: Why You Should Be Using Redis

Read Article

VexioApp

We build scalable architectures, stunning user interfaces, and robust backend systems for modern businesses.

Work with us →