Why Docker for Frontend?
Docker ensures every developer on your team has the exact same development environment, eliminates "works on my machine" issues, and makes deployment consistent. It's especially useful for frontend apps that need specific Node.js versions or complex build processes.
React Application
Development Dockerfile
# Dockerfile.dev
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 5173
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
Production Dockerfile (Multi-Stage)
# Dockerfile
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Serve with Nginx
FROM nginx:alpine
# Copy custom nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy built files
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Nginx Configuration for SPA
# nginx.conf
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Handle SPA routing
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_min_length 1000;
}
Next.js Application
# Dockerfile for Next.js (standalone output)
FROM node:20-alpine AS base
# Stage 1: Install dependencies
FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
# Stage 2: Build
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Set build-time environment variables
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
RUN npm run build
# Stage 3: Production
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
Docker Compose for Frontend Development
# docker-compose.yml
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules # Anonymous volume for node_modules
environment:
- NEXT_PUBLIC_API_URL=http://api:4000
depends_on:
- api
api:
build: ./api
ports:
- "4000:4000"
environment:
- DATABASE_URL=postgres://admin:secret@db:5432/myapp
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Common Gotchas
Watch Out For
- Hot Reload: Use bind mounts and
--host 0.0.0.0to enable hot reload in containers - node_modules: Use an anonymous volume to prevent host node_modules from overriding the container's
- Build Args: Environment variables prefixed with
NEXT_PUBLIC_must be set at build time, not runtime - File Watching: On macOS/Windows, file watching may need polling — set
WATCHPACK_POLLING=true