Dockerizing frontend apps sounds simple — until node_modules disappears or your image is 1.2GB. Here are the most common mistakes I've seen (and made myself).
- Copying everything, then installing dependencies ❌ Wrong:
dockerfile
COPY . .
RUN npm install
RUN npm run build
Every code change invalidates the cache, forcing npm install to re-run. Slow.
✅ Right:
dockerfile
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
Docker caches the RUN npm install layer unless package.json changes. Much faster rebuilds.
- Using node:latest as the base image for production Your 900MB image includes compilers, npm, and Python — none of which your nginx server needs.
✅ Right: Multi-stage builds
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
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
Final image: ~25MB instead of 900MB.
- Running as root in the container Your container runs as root by default. If someone sneaks into your container, they own the host's kernel namespaces.
✅ Right:
dockerfile
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
USER nextjs
For nginx images, they already have a nginx user — use it.
- Ignoring npm ci in favor of npm install npm install can update lock files and install different versions than your package-lock.json defines.
✅ Right:
dockerfile
COPY package*.json package-lock.json ./
RUN npm ci
npm ci:
Fails if lock file is out of sync
Installs exactly what's in the lock file
Is faster for CI/CD
- Leaving node_modules in the final stage You don't need dependencies in production if you're serving static files. But sometimes people do:
dockerfile
FROM node:20-alpine
COPY . .
RUN npm ci && npm run build
CMD ["npm", "run", "preview"] # still has dev dependencies!
✅ Right: Build once, discard everything except the output (see multi-stage example above). Only the dist or .next folder goes to production.
Quick checklist before you ship
Multi-stage build with static server
COPY package.json before RUN npm ci
Non-root user
npm ci not npm install
No dev dependencies in final image
Your frontend image doesn't need Node.js at runtime. Treat your Dockerfile like you treat your bundle — ship only what's necessary.
What mistake would you add? Drop it in the comments.
Top comments (0)