Dockerfile Best Practices
Instruction Reference
# Base image — use specific tags, never :latest in prod
FROM node:20-alpine
# Metadata
LABEL maintainer="team@company.com"
# Set working directory (creates if missing)
WORKDIR /app
# Environment variables
ENV NODE_ENV=production
ENV PORT=3000
# ARG — build-time variable (not in final image env)
ARG BUILD_VERSION=latest
# Copy files — use .dockerignore to exclude node_modules etc.
COPY package*.json ./ # copy package files first (cache layer)
COPY . . # then source code
# Run commands — chain with && to minimize layers
RUN apt-get update && apt-get install -y curl git && rm -rf /var/lib/apt/lists/* # clean up in same layer!
# Install dependencies
RUN npm ci --only=production
# Expose port (documentation only — does NOT publish)
EXPOSE 3000
# Non-root user (security best practice)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# Healthcheck
HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD curl -f http://localhost:3000/health || exit 1
# Volume mount point
VOLUME ["/app/data"]
# CMD — default command (overridable at runtime)
CMD ["node", "server.js"]
# ENTRYPOINT — fixed executable (CMD becomes its args)
ENTRYPOINT ["node"]
CMD ["server.js"]Multi-Stage Builds
Use multiple FROM stages to keep the final image small — build tools and dev dependencies don't end up in production.
# Stage 1: build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: production image
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist # only copy built output
EXPOSE 3000
CMD ["node", "dist/server.js"]
# Build a specific stage
# docker build --target builder -t myapp:builder .Next.js Dockerfile Example
FROM node:20-alpine AS base
FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm && pnpm install --frozen-lockfile
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN pnpm build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
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
CMD ["node", "server.js"].dockerignore
node_modules
.git
.gitignore
*.md
.env*
.next
dist
coverage
.nyc_output
*.logLayer Caching Tips
Copy package.json before source code — deps only reinstall when package.json changes
Chain apt-get update && apt-get install && rm -rf in a single RUN
Order instructions from least to most frequently changed
Use --mount=type=cache in BuildKit to cache package manager downloads
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free