Dockerfiles describe how to build your container images. Each instruction shapes the final image and its behavior. Here’s a quick, practical guide.
Core instructions
- FROM: Sets the base image. Start with a minimal, trusted base (e.g.,
alpineor vendor official images). - RUN: Executes commands at build time (install packages, build assets). Combine related commands to reduce layers; use
--no-cachefor package managers. - CMD: Default command when the container starts. Only one CMD is used—the last one declared. Often an array form like
["node","app.js"]. - ENTRYPOINT: Sets the main executable. Combine with CMD for default args. Use array form to avoid shell wrapping.
- WORKDIR: Sets the working directory for following instructions and runtime.
- COPY: Copies files into the image. Use
.dockerignoreto keep the context small. - ADD: Like COPY but can fetch URLs and auto-extract archives. Prefer COPY unless you need those extras.
- EXPOSE: Documents the port (doesn’t publish it). Useful for clarity and tooling.
- ENV: Sets environment variables for build and runtime.
- ARG: Build-time variables (not kept at runtime). Good for build flags and versions.
- VOLUME: Declares mount points. Use when you expect external storage, but be deliberate—volumes can hide changes.
- USER: Sets the user for following instructions and runtime. Run as non-root whenever possible.
- LABEL: Metadata (maintainer, version, source, license). Helps with governance and scanning.
- HEALTHCHECK: Command to determine if the container is healthy (e.g., curl localhost/healthz).
Best practices
- Keep images small: remove build deps, use multi-stage builds, and pin versions.
- Be explicit: use array form for CMD/ENTRYPOINT to avoid shell quirks.
- Cache smartly: order instructions so stable steps come first; invalidating COPY early forces rebuilds.
- Secure by default: run as non-root, avoid adding secrets, and scan images.
- Document intent: use LABEL and clear directory structure; keep a
.dockerignore.
Common patterns
# Multi-stage build example (Node)
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runtime
WORKDIR /app
COPY --from=build /app/dist ./dist
ENV NODE_ENV=production
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]
ENTRYPOINT vs CMD
- ENTRYPOINT: The executable you always run.
- CMD: Default arguments; users can override with
docker run image new-args. - Together:
ENTRYPOINT ["myapp"]andCMD ["--port","8080"]→ defaultmyapp --port 8080.
Quick checklist
- Choose a minimal, trusted base image.
- Use multi-stage builds to keep runtime lean.
- Run as non-root; set USER.
- Set WORKDIR, ENV, and HEALTHCHECK.
- Use .dockerignore to avoid sending junk in build context.
- Pin versions and scan images regularly.
With a solid grasp of Dockerfile instructions and these patterns, you can build lean, secure images that behave predictably in CI/CD and production.