Dockerfile Advanced Deep Dive

Published on


Comprehensive Dockerfile Reference & Advanced Mechanics

1. Parser Directives

Parser directives are special instructions to the Docker builder (BuildKit). They are not comments, despite starting with #.

Strict Rules:

  1. Must be the very first lines of the Dockerfile.
  2. Format: # directive=value.
  3. Case-insensitive keys, but case-sensitive values.
  4. A blank line usually follows them.

Supported Directives

DirectiveDescriptionExample
syntaxDefines the location of the BuildKit frontend image to use. Allows using newer Dockerfile features on older Docker daemons.# syntax=docker/dockerfile:1
escapeSets the escape character. Crucial for Windows where \ is a path separator.# escape= (Backtick)
check(New in v1.8.0) Configures build checks (linters). Can skip specific warnings or treat warnings as fatal errors.# check=skip=JSONArgsRecommended
# check=error=true

2. Variables & Scoping

Understanding the lifecycle of data within the build process vs. the runtime container.

ARG vs. ENV: The Persistence Matrix

FeatureARG (Build-Time)ENV (Runtime)
LifecycleTemporary. Exists only during docker build.Permanent. Persists in the final image and running container.
Overridable?Yes, via --build-arg.Yes, via docker run -e (but defaults persist).
SecurityUnsafe for secrets. Visible in docker history.Unsafe for secrets. Visible in docker inspect.
Base InteractionCan be used in FROM lines (e.g., FROM alpine:$VERSION).Cannot be used in FROM.

Variable Scope Mechanics

  • Pre-FROM ARGs: ARG declared before FROM is globally available for FROM instructions but cleared once inside a build stage. You must re-declare it (without a value) to use it inside the stage.
    ARG VERSION=3.9
    FROM python:$VERSION
    ARG VERSION  # Required to make $VERSION visible to RUN commands below
    RUN echo $VERSION

Environment Replacement (Bash Modifiers)

Docker supports standard Bash-style variable manipulation:

  • ${VAR:-default}: If VAR is set, use it. If not, use default.
  • ${VAR:+word}: If VAR is set, result is word. If empty, result is empty.

3. Advanced File Operations

COPY vs. ADD: detailed Technical Comparison

1. COPY (Preferred Standard)

  • Scope: Copies only from the Build Context or other Build Stages (--from).
  • Behavior: Literal copy. No magic transformations.
  • Optimization: Supports --link. This uploads files to an independent image layer, allowing rebase without rebuilding previous layers.

2. ADD (Specialized)

  • Remote Fetch: Can accept a URL (http/https) as the source. Note: Remote files usually have 600 permissions by default.
  • Auto-Extraction: If the source is a local recognized archive (gzip, bzip2, xz), ADD automatically unpacks it to the destination.
    • Exception: Remote URL archives are not auto-unpacked.
  • Git Support: Can clone Git repositories directly (e.g., ADD git@github.com...).

Here-Documents (Inline Files)

Allows creating file content directly within the Dockerfile, reducing context clutter.

  • Syntax: COPY <<EOF /path/to/file
  • Variable Expansion: Use <<EOF to allow variable expansion. Use <<"EOF" (quoted) to prevent expansion (literal string).

4. BuildKit Mounts (Optimization & Security)

Standard RUN commands create a new layer and start fresh. Mounts allow sharing data between build steps or from the host without persisting it in the image layer.

Syntax: RUN --mount=type=TYPE,key=value command

Mount Types

TypeFunctionCritical Use Case
cachePersists directories between builds on the host machine.Package manager caches (/var/cache/apt, /root/.cache/pip) to speed up installs.
bindMounts a host directory read-only into the build container.Accessing source code without COPYing it (useful for compiling binaries).
tmpfsMounts a volatile in-memory filesystem.Avoiding writes to the container layer for temporary build artifacts.
secretSecurely exposes a secret (file or env var) only to that specific RUN instruction.API Keys, Passwords. Does not leak into docker history.
sshExposes the host’s SSH agent socket.Cloning private Git repositories without copying private keys.

5. Execution Primitives: CMD vs. ENTRYPOINT

This controls the process ID 1 (PID 1) of the container.

The Shell vs. Exec Form Distinction

This applies to RUN, CMD, and ENTRYPOINT.

1. Shell Form: CMD npm start

  • Mechanism: Docker runs /bin/sh -c "npm start".
  • Implication: Your process is NOT PID 1. It is a child of the shell.
  • Problem: Signals (like SIGTERM from docker stop) are received by the shell, which often fails to pass them to the child process. The container effectively hangs until it is killed.

2. Exec Form: CMD ["npm", "start"] (JSON Array)

  • Mechanism: Docker runs the binary directly via exec().
  • Implication: Your process IS PID 1.
  • Constraint: No shell processing. CMD ["echo", "$HOME"] will print the literal string $HOME because no shell exists to expand variables.

The Interaction Matrix

No ENTRYPOINTENTRYPOINT ["exec"]ENTRYPOINT command (Shell)
No CMDError (Invalid)Runs execRuns command
CMD ["p1"]Runs p1Runs exec p1 (Appends args)Arguments Ignored
CMD p1Runs /bin/sh -c p1Runs exec /bin/sh -c p1Arguments Ignored

Best Practice Pattern:

  • ENTRYPOINT: Set the immutable executable (e.g., ["python", "main.py"]).
  • CMD: Set the default flags that users might want to change (e.g., ["--verbose"]).

By - Chinmay Bagad

Thank you for reading. This article is part of the PaperAstro starter template.