---
# === IDENTITY ===
id: software/devops/docker-mern-stack/2026
canonical_question: "Docker Compose reference: MERN/MEAN Stack"
aliases:
  - "Docker Compose MERN stack setup"
  - "Docker Compose MongoDB Express React Node"
  - "Docker MEAN stack deployment"
  - "containerize MERN app Docker Compose"
  - "Docker Compose MongoDB Node.js React Nginx"
  - "MERN Docker production setup"
  - "Docker Compose Express MongoDB Angular"
  - "docker-compose.yml MERN stack example"
entity_type: software_reference
domain: software > devops > Docker MERN Stack
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-27
confidence: 0.93
version: 1.0
first_published: 2026-02-27

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Docker Compose V2 replaced V1 (docker-compose to docker compose CLI, 2023); MongoDB 6.0 replaced mongo shell with mongosh (2022); Node 22 LTS released (2024)"
  next_review: 2026-08-26
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "NEVER store MongoDB credentials in docker-compose.yml -- use .env files or Docker secrets"
  - "ALWAYS use named volumes for MongoDB data -- bind mounts risk permission issues on Linux"
  - "NEVER use node:latest -- pin to a specific LTS version (e.g., node:22-slim)"
  - "ALWAYS run Node.js containers as non-root user (USER node) in production"
  - "NEVER expose MongoDB port 27017 to the host in production -- only internal Docker network access"
  - "ALWAYS use multi-stage builds for React/Angular frontend to keep production images under 50MB"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need Kubernetes deployment, not Docker Compose"
    use_instead: "software/devops/kubernetes-mern-deployment/2026"
  - condition: "Need Docker Compose for Django/Python backend stack"
    use_instead: "software/devops/docker-django-stack/2026"
  - condition: "Need MongoDB standalone deployment without Node.js"
    use_instead: "software/databases/mongodb-docker-setup/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: "frontend_framework"
    question: "Are you using React (MERN) or Angular (MEAN) for the frontend?"
    type: choice
    options: ["React (MERN)", "Angular (MEAN)", "Vue (MEVN)", "Next.js"]
  - key: "environment"
    question: "Is this for development (hot reload) or production (optimized builds)?"
    type: choice
    options: ["Development", "Production", "Both"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/devops/docker-mern-stack/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-27)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/devops/docker-compose-networking/2026"
      label: "Docker Compose Networking Guide"
    - id: "software/devops/nginx-reverse-proxy/2026"
      label: "Nginx Reverse Proxy Configuration"
  solves: []
  alternative_to:
    - id: "software/devops/kubernetes-mern-deployment/2026"
      label: "Kubernetes MERN Deployment"
  often_confused_with:
    - id: "software/devops/docker-django-stack/2026"
      label: "Docker Django Stack"

# === SOURCES (7 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "Containerize a Node.js application"
    author: Docker
    url: https://docs.docker.com/guides/nodejs/containerize/
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src2
    title: "mongo - Official Image"
    author: Docker Hub
    url: https://hub.docker.com/_/mongo
    type: official_docs
    published: 2025-01-15
    reliability: authoritative
  - id: src3
    title: "10 best practices to containerize Node.js web applications with Docker"
    author: Snyk
    url: https://snyk.io/blog/10-best-practices-to-containerize-nodejs-web-applications-with-docker/
    type: technical_blog
    published: 2024-09-15
    reliability: high
  - id: src4
    title: "Multi-stage builds"
    author: Docker
    url: https://docs.docker.com/build/building/multi-stage/
    type: official_docs
    published: 2025-03-01
    reliability: authoritative
  - id: src5
    title: "How to Use the NGINX Docker Official Image"
    author: Docker
    url: https://www.docker.com/blog/how-to-use-the-official-nginx-docker-image/
    type: official_docs
    published: 2024-08-20
    reliability: high
  - id: src6
    title: "Docker Compose Health Checks: An Easy-to-follow Guide"
    author: Last9
    url: https://last9.io/blog/docker-compose-health-checks/
    type: technical_blog
    published: 2025-04-10
    reliability: moderate_high
  - id: src7
    title: "Dockerizing MERN Stack Apps for Production in 2025"
    author: Kevin (Medium)
    url: https://medium.com/@mernstackdevbykevin/dockerizing-your-mern-stack-app-for-production-in-2025-f376240ca86c
    type: technical_blog
    published: 2025-01-20
    reliability: moderate_high
---

# Docker Compose MERN/MEAN Stack: Complete Reference

## TL;DR

- **Bottom line**: A production-ready MERN/MEAN stack uses four Docker Compose services -- MongoDB with named volumes, Express/Node API, a multi-stage-built React/Angular frontend served by Nginx, and Nginx as reverse proxy -- all connected via an internal Docker network.
- **Key tool/command**: `docker compose up --build -d` to build and start all services in detached mode.
- **Watch out for**: Running MongoDB without authentication or exposing port 27017 to the host in production -- always set `MONGO_INITDB_ROOT_USERNAME` and `MONGO_INITDB_ROOT_PASSWORD`.
- **Works with**: Docker Compose V2 (included in Docker Desktop 4.x+), Docker Engine 24+, MongoDB 7.x/8.x, Node.js 20/22 LTS, React 18/19, Angular 17/18/19.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- NEVER store MongoDB credentials in docker-compose.yml -- use .env files or Docker secrets
- ALWAYS use named volumes for MongoDB data -- bind mounts risk permission issues on Linux
- NEVER use node:latest -- pin to a specific LTS version (e.g., node:22-slim)
- ALWAYS run Node.js containers as non-root user (USER node) in production
- NEVER expose MongoDB port 27017 to the host in production -- only internal Docker network access
- ALWAYS use multi-stage builds for React/Angular frontend to keep production images under 50MB

## Quick Reference

| Service | Image | Ports | Volumes | Key Env |
|---|---|---|---|---|
| MongoDB | `mongo:8.0` | None (internal only) | `mongo-data:/data/db` | `MONGO_INITDB_ROOT_USERNAME`, `MONGO_INITDB_ROOT_PASSWORD` |
| Express API | `node:22-slim` (custom) | `3001:3001` | `./server:/app` (dev only) | `NODE_ENV`, `MONGODB_URI`, `PORT` |
| React Frontend | `node:22-slim` -> `nginx:1.27-alpine` | None (proxied) | `./client:/app` (dev only) | `REACT_APP_API_URL` (build-time) |
| Angular Frontend | `node:22-slim` -> `nginx:1.27-alpine` | None (proxied) | `./client:/app` (dev only) | `API_URL` (build-time) |
| Nginx Proxy | `nginx:1.27-alpine` | `80:80`, `443:443` | `./nginx/nginx.conf:/etc/nginx/nginx.conf:ro` | None |

**Connection Strings (internal Docker network):**

| From | To | Connection String |
|---|---|---|
| Express API | MongoDB | `mongodb://root:password@mongo:27017/appdb?authSource=admin` |
| Nginx | Express API | `http://api:3001` (proxy_pass) |
| Nginx | React/Angular | Serves static files from build stage |
| Browser | Nginx | `http://localhost` or `https://localhost` |

**Docker Compose V2 Commands:**

| Command | Purpose |
|---|---|
| `docker compose up --build -d` | Build images and start all services |
| `docker compose down -v` | Stop services and remove volumes (dev reset) |
| `docker compose logs -f api` | Follow logs for the API service |
| `docker compose exec mongo mongosh -u root -p` | Open MongoDB shell |
| `docker compose ps` | List running services and health status |
| `docker compose build --no-cache api` | Rebuild specific service without cache |

## Decision Tree

```
START: What type of deployment do you need?
├── Development (hot reload)?
│   ├── YES → Use bind mounts for source code + nodemon for API + Vite/ng serve for frontend
│   └── NO ↓
├── Production (optimized)?
│   ├── YES → Multi-stage builds + Nginx reverse proxy + no bind mounts + health checks
│   └── NO ↓
├── MERN (React)?
│   ├── YES → Multi-stage Dockerfile: node:22-slim (build) → nginx:1.27-alpine (serve)
│   └── NO ↓
├── MEAN (Angular)?
│   ├── YES → Multi-stage Dockerfile: node:22-slim (build with ng build) → nginx:1.27-alpine (serve)
│   └── NO ↓
├── Need MongoDB replica set (transactions)?
│   ├── YES → Add --replSet rs0 + keyFile + rs.initiate() init script
│   └── NO ↓
└── DEFAULT → Standard 3-service compose: mongo + api + client with Nginx
```

## Step-by-Step Guide

### 1. Create project directory structure

Set up the standard MERN project layout that Docker Compose expects. [src1]

```bash
mkdir -p mern-docker/{client,server,nginx,mongo-init}
cd mern-docker
touch docker-compose.yml .env .dockerignore
```

**Verify**: `ls -la` → should show client/, server/, nginx/, mongo-init/, docker-compose.yml, .env

### 2. Configure environment variables

Create a `.env` file at the project root. Docker Compose automatically loads this file. [src3]

```bash
# .env -- NEVER commit this file to version control
NODE_ENV=production
MONGO_ROOT_USER=root
MONGO_ROOT_PASSWORD=changeme_strong_password_here
MONGO_DB=appdb
MONGO_PORT=27017
API_PORT=3001
```

**Verify**: `docker compose config` → should show interpolated environment variables

### 3. Write the Docker Compose file

Create the multi-service configuration with health checks and dependency ordering. [src6]

> Full script: [docker-compose-production.yml](scripts/docker-compose-production.yml) (68 lines)

```yaml
# docker-compose.yml -- Production MERN stack
services:
  mongo:
    image: mongo:8.0
    restart: unless-stopped
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${MONGO_ROOT_USER}
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD}
      MONGO_INITDB_DATABASE: ${MONGO_DB}
    volumes:
      - mongo-data:/data/db
      - ./mongo-init:/docker-entrypoint-initdb.d:ro
# ... (see full script)
```

**Verify**: `docker compose config --services` → should list: mongo, api, client, nginx

### 4. Create the Node.js API Dockerfile

Build a production-ready Express API image with proper security. [src3]

> Full script: [server-dockerfile](scripts/server-dockerfile) (32 lines)

```dockerfile
# server/Dockerfile
FROM node:22-slim AS base
WORKDIR /app
ENV NODE_ENV=production
# Install dumb-init for proper signal handling
RUN apt-get update && apt-get install -y --no-install-recommends dumb-init \
    && rm -rf /var/lib/apt/lists/*
# ... (see full script)
```

**Verify**: `docker build -t mern-api ./server` → should complete without errors

### 5. Create the React frontend multi-stage Dockerfile

Build React with Node, then serve with Nginx for minimal image size. [src4]

> Full script: [client-dockerfile-react](scripts/client-dockerfile-react) (31 lines)

```dockerfile
# client/Dockerfile (React / Vite)
FROM node:22-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# ... (see full script)
```

**Verify**: `docker build -t mern-client ./client` → final image should be < 50MB (`docker images mern-client`)

### 6. Configure Nginx as reverse proxy

Create the Nginx configuration that routes API requests and serves static files. [src5]

> Full script: [nginx-conf](scripts/nginx-conf) (38 lines)

```nginx
# nginx/nginx.conf
worker_processes auto;
events { worker_connections 1024; }
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    # ... (see full script)
}
```

**Verify**: `docker compose up nginx` → `curl -I http://localhost` should return 200

### 7. Create MongoDB initialization script

Set up application database and user with least-privilege access. [src2]

```javascript
// mongo-init/01-create-app-user.js
// Runs automatically on first container start via /docker-entrypoint-initdb.d/
// Connects to MONGO_INITDB_DATABASE with root credentials

db.createUser({
  user: 'appuser',
  pwd: 'app_password_change_me',
  roles: [
    { role: 'readWrite', db: 'appdb' },
    { role: 'dbAdmin', db: 'appdb' }
  ]
});

db.createCollection('init_check');
print('MongoDB init complete: appuser created with readWrite on appdb');
```

**Verify**: `docker compose exec mongo mongosh -u root -p --eval "db.getUsers()"` → should list appuser

### 8. Start the full stack

Launch all services and verify connectivity. [src7]

```bash
# Build and start all services
docker compose up --build -d

# Check all services are healthy
docker compose ps

# Test API connectivity through Nginx
curl http://localhost/api/health

# Check MongoDB connectivity from API
docker compose logs api | grep "Connected to MongoDB"
```

**Verify**: `docker compose ps` → all services should show "healthy" or "running"

## Code Examples

### Complete docker-compose.yml (Production)

> Full script: [docker-compose-production.yml](scripts/docker-compose-production.yml) (68 lines)

```yaml
# docker-compose.yml -- Production MERN/MEAN stack
services:
  mongo:
    image: mongo:8.0
    restart: unless-stopped
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${MONGO_ROOT_USER}
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD}
      MONGO_INITDB_DATABASE: ${MONGO_DB}
    volumes:
      - mongo-data:/data/db
      - ./mongo-init:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
# ... (see full script)
```

### Complete docker-compose.yml (Development with Hot Reload)

> Full script: [docker-compose-dev.yml](scripts/docker-compose-dev.yml) (54 lines)

```yaml
# docker-compose.dev.yml -- Development with hot reload
services:
  mongo:
    image: mongo:8.0
    ports:
      - "27017:27017"  # Exposed for local tools (Compass, mongosh)
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${MONGO_ROOT_USER}
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD}
    volumes:
      - mongo-data:/data/db
# ... (see full script)
```

### Node.js/Express API Dockerfile (Production)

> Full script: [server-dockerfile](scripts/server-dockerfile) (32 lines)

```dockerfile
FROM node:22-slim AS base
WORKDIR /app
ENV NODE_ENV=production
RUN apt-get update && apt-get install -y --no-install-recommends dumb-init \
    && rm -rf /var/lib/apt/lists/*
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
# ... (see full script)
```

### React Multi-Stage Dockerfile (Production)

> Full script: [client-dockerfile-react](scripts/client-dockerfile-react) (31 lines)

```dockerfile
FROM node:22-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM node:22-slim AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# ... (see full script)
```

## Anti-Patterns

### Wrong: Storing credentials in docker-compose.yml

```yaml
# BAD -- credentials in plain text, committed to version control
services:
  mongo:
    image: mongo:8.0
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: supersecret123
```

### Correct: Using .env file or Docker secrets

```yaml
# GOOD -- credentials loaded from .env file (never committed)
services:
  mongo:
    image: mongo:8.0
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${MONGO_ROOT_USER}
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD}
# .env is in .gitignore
```

### Wrong: Using node:latest and running as root

```dockerfile
# BAD -- unpinned version, running as root, using npm start
FROM node:latest
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "start"]
```

### Correct: Pinned version, non-root user, proper signal handling

```dockerfile
# GOOD -- pinned LTS, dumb-init for signals, non-root user
FROM node:22-slim
WORKDIR /app
ENV NODE_ENV=production
RUN apt-get update && apt-get install -y --no-install-recommends dumb-init \
    && rm -rf /var/lib/apt/lists/*
COPY --chown=node:node package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --chown=node:node . .
USER node
CMD ["dumb-init", "node", "server.js"]
```

### Wrong: Single-stage frontend build (bloated image)

```dockerfile
# BAD -- 1.2GB image includes node_modules, source, build tools
FROM node:22
WORKDIR /app
COPY . .
RUN npm install && npm run build
CMD ["npx", "serve", "-s", "build"]
```

### Correct: Multi-stage build (< 50MB image)

```dockerfile
# GOOD -- only Nginx + static files in final image (~25MB)
FROM node:22-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:1.27-alpine
COPY --from=build /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
```

### Wrong: depends_on without health checks

```yaml
# BAD -- API starts before MongoDB is ready to accept connections
services:
  api:
    depends_on:
      - mongo
```

### Correct: depends_on with condition: service_healthy

```yaml
# GOOD -- API waits for MongoDB health check to pass
services:
  api:
    depends_on:
      mongo:
        condition: service_healthy
  mongo:
    healthcheck:
      test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
```

### Wrong: Exposing MongoDB to host in production

```yaml
# BAD -- MongoDB accessible from outside Docker network
services:
  mongo:
    ports:
      - "27017:27017"
```

### Correct: Internal-only MongoDB access

```yaml
# GOOD -- MongoDB only accessible within Docker network
services:
  mongo:
    expose:
      - "27017"
    # No 'ports' directive -- only other containers can reach it
```

## Common Pitfalls

- **MongoDB data loss on `docker compose down -v`**: The `-v` flag removes named volumes including MongoDB data. Fix: Use `docker compose down` (without -v) in production; use `-v` only for dev resets. [src2]
- **`ECONNREFUSED` on first startup**: API starts before MongoDB is ready. Fix: Add `healthcheck` to MongoDB and `depends_on: { mongo: { condition: service_healthy } }` to the API service. [src6]
- **Node.js ignoring SIGTERM in containers**: When using `CMD ["npm", "start"]`, npm becomes PID 1 and doesn't forward signals. Fix: Use `dumb-init` or call `CMD ["node", "server.js"]` directly. [src3]
- **Frontend can't reach API in production**: React uses `localhost:3001` which doesn't exist inside the Nginx container. Fix: Configure Nginx to proxy `/api` to the API container; frontend makes relative requests to `/api`. [src5]
- **`EACCES: permission denied` on mounted volumes**: Linux host uid/gid doesn't match container user. Fix: Use `COPY --chown=node:node` in Dockerfile and ensure the mounted directory is owned by uid 1000. [src3]
- **MongoDB init script doesn't run on restart**: `/docker-entrypoint-initdb.d/` scripts only run on first initialization (when `/data/db` is empty). Fix: Remove the volume to re-run init: `docker volume rm mern-docker_mongo-data`. [src2]
- **Image bloat from dev dependencies**: `npm install` includes devDependencies, inflating the production image. Fix: Use `npm ci --omit=dev` and set `ENV NODE_ENV=production` before the install step. [src3]
- **Docker build cache invalidation**: Copying source code before installing dependencies invalidates npm cache on every change. Fix: Copy `package.json` and `package-lock.json` first, run `npm ci`, then copy source. [src1]

## Diagnostic Commands

```bash
# Check all service status and health
docker compose ps

# View logs for a specific service
docker compose logs -f api

# Test MongoDB connectivity from API container
docker compose exec api node -e "
  const { MongoClient } = require('mongodb');
  MongoClient.connect(process.env.MONGODB_URI)
    .then(() => console.log('MongoDB OK'))
    .catch(e => console.error('MongoDB FAIL:', e.message));
"

# Check MongoDB health directly
docker compose exec mongo mongosh --eval "db.adminCommand('ping')" -u root -p

# Inspect Docker network
docker network inspect $(docker compose ps -q | head -1 | xargs docker inspect --format='{{range .NetworkSettings.Networks}}{{.NetworkID}}{{end}}')

# Check image sizes
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" | grep -E "mern|mongo|nginx"

# Check container resource usage
docker compose stats --no-stream

# Test Nginx proxy configuration
docker compose exec nginx nginx -t

# Verify no credentials in image layers
docker history mern-api --no-trunc | grep -i password
```

## Version History & Compatibility

| Component | Version | Status | Notes |
|---|---|---|---|
| Docker Compose | V2 (docker compose) | Current | V1 (docker-compose) deprecated Jan 2024 |
| MongoDB | 8.0 | Current | Requires mongosh (mongo shell removed in 6.0) |
| MongoDB | 7.0 | LTS until 2027 | Last version with Debian 11 support |
| Node.js | 22 LTS | Current (Active LTS until Oct 2027) | Recommended for production |
| Node.js | 20 LTS | Maintenance until Apr 2026 | Still widely used |
| Nginx | 1.27 | Current mainline | Alpine variant recommended for size |
| React | 19 | Current | Vite preferred over CRA (deprecated) |
| Angular | 19 | Current | Standalone components default |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Development environment for 1-5 developers | Multi-node production cluster with auto-scaling | Kubernetes or Docker Swarm |
| Single-server production deployment (< 10K RPM) | High-availability MongoDB requirement | MongoDB Atlas + containerized API |
| CI/CD integration testing | Serverless/edge deployment | Vercel/Netlify (frontend) + managed DB |
| Consistent environment across dev/staging/prod | Need GPU workloads or specialized hardware | Cloud-specific services (ECS, Cloud Run) |
| Quick prototyping and demos | Microservices with 10+ services | Kubernetes with Helm charts |

## Important Caveats

- Docker Compose V1 (`docker-compose` with hyphen) is deprecated since January 2024 -- use V2 (`docker compose` as a Docker CLI plugin)
- MongoDB 6.0+ removed the `mongo` shell -- use `mongosh` for health checks and interactive sessions
- Node.js Alpine images (`node:22-alpine`) may cause issues with native npm packages (bcrypt, sharp) that require glibc -- use `node:22-slim` (Debian-based) for better compatibility
- Docker Desktop on macOS/Windows has slower volume performance for bind mounts -- use named volumes or Docker's synchronized file shares for development
- The `MONGO_INITDB_*` environment variables only take effect on first initialization when the data volume is empty -- changing them after initial setup has no effect
- React/Vite environment variables must be prefixed with `VITE_` and are baked in at build time, not runtime -- rebuilding the image is required to change API URLs

## Related Units
<!-- Generated from related_kos frontmatter -->

- [Docker Compose Networking Guide](/software/devops/docker-compose-networking/2026)
- [Nginx Reverse Proxy Configuration](/software/devops/nginx-reverse-proxy/2026)
- [Kubernetes MERN Deployment](/software/devops/kubernetes-mern-deployment/2026)
- [Docker Django Stack](/software/devops/docker-django-stack/2026)
