Production Docker images: smaller, safer, and faster to build
Production images need design#
A Dockerfile that works locally may still be poor for production: large images, dev dependencies, copied env files, root users, unclear tags, and slow cache behavior. Treat images as release artifacts.
Multi-stage build#
Build-time tools are often not needed at runtime. Multi-stage builds let you compile in one stage and copy only the required output into a smaller runtime stage.
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=build /app/.next ./.next
COPY --from=build /app/public ./public
EXPOSE 3000
CMD ["npm", "run", "start"]
Cache and context#
Copy dependency manifests before source code so install layers can be reused. Keep build context small with .dockerignore to prevent unrelated files from invalidating cache.
Secrets and users#
Never bake secrets into images. Inject database passwords, service keys, signing secrets, and API keys at runtime. Run as a non-root user when the framework and base image allow it.
Checklist#
- Use explicit base image versions.
- Use multi-stage builds when runtime needs less than build time.
- Exclude local output, logs, Git history, and env files.
- Keep secrets out of images and logs.
- Tag images by commit SHA or release version.
- Scan dependencies and images in CI.
Primary references#
Share this article
Help others discover this article
Comments
Loading comments...
Loading comments...