Dockerfile Best Practices: Building Small, Secure Images
Dockerfile Best Practices: Building Small, Secure Images
A Dockerfile that works on your laptop and a Dockerfile that belongs in production are two different things. Here are the practices that separate them.
Table of Contents
- 1. Use multi-stage builds
- 2. Pin your base images
- 3. Order layers for cache
- 4. Don't run as root
- 5. Write a real .dockerignore
- 6. One process per container
- 7. Scan before you ship
1. Use multi-stage builds
Build dependencies (compilers, dev headers, test tooling) have no business in your final image. Multi-stage builds keep the runtime image small:
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
USER node
CMD ["node", "dist/server.js"]
2. Pin your base images
FROM node:latest is a lottery. Pin to a specific version — better yet, a digest — so builds are reproducible and you upgrade deliberately.
3. Order layers for cache
Docker caches each layer. Copy package.json before the rest of the source so dependency installs are cached unless dependencies actually change:
COPY package*.json ./ # changes rarely
RUN npm ci # cached layer
COPY . . # changes often — cache busts here only
4. Don't run as root
Containers running as root give attackers a head start on container escapes. Most official images ship a non-root user — use it: USER node, or create your own.
5. Write a real .dockerignore
Without it, COPY . . ships your .git, node_modules, local .env files, and secrets into the image:
.git
node_modules
.env
*.md
.dockerignore
6. One process per container
Your container's PID 1 should be the app — not a shell script wrapping three daemons. Use CMD ["node", "server.js"] (exec form) so signals like SIGTERM reach the process and graceful shutdown works.
7. Scan before you ship
Run docker scout cves or trivy image myapp:latest in CI. Base images accumulate CVEs; catching them in the pipeline beats catching them in an incident.
Small images, non-root users, pinned versions, clean layers — apply these and your containers stop being a liability.
Related Articles
- Running WordPress on Docker Compose in Production
- Docker Compose Tutorial: From Zero to Multi-Container Apps
Last Updated: October 2026 Author: CloudOpsGuide Team Difficulty: Beginner Estimated Reading Time: 9 minutes