Ardrey 情報マガジン

Dockerfile

📅 November 6, 2026📖 Verified Publication

The Complete Technical Guide to Dockerfile Architecture and Optimization

Key Takeaways:

  • Layer Caching Efficiency: Dockerfiles execute instructions sequentially; each successful instruction creates a layer. Placing frequently changing instructions (like COPY . /app) at the end can improve cache hit rates by up to 40% in CI/CD pipelines.
  • Base Image Selection: Choosing a minimal base image (e.g., Alpine or distroless) instead of a full OS (e.g., Ubuntu) can reduce final image size by 60-80%, directly impacting network transfer times and storage costs.
  • Instruction Determinism: Use of fixed versions in ADD/COPY commands and pinning package versions in RUN commands ensures reproducible builds, a critical requirement for production-grade containerization.
  • Multi-Stage Builds: Utilizing multi-stage builds allows developers to separate build dependencies from runtime dependencies, resulting in images containing only the necessary executable binaries and libraries.

Understanding the Dockerfile Syntax and Execution Model

A Dockerfile is a plain text file containing a list of instructions that are executed sequentially when docker build is invoked. The core operational unit of a Dockerfile is the "instruction," which modifies the image's filesystem or defines the container's execution parameters. There are two primary categories of instructions: filesystem instructions (such as COPY, RUN, and ADD) and execution instructions (such as ENTRYPOINT and EXPOSE).

Instruction Semantics and Metadata

Each instruction in a Dockerfile serves a specific function. While many instructions appear simple, their underlying implementation involves complex file system operations (specifically the union file system or whiteout mechanism) to manage layers. For instance, when a RUN instruction executes a command that deletes a file, Docker does not delete the file from the underlying layer; instead, it creates a "whiteout" marker in the next layer. This mechanism is critical for understanding why image sizes may not decrease as expected when files are removed.

Optimization Strategies: Multi-Stage Builds and Layering

Efficiency in containerization is primarily driven by two factors: reducing the final image size and maximizing build cache efficiency. Modern Dockerfiles often utilize multi-stage builds to address the issue of "build bloat."

The Mechanics of Multi-Stage Builds

In a standard single-stage build, all tools required for compilation (compilers, package managers, SDKs) remain in the final image. A multi-stage build separates the process into distinct phases. For example, the first stage uses a language-specific builder image (e.g., golang:1.21-alpine) to compile the application. The second stage uses a minimal runtime image (e.g., scratch or alpine) and copies only the compiled binary from the first stage. This approach typically results in a final image size reduction of over 90% compared to including the full toolchain.

Comparative Analysis of Base Images

The selection of the base image is the single most impactful decision in a Dockerfile's architecture. It dictates the security attack surface, resource footprint, and compatibility with the host operating system. The following table compares the three most common base image families regarding size, security, and portability.

Base Image Type Estimated Size Security Surface Portability Use Case
Full Linux Distros (Ubuntu, Debian) ~50MB - 300MB High (many binaries, services) High (includes tooling) Debugging, legacy apps, complex OS dependencies
Minimal Distros (Alpine, Amazon Linux 2 Micro) ~5MB - 100MB Moderate (musl vs glibc issues) Medium (missing some binaries) Production microservices, serverless functions
Distroless / Scratch ~0MB - 10MB Lowest (no shell, no package manager) Low (requires external tools) High-security requirements, static binaries

Caching Best Practices

To leverage the Docker layer cache effectively, developers must understand that any change to a layer invalidates all subsequent layers. Therefore, the logical workflow for a Dockerfile should be:

  1. Copy dependency manifests (e.g., package.json, requirements.txt).
  2. Install dependencies.
  3. Copy the source code.
  4. Run build/test commands.

This order ensures that if only the source code changes, the installation of dependencies (a time-consuming step) is pulled from the cache.

FAQs on Dockerfile Implementation

What is the difference between COPY and ADD in a Dockerfile?

The COPY instruction is preferred for general use as it is simpler and more predictable, moving files from the build context to the image. The ADD instruction is specialized; it automatically handles extracting tar/bz2 files and can fetch content from a URL. Best practice dictates using COPY for all local files to avoid unexpected behavior during builds.

Why should I use .dockerignore?

The .dockerignore file prevents specified files from being sent to the build daemon. It is critical for two reasons: first, it prevents sensitive data (like .env files or private keys) from accidentally being baked into the image; second, it significantly reduces the size of the build context, which improves network performance and build speed, especially in CI/CD environments.

How does the ENTRYPOINT instruction interact with CMD?

Both instructions define the executable when a container starts, but they work together. ENTRYPOINT sets the main command, while CMD provides default arguments to that command. If a user overrides the command at runtime (e.g., docker run image ls -l), the arguments replace the CMD but are appended to the ENTRYPOINT. This allows for flexible container execution while maintaining a consistent base command.