Deploying MERN apps: Dockerfile for Node.js/React, docker-compose for local dev, CI/CD with GitHub Actions, MongoDB Atlas, environment configuration, health checks, and zero-downtime deployment. Use when containerizing or deploying a MERN app.
Deployment target or problem: $ARGUMENTS
# backend/Dockerfile
# Stage 1: Build dependencies
FROM node:20-alpine AS deps
WORKDIR /app
# Copy package files first for layer caching
COPY package.json package-lock.json ./
RUN npm ci --only=production
# Stage 2: Runtime image
FROM node:20-alpine AS runtime
WORKDIR /app
# Security: run as non-root user
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nodeapp && \
chown -R nodeapp:nodejs /app
# Copy production deps from stage 1
COPY --from=deps --chown=nodeapp:nodejs /app/node_modules ./node_modules
# Copy source
COPY --chown=nodeapp:nodejs . .
USER nodeapp
# Validate required env vars at build time (optional)
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', r => r.statusCode === 200 ? process.exit(0) : process.exit(1))"
CMD ["node", "src/server.js"]
# frontend/Dockerfile
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
# Inject build-time env vars (these get baked into the JS bundle)
ARG VITE_API_URL
ENV VITE_API_URL=${VITE_API_URL}
RUN npm run build # outputs to /app/dist
# Stage 2: Serve with nginx
FROM nginx:alpine AS runtime
# Custom nginx config for SPA routing
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# frontend/nginx.conf
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_types text/plain application/javascript text/css application/json;
gzip_min_length 1000;
# Cache static assets
location ~* \.(js|css|png|jpg|svg|ico|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# SPA routing — all paths serve index.html
location / {
try_files $uri $uri/ /index.html;
}
# Proxy API calls (optional — avoids CORS in production)
location /api {
proxy_pass http://backend:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
# docker-compose.yml