Writing and Optimizing Dockerfiles
What this article covers
- Structure of a Dockerfile.
- Key instructions and their order.
- Layers, caching, and image size.
- Security and clean builds.
- Common pitfalls and solutions.
Introduction: Writing and Optimizing Dockerfiles
A Dockerfile is the recipe for a Docker image. It specifies which base image to use, which files to copy, which commands to run, and how the container starts. A well-written Dockerfile produces small, secure images that build quickly. A poorly written one leads to long build times, bloated images, and security vulnerabilities.
This article covers the fundamentals, key instructions, and best practices for clean Dockerfiles.
Key Terms
- FROM: Base image.
- RUN: Execute a command.
- COPY: Copy files.
- ADD: Copy and extract files.
- CMD: Default command at startup.
- ENTRYPOINT: Fixed startup command.
- WORKDIR: Working directory.
- ENV: Environment variable.
- EXPOSE: Declare a port.
- USER: Switch user.
- Layer: A layer in the image.
- Cache: Build cache.
Basic Structure
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
USER node
EXPOSE 3000
CMD ["node", "server.js"]
Choosing FROM
| Image | Size | Use Case |
|---|---|---|
ubuntu | large | Compatibility |
debian:slim | medium | Versatile |
alpine | small | Minimal, but musl-based |
distroless | very small | Minimal, no shell |
Recommendation: Use official slim images or distroless for production containers.
Mind the Order
Docker caches layers. Copy files that change infrequently first:
- Base image.
- Dependency files.
- Install dependencies.
- Copy source code.
- Build steps.
- Runtime command.
Wrong:
COPY . .
RUN npm ci
Right:
COPY package*.json ./
RUN npm ci
COPY . .
COPY vs ADD
COPYsimply copies files.ADDcan handle URLs and tar archives.- In most cases,
COPYis safer and more predictable.
Minimize Layers
Each RUN, COPY, and ADD instruction creates a layer. Fewer layers mean smaller images:
RUN apt-get update && \
apt-get install -y --no-install-recommends curl ca-certificates && \
rm -rf /var/lib/apt/lists/*
Reduce Image Size
- Use
.dockerignore. - Install only production dependencies.
- Remove build tools.
- Use multistage builds.
- Don’t copy unnecessary files.
Example .dockerignore:
node_modules
.git
.env
Dockerfile
README.md
tests
Security
- Run as a non-root user:
RUN useradd -m appuser
USER appuser
- Never embed secrets in the Dockerfile.
- Don’t store passwords in environment variables.
- Keep permissions minimal.
- Use read-only filesystems when possible.
CMD and ENTRYPOINT
CMD is the default command; ENTRYPOINT is the fixed command. Combined:
ENTRYPOINT ["node"]
CMD ["server.js"]
Healthchecks
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
Build Command
docker build -t mein-image:1.0 .
Force Rebuild Without Cache
docker build --no-cache -t mein-image:1.0 .
Logs and Configuration
- Output logs to
stdout/stderr. - Pass configuration via environment variables.
- Don’t hardcode paths in images.
Common Pitfalls
- Everything in one layer: creates bloated images.
- Build tools in the runtime image: security risks.
- Secrets in environment variables: visible in images or logs.
- Wrong FROM version: using
latestinstead of fixed tags. - Missing .dockerignore: unnecessary files end up in the image.
- Wrong permissions: container runs as root.
- CMD in shell form: signals don’t propagate correctly.
Further Reading and Resources
- BotServ.de Docker Multistage Builds
- BotServ.de Docker Commands
- BotServ.de Docker Image Optimization
- BotServ.de Docker Security
FAQ: Dockerfile
What is a Dockerfile? A text file containing instructions to build a Docker image.
Should I use COPY . .?
Only with a .dockerignore in place, otherwise unnecessary files end up in the image.
What’s better: Alpine or Debian Slim? Debian Slim is simpler and more compatible; Alpine is smaller but musl-based.
How do I speed up the build?
Pay attention to layer order, leverage caching, and set up .dockerignore.
Why use a non-root user? It reduces risk if a container is compromised.
Sources and Further Reading
- Dockerfile Reference: https://docs.docker.com/reference/dockerfile/
- Docker Best Practices: https://docs.docker.com/develop/dev-best-practices/
- BuildKit: https://docs.docker.com/build/buildkit/
Summary: Writing and Optimizing Dockerfiles
A good Dockerfile is key to fast builds, small images, and secure containers. The right order of instructions, effective caching, multistage builds, .dockerignore, non-root users, and fixed image tags are essential. Following these principles helps you avoid common mistakes and build Docker images that work well in homelabs and production alike.


