docker compose up --build -d to build and start all services in detached mode.MONGO_INITDB_ROOT_USERNAME and MONGO_INITDB_ROOT_PASSWORD.| 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 |
| 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 |
| 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 |
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
Set up the standard MERN project layout that Docker Compose expects. [src1]
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
Create a .env file at the project root. Docker Compose automatically loads this file. [src3]
# .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
Create the multi-service configuration with health checks and dependency ordering. [src6]
# 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}
volumes:
- mongo-data:/data/db
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
Verify: docker compose config --services → should list: mongo, api, client, nginx
Build a production-ready Express API image with proper security. [src3]
# server/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 --chown=node:node package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --chown=node:node . .
USER node
EXPOSE 3001
CMD ["dumb-init", "node", "server.js"]
Verify: docker build -t mern-api ./server → should complete without errors
Build React with Node, then serve with Nginx for minimal image size. [src4]
# client/Dockerfile (React/Vite)
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
FROM nginx:1.27-alpine AS production
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
Verify: docker build -t mern-client ./client → final image should be < 50MB
Create the Nginx configuration that routes API requests and serves static files. [src5]
# nginx/nginx.conf
worker_processes auto;
events { worker_connections 1024; }
http {
include /etc/nginx/mime.types;
upstream api { server api:3001; }
server {
listen 80;
location /api/ { proxy_pass http://api/; }
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
}
}
Verify: docker compose up nginx → curl -I http://localhost should return 200
Set up application database and user with least-privilege access. [src2]
// mongo-init/01-create-app-user.js
db.createUser({
user: 'appuser',
pwd: 'app_password_change_me',
roles: [
{ role: 'readWrite', db: 'appdb' },
{ role: 'dbAdmin', db: 'appdb' }
]
});
print('MongoDB init complete: appuser created');
Verify: docker compose exec mongo mongosh -u root -p --eval "db.getUsers()" → should list appuser
Launch all services and verify connectivity. [src7]
# 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
Verify: docker compose ps → all services should show "healthy" or "running"
Full script: docker-compose-production.yml (68 lines)
services:
mongo:
image: mongo:8.0
restart: unless-stopped
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_ROOT_USER}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD}
volumes:
- mongo-data:/data/db
- ./mongo-init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
Full script: docker-compose-dev.yml (54 lines)
services:
mongo:
image: mongo:8.0
ports:
- "27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_ROOT_USER}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD}
volumes:
- mongo-data:/data/db
Full script: server-dockerfile (32 lines)
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 --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"]
Full script: client-dockerfile-react (31 lines)
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
FROM nginx:1.27-alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
# 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
# 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}
# BAD -- unpinned version, running as root, using npm start
FROM node:latest
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "start"]
# 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"]
# 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"]
# 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/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
# BAD -- API starts before MongoDB is ready
services:
api:
depends_on:
- mongo
# 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
# BAD -- MongoDB accessible from outside Docker network
services:
mongo:
ports:
- "27017:27017"
# GOOD -- MongoDB only accessible within Docker network
services:
mongo:
expose:
- "27017"
# No 'ports' directive -- only other containers can reach it
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]CMD ["npm", "start"], npm becomes PID 1 and doesn't forward signals. Fix: Use dumb-init or call CMD ["node", "server.js"] directly. [src3]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]/docker-entrypoint-initdb.d/ scripts only run on first initialization when /data/db is empty. Fix: Remove volume to re-run: docker volume rm mern-docker_mongo-data. [src2]npm install includes devDependencies. Fix: Use npm ci --omit=dev and set ENV NODE_ENV=production before install. [src3]package.json and package-lock.json first, run npm ci, then copy source. [src1]# 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
# 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
| 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 |
| 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 |
docker-compose with hyphen) is deprecated since January 2024 -- use V2 (docker compose as a Docker CLI plugin)mongo shell -- use mongosh for health checks and interactive sessionsnode:22-alpine) may cause issues with native npm packages (bcrypt, sharp) that require glibc -- use node:22-slim (Debian-based) for better compatibilityMONGO_INITDB_* environment variables only take effect on first initialization when the data volume is empty -- changing them after initial setup has no effectVITE_ and are baked in at build time, not runtime -- rebuilding the image is required to change API URLs