---
title: "Dockerfile"
lang: "en"
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/dockerfile
---

0

[Home](/)›[Cheatsheets](/cheatsheets)

Cheatsheets

# Dockerfile

Dockerfile reference guide covering FROM, RUN, COPY, EXPOSE, CMD, ENTRYPOINT, environment variables, build optimization, best practices, and container image construction.

9 Categories24 Sections59 ExamplesPublished: 28 Feb 2026

[Markdown for AI(opens in a new tab)](/dockerfile/index.md "Open the plain-Markdown version of this page, for pasting into an AI tool")

Series

[Containers & Kubernetes](/series/containers--kubernetes)5/7

[PreviousDocker Compose](/cheatsheets/docker-compose)[NextDocker Swarm](/cheatsheets/docker-swarm)

All posts in this series (7)

Cheatsheets7

1.  [Docker](/cheatsheets/docker)
2.  [Helm](/cheatsheets/helm)
3.  [Kubernetes](/cheatsheets/kubernetes)
4.  [Docker Compose](/cheatsheets/docker-compose)
5.  [DockerfileYou are here](/cheatsheets/dockerfile)
6.  [Docker Swarm](/cheatsheets/docker-swarm)
7.  [kubectl](/cheatsheets/kubectl)

This cheat sheet covers the core Dockerfile instructions, best practices, and common pitfalls for building small, secure Docker images. Each section pairs a Dockerfile snippet with the build output it produces, plus notes on how the instruction behaves.

[Getting Started](#category-getting-started)

-   [FROM - Base Image Selection](#section-basic-from-instruction)
-   [Image Naming and Tags](#section-image-naming)
-   [Multi-Stage Dockerfile Overview](#section-multi-stage-intro)

[Copying & Adding Files](#category-copying-files)

-   [COPY - Basic File Operations](#section-copy-instruction)
-   [COPY with Ownership](#section-copy-with-chown)
-   [ADD - Advanced File Operations](#section-add-instruction)

[Running Commands](#category-running-commands)

-   [RUN - Basic Command Execution](#section-run-basic)
-   [RUN - Exec Form vs Shell Form](#section-run-exec-form)

[Environment & Variables](#category-environment-vars)

-   [ENV - Environment Variables](#section-env-instruction)
-   [ARG - Build Arguments](#section-arg-instruction)
-   [Variable Substitution and Defaults](#section-variable-substitution)

[Working Directory & Volumes](#category-workdir-volumes)

-   [WORKDIR - Working Directory](#section-workdir-instruction)
-   [VOLUME - Data Persistence Points](#section-volume-instruction)

[Exposing Ports](#category-ports-exposure)

-   [EXPOSE - Port Declarations](#section-expose-instruction)

[Entry Points & Commands](#category-entrypoint-cmd)

-   [CMD - Default Command](#section-cmd-instruction)
-   [ENTRYPOINT - Entry Point](#section-entrypoint-instruction)
-   [CMD and ENTRYPOINT Interaction](#section-cmd-entrypoint-interaction)

[Build Optimization](#category-optimization)

-   [Layer Caching Strategy](#section-layer-caching)
-   [.dockerignore File](#section-dockerignore)
-   [Multi-Stage Build Optimization](#section-multistage-optimization)

[Metadata & Advanced](#category-advanced-metadata)

-   [LABEL - Image Metadata](#section-label-instruction)
-   [USER - Container User](#section-user-instruction)
-   [HEALTHCHECK - Container Health](#section-healthcheck-instruction)
-   [Dockerfile Shell Form](#section-shell-directive)

No commands found

Try adjusting your search term

## Getting Started

### FROM - Base Image Selection

Base image selection and version pinning for a Dockerfile

#### Accessibility

Beginner

#### Best Practices

-   Always specify explicit version tags (never use latest)
-   Choose slim or alpine variants for production to reduce image size
-   Use official images from Docker Hub when possible
-   Review image documentation for security and size considerations

#### Common Errors

-   **FROM instruction not first:** FROM must be first instruction in Dockerfile
-   **Using latest tag:** Loses reproducibility and introduces unpredictable changes

#### Keywords

FROMbaseimageversiontagregistry

[Learn more](https://docs.docker.com/engine/reference/builder/#from)

#### FROM Ubuntu LTS Base Image

Select a specific Ubuntu version as the base image. Pinning the version keeps builds reproducible and makes the patch level explicit.

Code

```
1FROM ubuntu:20.042RUN apt-get update && apt-get install -y curl
```

Execution

Terminal window

```
$ docker build -t myapp:latest .Step 1/2 : FROM ubuntu:20.04 ---> 1d622ef08d5eStep 2/2 : RUN apt-get update && apt-get install -y curl ---> Running in 5f8a9c2b3d4eCollecting packages... ---> 7c9e4f2b3a5dSuccessfully built 7c9e4f2b3a5d
```

-   Always use specific versions, never just 'ubuntu' or 'latest'
-   ubuntu:20.04 is LTS and receives security updates longer
-   First instruction must be FROM

#### FROM Alpine Lightweight Base

Alpine-based images are minimal, which suits production microservices where image size matters.

Code

```
1FROM alpine:3.182RUN apk add --no-cache python3 py3-pip
```

Execution

Terminal window

```
$ docker build -t lightweight-app:latest .Step 1/2 : FROM alpine:3.18 ---> a24bb4aacf12Step 2/2 : RUN apk add --no-cache python3 py3-pip ---> Running in 3f7e2c1a9b5d ---> 9c2d4e7f1a3bSuccessfully built 9c2d4e7f1a3b
# Final image size: 89MB vs 77MB (Ubuntu) - but lighter for specific apps
```

-   Alpine uses musl libc instead of glibc
-   Package manager is 'apk' not 'apt'
-   Smallest base images available

#### FROM Official Python Image

Language-specific official images ship with the runtime already installed, so the Dockerfile stays shorter.

Code

```
1FROM python:3.11-slim2WORKDIR /app3COPY requirements.txt .4RUN pip install -r requirements.txt
```

Execution

Terminal window

```
$ docker build -t python-app:latest .Step 1/4 : FROM python:3.11-slim ---> b9ef8f396e26Step 2/4 : WORKDIR /app ---> Running in 3a5f8d2c1e4b ---> 6e2a7f9d4c1bStep 3/4 : COPY requirements.txt . ---> c9d4e2f7a1b5Step 4/4 : RUN pip install -r requirements.txt ---> Running in 7f2e4c9a3d1bSuccessfully built 8d3f5e2a7c4b
```

-   python:3.11 has full Python plus build tools (1.1GB)
-   python:3.11-slim has minimal dependencies (181MB)
-   python:3.11-alpine is smallest (51MB)

### Image Naming and Tags

Docker image naming conventions and tagging strategies

#### Accessibility

Beginner

#### Best Practices

-   Use semantic versioning (major.minor.patch)
-   Always tag with version number, not just latest
-   Maintain stable/latest tags for quick releases
-   Use registry domain for private images

#### Common Errors

-   **Using only latest tag:** Makes it impossible to rollback to previous versions

#### Keywords

tagnamingregistryversionsemantic

[Learn more](https://docs.docker.com/engine/reference/commandline/tag/)

#### Tag Image with Domain Registry

Tag images with registry domain and semantic version for private registries and version management.

Code

```
1FROM nginx:1.252COPY index.html /usr/share/nginx/html/
```

Execution

Terminal window

```
$ docker build -t gcr.io/my-project/web-server:1.0.0 .Successfully built 4d8e2f5a9c1b
$ docker push gcr.io/my-project/web-server:1.0.0The push refers to repository [gcr.io/my-project/web-server]8c3f7e2d4a1b: Pushed1.0.0: digest: sha256:abc123...
```

-   Format: \[registry\]/repository:tag
-   gcr.io = Google Container Registry
-   registry.example.com for self-hosted registries

#### Multiple Tags for Single Image

Tag same image with multiple names for version management and release strategies.

Code

```
1FROM node:18-alpine2COPY app.js .3CMD ["node", "app.js"]
```

Execution

Terminal window

```
$ docker build -t myapp:1.2.3 -t myapp:latest -t myapp:stable .Successfully built 5f9d3a2c8e1b
$ docker images | grep myappmyapp         1.2.3        5f9d3a2c8e1bmyapp         latest       5f9d3a2c8e1bmyapp         stable       5f9d3a2c8e1b
```

-   Use -t flag multiple times to create multiple tags
-   All tags reference same image layers (no duplication)
-   Common tags: latest, stable, v1.2.3, main, rc1

### Multi-Stage Dockerfile Overview

Introduction to multi-stage builds for optimized image sizes

#### Accessibility

Intermediate

#### Best Practices

-   Use multi-stage builds for compiled languages (Go, Rust, Java)
-   Name stages meaningfully (builder, dependencies, runtime)
-   Copy only artifacts needed in final stage from builder
-   Reduces security attack surface with minimal dependencies

#### Common Errors

-   **Forgetting FROM in builder stage:** Each stage needs its own FROM

#### Keywords

multi-stagebuilderproductionsizeoptimization

[Learn more](https://docs.docker.com/build/building/multi-stage/)

#### Single Stage vs Multi-Stage Comparison

A single stage keeps the build tools in the image, so the result is large and carries dependencies the application never uses.

Code

```
1# Inefficient single stage2FROM golang:1.213WORKDIR /src4COPY . .5RUN go build -o /usr/local/bin/app .6ENTRYPOINT ["app"]
```

Execution

Terminal window

```
$ docker build -t app:single .Successfully built a2b8f4d7c3e1
$ docker images app:singleREPOSITORY   TAG       IMAGE ID       SIZEapp          single    a2b8f4d7c3e1   1.3GB
```

-   golang:1.21 is 1GB+ because it includes compiler and build tools
-   Binary is only a few megabytes
-   Extra bloat in production image

#### Optimized Multi-Stage Build

Multi-stage builds separate compilation from runtime, so the final image holds 15MB instead of 1.3GB.

Code

```
1FROM golang:1.21 AS builder2WORKDIR /src3COPY . .4RUN go build -o /usr/local/bin/app .5
6FROM alpine:3.187RUN apk add --no-cache ca-certificates8COPY --from=builder /usr/local/bin/app /usr/local/bin/app9ENTRYPOINT ["app"]
```

Execution

Terminal window

```
$ docker build -t app:multi .Step 1/5 : FROM golang:1.21 AS builder ---> 2d8c4f1a9e3bStep 3/5 : RUN go build -o /usr/local/bin/app . ---> Running in 4c5f8a2d7e1b ---> 7e3f4c6b1a9dStep 5/8 : FROM alpine:3.18 ---> a24bb4aacf12Step 6/8 : COPY --from=builder /usr/local/bin/app /usr/local/bin/app ---> c8d2f5a1e7b4Successfully built 8f2a4e9d3c1b
$ docker imagesapp          multi      8f2a4e9d3c1b   15MB
```

-   builder stage discarded after build
-   Only Alpine runtime included in final image
-   1.3GB -> 15MB reduction (1000x smaller)

## Copying & Adding Files

### COPY - Basic File Operations

Copy files and directories from build context to container

#### Accessibility

Beginner

#### Best Practices

-   COPY files after FROM but before RUN to use the cache
-   Use specific paths instead of COPY . . to reduce cache invalidation
-   Place stable files (dependencies) before frequently changing files

#### Common Errors

-   **Path not found:** Verify file exists relative to build context
-   **Permission denied:** Original file permissions preserved

#### Keywords

COPYfilesdirectoriescontextsourcedestination

[Learn more](https://docs.docker.com/engine/reference/builder/#copy)

#### Copy Single File

Copy single file from build context (current directory) into container filesystem.

Code

```
1FROM nginx:1.25-alpine2COPY index.html /usr/share/nginx/html/
```

Execution

Terminal window

```
$ docker build -t web:latest .Step 1/2 : FROM nginx:1.25-alpine ---> a24bb4aacf12Step 2/2 : COPY index.html /usr/share/nginx/html/ ---> 5c2f8e1a3d7bSuccessfully built 5c2f8e1a3d7b
```

-   Source path is relative to build context (docker build . directory)
-   Destination is absolute path in container
-   Creates destination directory if it doesn't exist

#### Copy Multiple Files with Wildcard

Use wildcards to copy multiple files matching pattern into container.

Code

```
1FROM python:3.11-slim2WORKDIR /app3COPY *.py ./4COPY requirements.txt .
```

Execution

Terminal window

```
$ docker build -t python-app:latest .Step 1/4 : FROM python:3.11-slim ---> b9ef8f396e26Step 2/4 : WORKDIR /app ---> Running in a1b2c3d4e5f6 ---> 7e2f4c8a1d3bStep 3/4 : COPY *.py ./ ---> 3c5f9a2e7d1bStep 4/4 : COPY requirements.txt . ---> 8f4a2c6e9d1bSuccessfully built 8f4a2c6e9d1b
```

-   Wildcards: \* matches any characters, ? matches single character
-   COPY \*.py ./ copies all Python files to current directory

#### Copy Directory Recursively

Copy entire directories with all subdirectories and files.

Code

```
1FROM node:18-alpine2WORKDIR /app3COPY src ./src4COPY public ./public
```

Execution

Terminal window

```
$ docker build -t node-app:latest .Successfully built 9d5e3f2a8c1b
```

-   Directories copied recursively by default
-   Preserves directory structure in container

### COPY with Ownership

Copy files and set ownership to non-root user

#### Accessibility

Intermediate

#### Best Practices

-   Always use --chown to run as non-root in production
-   Combine with USER directive for consistent security
-   Create dedicated user with no shell for security

#### Common Errors

-   **chown: user not found: User must be created before COPY --chown:**

#### Keywords

COPYCHOWNuserownershippermissions

[Learn more](https://docs.docker.com/engine/reference/builder/#copy)

#### Copy with User Ownership

Copy files and assign ownership to non-root user for better security.

Code

```
1FROM node:18-alpine2RUN addgroup -S nodejs && adduser -S nodejs -G nodejs3WORKDIR /app4COPY --chown=nodejs:nodejs . .5USER nodejs6CMD ["node", "index.js"]
```

Execution

Terminal window

```
$ docker build -t secure-app:latest .Successfully built 7c3e5f1a2d8b
$ docker run secure-app:latest# App runs as nodejs user, not root
```

-   Format: COPY --chown=user:group source dest
-   Prevents running container as root
-   User/group must exist in image

#### Copy with Numeric User ID

Use numeric user:group IDs for CHOWN (more portable across systems).

Code

```
1FROM python:3.11-slim2RUN groupadd -r appuser && useradd -r -g appuser appuser3WORKDIR /home/appuser/app4COPY --chown=1000:1000 . .5USER appuser6ENTRYPOINT ["python", "main.py"]
```

Execution

Terminal window

```
$ docker build -t app:latest .Successfully built 4b2f7d9a1c5e
```

-   Numeric IDs are more reliable than names
-   1000:1000 common for non-root user

### ADD - Advanced File Operations

Add files, directories, or remote URLs with automatic extraction

#### Accessibility

Intermediate

#### Best Practices

-   Prefer COPY over ADD for local files (more explicit)
-   Use ADD only for remote URLs or tar extraction
-   Always clean up extracted archives to reduce layer size

#### Common Errors

-   **File not extracted:** Only tar archives are auto-extracted
-   **Large layer size:** Remember to rm archive after extraction

#### Keywords

ADDfilesURLtarextractionautomatic

[Learn more](https://docs.docker.com/engine/reference/builder/#add)

#### ADD from Remote URL

Download files from URLs and automatically extract tar archives.

Code

```
1FROM ubuntu:20.042ADD https://example.com/app.tar.gz /tmp/3WORKDIR /app4RUN tar -xzf /tmp/app.tar.gz && rm /tmp/app.tar.gz
```

Execution

Terminal window

```
$ docker build -t app:latest .Step 1/4 : FROM ubuntu:20.04 ---> 1d622ef08d5eStep 2/4 : ADD https://example.com/app.tar.gz /tmp/Downloading [==================================================>] 42MB/42MB ---> 8c5f2a1d4e9bStep 3/4 : WORKDIR /app ---> 7e3f4c2a1d8bSuccessfully built 7e3f4c2a1d8b
```

-   Automatically extracts .tar.\* files
-   Downloads happen during build
-   Should be followed by cleanup (rm) to remove archive

#### ADD Automatically Extracts TAR

TAR archive automatically extracted to destination directory.

Code

```
1FROM alpine:3.182ADD https://releases.example.com/v1.2.3/app.tar.gz /opt/3WORKDIR /opt4RUN ls -la
```

Execution

Terminal window

```
$ docker build -t app:latest .Successfully built 3f9e2d5a1c7b
Step 4/4 : RUN ls -la ---> Running in 5d8c3f2a1e9btotal 48drwxr-xr-x    3 root     root          4096 Feb 28 12:00 .drwxr-xr-x    1 root     root          4096 Feb 28 12:00 ..-rw-r--r--    1 root     root      12345678 Feb 28 12:00 app-rw-r--r--    1 root     root        54321 Feb 28 12:00 config.yaml
```

-   Only extracts recognized tar formats (.tar.gz, .tar.bz2, .tar)
-   Regular files copied as-is

## Running Commands

### RUN - Basic Command Execution

Execute commands during image build

#### Accessibility

Beginner

#### Best Practices

-   Chain commands with && to minimize layers
-   Clean package managers (apt-get clean, apk cache)
-   Use specific package versions for reproducibility
-   Remove build dependencies in same RUN to save space

#### Common Errors

-   **Command not found errors:** apt-get update must run before install
-   **Large layers:** Each RUN instruction creates new layer

#### Keywords

RUNcommandshellbuilddependencies

[Learn more](https://docs.docker.com/engine/reference/builder/#run)

#### RUN Single Command

Execute shell command during build. Each RUN creates a new layer.

Code

```
1FROM ubuntu:20.042RUN apt-get update3RUN apt-get install -y curl
```

Execution

Terminal window

```
$ docker build -t app:latest .Step 1/3 : FROM ubuntu:20.04 ---> 1d622ef08d5eStep 2/3 : RUN apt-get update ---> Running in 5f8a9c2b3d4eReading package lists... Done ---> 7c9e4f2b3a5dStep 3/3 : RUN apt-get install -y curl ---> Running in 3d7f2a1c8e5bSetting up curl (7.68.0-1ubuntu4)... ---> 8b4f3e2c9a1dSuccessfully built 8b4f3e2c9a1d
```

-   Each RUN instruction creates separate layer
-   Inefficient to use multiple RUN for related commands
-   Command executes in /bin/sh by default

#### RUN Chain Commands with AND

Chain multiple commands with && to create a single layer and keep the image smaller.

Code

```
1FROM ubuntu:20.042RUN apt-get update && \3    apt-get install -y curl git vim && \4    apt-get clean
```

Execution

Terminal window

```
$ docker build -t app:latest .Successfully built 9d5e3f2a8c1b
$ docker history app:latestIMAGE              CREATED        SIZE9d5e3f2a8c1b       30 seconds ago 187MB
```

-   && runs the next command only if the previous one succeeded
-   Backslash continues line in Dockerfile
-   apt-get clean removes package cache

#### RUN Install Multiple Packages

Multi-line RUN with backslash for readability while maintaining single layer.

Code

```
1FROM debian:12-slim2RUN apt-get update && apt-get install -y \3    build-essential \4    git \5    curl \6    wget \7    && apt-get clean \8    && rm -rf /var/lib/apt/lists/*
```

Execution

Terminal window

```
$ docker build -t build-tools:latest .Step 1/2 : FROM debian:12-slim ---> 7b2f8e3a9c1dStep 2/2 : RUN apt-get update && apt-get install -y ... ---> Running in 4f9d2c5a1e8bProcessing triggers... ---> 8c3f7e2d4a9bSuccessfully built 8c3f7e2d4a9b
```

-   apt-get clean removes cache (reduces layer size)
-   rm -rf /var/lib/apt/lists/\* also reduces layer

### RUN - Exec Form vs Shell Form

Differences between the exec form and the shell form of RUN

#### Accessibility

Intermediate

#### Best Practices

-   Use shell form for complex commands with pipes and variables
-   Use exec form for simple commands (more explicit)
-   Specify SHELL if using bash features
-   Remember exec form needs array syntax

#### Common Errors

-   **Unknown escape sequence:** Shell form escapes differ from exec form
-   **Variables not expanded:** Exec form doesn't expand $VAR

#### Keywords

RUNexecshellformJSONsignal

[Learn more](https://docs.docker.com/engine/reference/builder/#run)

#### Shell Form (Default)

Shell form processes variables and shell syntax (pipes, redirects, etc).

Code

```
1FROM alpine:3.182RUN echo "Building application"3RUN echo $PATH
```

Execution

Terminal window

```
$ docker build -t app:latest .Step 1/3 : FROM alpine:3.18 ---> a24bb4aacf12Step 2/3 : RUN echo "Building application" ---> Running in 5f8a9c2b3d4eBuilding application ---> 7c9e4f2b3a5dStep 3/3 : RUN echo $PATH ---> Running in 3d7f2a1c8e5b/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ---> 8b4f3e2c9a1d
```

-   RUN command will use /bin/sh -c
-   Environment variables expanded
-   Pipes and redirects work

#### Exec Form (JSON Array)

Exec form calls command directly without shell interpretation.

Code

```
1FROM alpine:3.182RUN ["apk", "add", "--no-cache", "curl"]3RUN ["echo", "Building"]
```

Execution

Terminal window

```
$ docker build -t app:latest .Step 1/3 : FROM alpine:3.18 ---> a24bb4aacf12Step 2/2 : RUN ["apk", "add", "--no-cache", "curl"] ---> Running in 5f8a9c2b3d4eFetching https://dl-cdn.alpinelinux.org/alpine/v3.18/main/x86_64/PACKAGES.gz(1/2) Installing ca-certificates ---> 7c9e4f2b3a5dStep 3/3 : RUN ["echo", "Building"] ---> Running in 3d7f2a1c8e5bBuilding ---> 8b4f3e2c9a1d
```

-   No shell expansion or variable interpolation
-   Command executed directly (better for signals)
-   Each array element becomes separate argument

#### Shell Parameter in RUN

Set default shell with SHELL instruction for all RUN, ENTRYPOINT, CMD instructions.

Code

```
1FROM ubuntu:20.042SHELL ["/bin/bash", "-c"]3RUN for i in 1 2 3; do echo "Number: $i"; done
```

Execution

Terminal window

```
$ docker build -t app:latest .Successfully built 5c2f8e1a3d7b
Step 2/2 : RUN for i in 1 2 3; do echo "Number: $i"; done ---> Running in 4f9d2c5a1e8bNumber: 1Number: 2Number: 3 ---> 8f4a2c6e9d1b
```

-   SHELL instruction affects subsequent instructions
-   Useful for bash-specific syntax (loops, pipes, etc)

## Environment & Variables

### ENV - Environment Variables

Set environment variables that persist in running containers

#### Accessibility

Beginner

#### Best Practices

-   Set PYTHONUNBUFFERED=1 for Python apps
-   Set NODE\_ENV for Node.js apps
-   Use ENV for default values, ARG for build-time arguments
-   Document purpose of environment variables

#### Common Errors

-   **Variable not set in container:** Used ARG instead of ENV
-   **Variable undefined:** Referenced before being set

#### Keywords

ENVenvironmentvariablespersistencecontainers

[Learn more](https://docs.docker.com/engine/reference/builder/#env)

#### Set Single Environment Variable

ENV sets environment variables available during build and in running containers.

Code

```
1FROM python:3.11-slim2ENV PYTHONUNBUFFERED=13ENV APP_ENV=production4RUN echo "Environment: $APP_ENV"
```

Execution

Terminal window

```
$ docker build -t app:latest .Step 1/4 : FROM python:3.11-slim ---> b9ef8f396e26Step 2/4 : ENV PYTHONUNBUFFERED=1 ---> Running in a1b2c3d4e5f6 ---> 7e2f4c8a1d3bStep 3/4 : ENV APP_ENV=production ---> Running in 5f8a9c2b3d4e ---> 8f4a2c6e9d1bStep 4/4 : RUN echo "Environment: $APP_ENV" ---> Running in 3d7f2a1c8e5bEnvironment: production ---> 9c2d4e7f1a3bSuccessfully built 9c2d4e7f1a3b
```

-   Variables persist in running containers
-   Available in all subsequent build steps
-   Can be overridden at runtime with -e flag

#### Multiple Variables and Defaults

Set multiple environment variables with backslash continuation.

Code

```
1FROM node:18-alpine2ENV NODE_ENV=production \3    PORT=3000 \4    LOG_LEVEL=info \5    DATABASE_URL=postgresql://localhost/db6RUN echo "Running in $NODE_ENV mode on port $PORT"
```

Execution

Terminal window

```
$ docker build -t app:latest .Successfully built 5c2f8e1a3d7b
$ docker run app:latest envNODE_ENV=productionPORT=3000LOG_LEVEL=infoDATABASE_URL=postgresql://localhost/db
```

-   Multiple ENV values on single line with backslash
-   All variables available in container at runtime

#### Variable Substitution in Dockerfile

Use variables in subsequent ENV declarations and RUN instructions.

Code

```
1FROM ubuntu:20.042ENV APP_VERSION=2.5.13ENV APP_PATH=/opt/app-${APP_VERSION}4RUN mkdir -p $APP_PATH && echo "App path: $APP_PATH"5LABEL version="${APP_VERSION}"
```

Execution

Terminal window

```
$ docker build -t app:2.5.1 .Step 4/5 : RUN mkdir -p $APP_PATH && echo "App path: $APP_PATH" ---> Running in 5f8a9c2b3d4eApp path: /opt/app-2.5.1 ---> 7c9e4f2b3a5dSuccessfully built 7c9e4f2b3a5d
```

-   ${VAR} syntax substitutes variable values
-   Variables only available from that line onward

### ARG - Build Arguments

Define build-time arguments that don't persist in final image

#### Accessibility

Intermediate

#### Best Practices

-   Use ARG for build-time configuration
-   Use ENV for runtime configuration
-   Document all ARG values
-   Provide sensible defaults

#### Common Errors

-   **ARG not available at runtime:** Use ENV instead to persist values
-   **Build argument not working:** Use --build-arg flag (not -e)

#### Keywords

ARGargumentsbuild-timevariablesubstitution

[Learn more](https://docs.docker.com/engine/reference/builder/#arg)

#### ARG for Build-Time Configuration

ARG defines build-time arguments passed via --build-arg flag, not persisted in image.

Code

```
1FROM python:3.11-slim2ARG BUILD_DATE3ARG VCS_REF4ENV BUILD_DATE=${BUILD_DATE}5ENV VCS_REF=${VCS_REF}6RUN echo "Built on: $BUILD_DATE from commit: $VCS_REF"
```

Execution

Terminal window

```
$ docker build --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \               --build-arg VCS_REF=$(git rev-parse --short HEAD) \               -t app:latest .Step 1/5 : FROM python:3.11-slim ---> b9ef8f396e26Step 5/5 : RUN echo "Built on: $BUILD_DATE from commit: $VCS_REF" ---> Running in 5f8a9c2b3d4eBuilt on: 2026-02-28T12:00:00Z from commit: abc123xyz ---> 8f4a2c6e9d1bSuccessfully built 8f4a2c6e9d1b
```

-   Build arguments only available during build
-   Not included in final image
-   Use ENV to persist values

#### ARG with Default Values

ARG with default values that can be overridden at build time.

Code

```
1FROM node:18-alpine2ARG NODE_ENV=development3ARG VERSION=1.0.04ARG PORT=30005ENV NODE_ENV=${NODE_ENV} \6    VERSION=${VERSION} \7    PORT=${PORT}8RUN echo "Building v$VERSION for $NODE_ENV on port $PORT"
```

Execution

Terminal window

```
$ docker build -t app:latest .Step 5/5 : RUN echo "Building v$VERSION for $NODE_ENV on port $PORT" ---> Running in 5f8a9c2b3d4eBuilding v1.0.0 for development on port 3000 ---> 7c9e4f2b3a5d
$ docker build --build-arg NODE_ENV=production \               --build-arg VERSION=2.0.0 \               -t app:2.0.0 .Step 5/5 : RUN echo "Building v$VERSION for $NODE_ENV on port $PORT" ---> Running in 3d7f2a1c8e5bBuilding v2.0.0 for production on port 3000 ---> 8b4f3e2c9a1d
```

-   Defaults used if no --build-arg provided
-   Can be overridden per build

### Variable Substitution and Defaults

Advanced variable substitution patterns in Dockerfile

#### Accessibility

Advanced

#### Best Practices

-   Use ARG for all configurable build parameters
-   Provide clear defaults
-   Document all variable names and purposes
-   Use semantic versioning for VERSION

#### Common Errors

-   **Variable undefined in FROM:** Declare ARG before FROM
-   **Unexpected substitution: ${} syntax required:**

#### Keywords

variablessubstitutiondefaultsexpansionpatterns

[Learn more](https://docs.docker.com/engine/reference/builder/#arg)

#### Variable Expansion with Defaults

Use variables in FROM and subsequent instructions for flexible builds.

Code

```
1ARG UBUNTU_VERSION=20.042FROM ubuntu:${UBUNTU_VERSION}3ARG APP_PATH=/opt/app4ARG APP_USER=appuser5RUN mkdir -p ${APP_PATH} && \6    useradd -r -m -d ${APP_PATH} ${APP_USER}
```

Execution

Terminal window

```
$ docker build -t app:focal .Successfully built 7c9e4f2b3a5d
$ docker build --build-arg UBUNTU_VERSION=22.04 \               -t app:jammy .Step 1/4 : FROM ubuntu:22.04 ---> 6790c6674aaaSuccessfully built 4b2f7d9a1c5e
```

-   ARG before FROM available in FROM instruction
-   Base image variant changes based on ARG

#### Build-Stage Variables

ARG declared at different stages for multi-stage build flexibility.

Code

```
1ARG GOLANG_VERSION=1.212FROM golang:${GOLANG_VERSION} AS builder3ARG VERSION=1.0.04WORKDIR /src5RUN echo "Building with Go $GOLANG_VERSION, version $VERSION"6
7FROM alpine:3.188ARG VERSION=1.0.09COPY --from=builder /src /app10ENV VERSION=${VERSION}
```

Execution

Terminal window

```
$ docker build --build-arg GOLANG_VERSION=1.22 \               --build-arg VERSION=2.0.0 \               -t app:2.0.0 .Step 1/6 : ARG GOLANG_VERSION=1.21 ---> Running in 5f8a9c2b3d4eStep 2/6 : FROM golang:1.22 ---> 2d8c4f1a9e3bStep 3/6 : ARG VERSION=1.0.0 ---> Running in 3d7f2a1c8e5bSuccessfully built 5c2f8e1a3d7b
```

-   ARG before FROM is global
-   ARG in stage is local to that stage
-   Each stage can redefine ARG

## Working Directory & Volumes

### WORKDIR - Working Directory

Set working directory for subsequent instructions

#### Accessibility

Beginner

#### Best Practices

-   Always set WORKDIR explicitly (default is /)
-   Use absolute paths for clarity
-   Keep paths consistent across team
-   Use WORKDIR with CHOWN for proper ownership

#### Common Errors

-   **No such file or directory:** WORKDIR doesn't exist for COPY in some images

#### Keywords

WORKDIRdirectorypathcdworking

[Learn more](https://docs.docker.com/engine/reference/builder/#workdir)

#### WORKDIR Basic Usage

WORKDIR sets current working directory for COPY, RUN, CMD, and ENTRYPOINT.

Code

```
1FROM python:3.11-slim2WORKDIR /app3COPY . .4RUN ls -la
```

Execution

Terminal window

```
$ docker build -t app:latest .Step 1/4 : FROM python:3.11-slim ---> b9ef8f396e26Step 2/4 : WORKDIR /app ---> Running in a1b2c3d4e5f6 ---> 7e2f4c8a1d3bStep 3/4 : COPY . . ---> 3c5f9a2e7d1bStep 4/4 : RUN ls -la ---> Running in 5f8a9c2b3d4etotal 48drwxr-xr-x  8 root root    4096 Feb 28 12:00 .-rw-r--r--  1 root root    1234 Feb 28 12:00 main.py-rw-r--r--  1 root root     234 Feb 28 12:00 requirements.txt ---> 8f4a2c6e9d1bSuccessfully built 8f4a2c6e9d1b
```

-   Creates directory if it doesn't exist
-   Subsequent COPY/ADD use this as destination
-   RUN commands execute in this directory

#### Multiple WORKDIR Instructions

Multiple WORKDIR instructions change directory progressively.

Code

```
1FROM node:18-alpine2WORKDIR /app3COPY package.json .4RUN npm install5WORKDIR /app/src6COPY src .
```

Execution

Terminal window

```
$ docker build -t app:latest .Successfully built 5c2f8e1a3d7b
```

-   Each WORKDIR is cumulative (relative paths work)
-   /app and /app/src are different working directories

#### WORKDIR with Variables

Use ARG/ENV variables in WORKDIR for flexible directory paths.

Code

```
1FROM ubuntu:20.042ARG APP_HOME=/usr/local/app3ENV APP_HOME=${APP_HOME}4WORKDIR ${APP_HOME}5RUN echo "Application home: $(pwd)"
```

Execution

Terminal window

```
$ docker build --build-arg APP_HOME=/opt/application \               -t app:latest .Step 4/5 : WORKDIR /opt/application ---> Running in 5f8a9c2b3d4e ---> 7c9e4f2b3a5dStep 5/5 : RUN echo "Application home: $(pwd)" ---> Running in 3d7f2a1c8e5bApplication home: /opt/application ---> 8b4f3e2c9a1d
```

-   Variables interpolated at build time

### VOLUME - Data Persistence Points

Define mount points for persistent data

#### Accessibility

Intermediate

#### Best Practices

-   Document what each volume is for
-   Use named volumes in production (not anonymous)
-   Consider backup strategy for volumes
-   Mount volumes explicitly at runtime

#### Common Errors

-   **Volume not persisting:** Use -v flag to mount volume at runtime

#### Keywords

VOLUMEmountpersistentdatastorage

[Learn more](https://docs.docker.com/engine/reference/builder/#volume)

#### VOLUME for Database Storage

VOLUME declares mount points for persistent data that survives container removal.

Code

```
1FROM postgres:15-alpine2VOLUME ["/var/lib/postgresql/data"]3EXPOSE 54324CMD ["postgres"]
```

Execution

Terminal window

```
$ docker build -t postgres-custom:latest .Successfully built 7c9e4f2b3a5d
$ docker run -d postgres-custom:latest4f8e2c7a3b1d
$ docker inspect 4f8e2c7a3b1d --format='{{json .Mounts}}'[{"Type":"volume","Name":"abc123def456","Source":"...","Destination":"/var/lib/postgresql/data"...}]
```

-   Creates anonymous volume if not specified
-   Data persists even if container is deleted
-   Can be mounted by other containers

#### Multiple Volumes

Multiple VOLUME instructions create separate mount points for different data.

Code

```
1FROM mysql:8.02VOLUME ["/var/lib/mysql", "/var/log/mysql"]3EXPOSE 33064ENV MYSQL_ROOT_PASSWORD=secret5CMD ["mysqld"]
```

Execution

Terminal window

```
$ docker run -d mysql-custom:latest5f9d3a2c8e1b
$ docker volume ls | grep mysqlvolumes     abc123def456        (database files)volumes     def789ghi012        (log files)
```

-   Each VOLUME is independent
-   Can mount to named volumes with -v flag

## Exposing Ports

### EXPOSE - Port Declarations

Document which ports the application listens on

#### Accessibility

Beginner

#### Best Practices

-   Document all ports application uses
-   Include both HTTP and debugging ports
-   Remember EXPOSE is informational only
-   Publish ports at runtime with -p flag

#### Common Errors

-   **Port not accessible:** Need -p flag at runtime to publish
-   **Wrong port mapping:** EXPOSE doesn't map, use -p

#### Keywords

EXPOSEportnetworklisteningdocumentation

[Learn more](https://docs.docker.com/engine/reference/builder/#expose)

#### EXPOSE Single Port

EXPOSE documents which ports container listens on (does not actually publish).

Code

```
1FROM nginx:1.25-alpine2EXPOSE 803EXPOSE 4434COPY index.html /usr/share/nginx/html/
```

Execution

Terminal window

```
$ docker build -t web-server:latest .Successfully built 5c2f8e1a3d7b
$ docker inspect web-server:latest --format='{{json .ExposedPorts}}'{"80/tcp":{},"443/tcp":{}}
```

-   EXPOSE is declarative and informational
-   Does NOT actually publish ports
-   Use -p flag at runtime to publish

#### EXPOSE Multiple Ports

Multiple EXPOSE declarations for application and debug ports.

Code

```
1FROM node:18-alpine2EXPOSE 3000 3001 92293COPY . .4RUN npm install5CMD ["npm", "start"]
```

Execution

Terminal window

```
$ docker build -t node-app:latest .Successfully built 7c9e4f2b3a5d
```

-   3000: application port
-   3001: alternative service
-   9229: Node.js debugger port

#### EXPOSE with Port Range

EXPOSE range of ports for applications using dynamic port allocation.

Code

```
1FROM ubuntu:20.042EXPOSE 8000-81003RUN apt-get update && apt-get install -y netcat4CMD ["nc", "-l", "-p", "8000"]
```

Execution

Terminal window

```
$ docker build -t range-app:latest .Successfully built 8b4f3e2c9a1d
$ docker run -p 8000-8100:8000-8100 range-app:latest
```

-   Useful for services with multiple instances
-   Port range notation: START-END

## Entry Points & Commands

### CMD - Default Command

Specify default command to run when container starts

#### Accessibility

Beginner

#### Best Practices

-   Use exec form for main application command
-   Only one CMD in Dockerfile (last one wins)
-   Make commands interruptible (handle SIGTERM)

#### Common Errors

-   **Command not executing:** Need exec form for proper signal handling
-   **Multiple CMDs:** Only last CMD is used

#### Keywords

CMDcommanddefaultstartupexecution

[Learn more](https://docs.docker.com/engine/reference/builder/#cmd)

#### CMD Shell Form

CMD shell form executes command through shell, allowing variable expansion.

Code

```
1FROM python:3.11-slim2COPY . /app3WORKDIR /app4RUN pip install -r requirements.txt5CMD python app.py
```

Execution

Terminal window

```
$ docker build -t python-app:latest .Successfully built 5c2f8e1a3d7b
$ docker run python-app:latestStarting application...App is running
```

-   Runs via /bin/sh -c
-   Can use environment variables
-   Same as typing command in shell

#### CMD Exec Form (JSON)

CMD exec form calls command directly without shell, better for signals.

Code

```
1FROM node:18-alpine2COPY . /app3WORKDIR /app4RUN npm install5CMD ["node", "server.js"]
```

Execution

Terminal window

```
$ docker build -t node-app:latest .Successfully built 7c9e4f2b3a5d
$ docker run node-app:latestServer listening on port 3000
```

-   No shell interpretation
-   Each element becomes separate argument
-   Signals (SIGTERM) properly delivered

#### Override CMD at Runtime

CMD can be overridden by providing command at docker run time.

Code

```
1FROM python:3.11-slim2COPY . /app3WORKDIR /app4RUN pip install -r requirements.txt5CMD ["python", "app.py"]
```

Execution

Terminal window

```
$ docker run python-app:latestRunning app.py...
$ docker run python-app:latest python -m pytest tests/Running tests...test_app.py::test_main PASSED
```

-   Container image has default CMD
-   Passing command at runtime overrides it

### ENTRYPOINT - Entry Point

Configure container as executable with ENTRYPOINT

#### Accessibility

Intermediate

#### Best Practices

-   Use ENTRYPOINT for wrapper scripts
-   Combine with CMD for default arguments
-   Make wrapper scripts handle SIGTERM
-   Consider using tini for signal management

#### Common Errors

-   **Script not executable:** Remember chmod +x in Dockerfile
-   **Signals not working:** Exec form required, not shell form

#### Keywords

ENTRYPOINTentrypointwrapperexecutable

[Learn more](https://docs.docker.com/engine/reference/builder/#entrypoint)

#### ENTRYPOINT with Exec Form

ENTRYPOINT with exec form makes container behave as executable.

Code

```
1FROM python:3.11-slim2COPY entrypoint.sh /usr/local/bin/3RUN chmod +x /usr/local/bin/entrypoint.sh4ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]5CMD ["python", "app.py"]
```

Execution

Terminal window

```
$ docker build -t app:latest .Successfully built 5c2f8e1a3d7b
$ docker run app:latestSetup: Creating database...Starting application...
$ docker run app:latest python -c "import sys; print(sys.version)"Setup: Creating database...3.11.2
```

-   ENTRYPOINT is the wrapper script
-   CMD provides default arguments
-   Useful for setup before main app

#### ENTRYPOINT with Shell Script

ENTRYPOINT executes shell script as main process.

Code

```
1FROM alpine:3.182RUN apk add --no-cache bash curl3COPY health-check.sh /usr/local/bin/health-check4RUN chmod +x /usr/local/bin/health-check5ENTRYPOINT ["bash", "/usr/local/bin/health-check"]
```

Execution

Terminal window

```
$ docker build -t health-checker:latest .Successfully built 7c9e4f2b3a5d
$ docker run health-checker:latest api.example.comChecking health of api.example.comStatus: OK
```

-   Script runs as PID 1 in container
-   Must handle signals properly
-   Can accept arguments from docker run

#### ENTRYPOINT with CMD

ENTRYPOINT + CMD combination where tini manages signals properly.

Code

```
1FROM ubuntu:20.042RUN apt-get update && apt-get install -y curl tini3COPY . /app4WORKDIR /app5ENTRYPOINT ["tini", "--"]6CMD ["./app", "start"]
```

Execution

Terminal window

```
$ docker build -t tini-app:latest .Successfully built 8b4f3e2c9a1d
$ docker run tini-app:latestStarting app with tini init system...
```

-   tini is lightweight init system for containers
-   Forwards signals to the application and reaps zombie processes
-   CMD provides default arguments to ENTRYPOINT

### CMD and ENTRYPOINT Interaction

How CMD and ENTRYPOINT work together

#### Accessibility

Advanced

#### Best Practices

-   Use ENTRYPOINT primarily for wrapper/setup
-   Use CMD for default arguments
-   Make both overridable for flexibility
-   Document expected arguments

#### Common Errors

-   **Cannot override:** Use exec form for both
-   **Arguments not passed:** Arrays must use exec form

#### Keywords

CMDENTRYPOINTinteractionoverridearguments

[Learn more](https://docs.docker.com/engine/reference/builder/#entrypoint)

#### ENTRYPOINT as Wrapper with CMD Arguments

ENTRYPOINT and CMD together allow flexible command execution with wrapper.

Code

```
1FROM python:3.11-slim2COPY wrapper.sh /usr/local/bin/3COPY app.py .4RUN chmod +x /usr/local/bin/wrapper.sh5ENTRYPOINT ["/usr/local/bin/wrapper.sh"]6CMD ["python", "app.py"]
```

Execution

Terminal window

```
$ docker build -t wrapped-app:latest .Successfully built 5c2f8e1a3d7b
$ docker run wrapped-app:latest[WRAPPER] Starting application...App is running
$ docker run wrapped-app:latest python -c "print('test')"[WRAPPER] Starting...test
```

-   CMD becomes arguments to ENTRYPOINT
-   Can override CMD at runtime
-   Wrapper executes first, then CMD

#### Pure ENTRYPOINT (No CMD)

ENTRYPOINT alone without CMD for single-purpose containers.

Code

```
1FROM golang:1.21-alpine2WORKDIR /src3COPY . .4RUN go build -o /app .5ENTRYPOINT ["/app"]
```

Execution

Terminal window

```
$ docker build -t go-app:latest .Successfully built 7c9e4f2b3a5d
$ docker run go-app:latest --helpUsage: app [OPTIONS]
```

-   Container is tool-like (always runs binary)
-   Can still pass arguments
-   No default command

## Build Optimization

### Layer Caching Strategy

Docker layer caching and how it shortens rebuilds

#### Accessibility

Intermediate

#### Best Practices

-   Order instructions by change frequency
-   Keep stable layers early in Dockerfile
-   Use cache mounts for package managers
-   Don't invalidate cache unnecessarily

#### Common Errors

-   **Slow builds:** Dependencies reinstalled every time
-   **Cache not used:** Checking intermediate layers

#### Keywords

layerscachingefficiencybuildspeed

[Learn more](https://docs.docker.com/build/guide/layers/)

#### Inefficient Layer Caching

Copying all files early invalidates cache when any file changes, forcing reinstall.

Code

```
1FROM python:3.11-slim2COPY . .3RUN pip install -r requirements.txt4RUN python app.py
```

Execution

Terminal window

```
$ docker build -t app:latest .Step 1/4 : FROM python:3.11-slim ---> b9ef8f396e26Step 2/4 : COPY . . ---> 7e2f4c8a1d3bStep 3/4 : RUN pip install -r requirements.txt ---> Running in 5f8a9c2b3d4eCollecting requests... ---> 8f4a2c6e9d1b  (3 min 45 sec)Step 4/4 : RUN python app.py ---> Running in 3d7f2a1c8e5b ---> 9c2d4e7f1a3b
# After changing app.py$ docker build -t app:latest .Step 1/4 : FROM python:3.11-slim ---> b9ef8f396e26Step 2/4 : COPY . . ---> (invalidated by changed app.py)Step 3/4 : RUN pip install -r requirements.txt ---> Running in 5f8a9c2b3d4e (runs again!)Collecting requests... ---> 8f4a2c6e9d1b  (3 min 45 sec - WASTED TIME)
```

-   COPY . . invalidates all subsequent layers on any change
-   Python packages reinstalled unnecessarily

#### Optimized Layer Caching

Copy stable files (dependencies) first, changing files last to preserve cache.

Code

```
1FROM python:3.11-slim2COPY requirements.txt .3RUN pip install -r requirements.txt4COPY . .5CMD ["python", "app.py"]
```

Execution

Terminal window

```
$ docker build -t app:latest .Step 1/5 : FROM python:3.11-slim ---> b9ef8f396e26Step 2/5 : COPY requirements.txt . ---> 7e2f4c8a1d3bStep 3/5 : RUN pip install -r requirements.txt ---> Running in 5f8a9c2b3d4eCollecting requests... ---> 8f4a2c6e9d1b  (3 min 45 sec)Step 4/5 : COPY . . ---> 9d5e3f2a8c1bStep 5/5 : CMD ["python", "app.py"] ---> 5c2f8e1a3d7b
# After changing app.py$ docker build -t app:latest .Step 1/5 : FROM python:3.11-slim ---> b9ef8f396e26 (cached)Step 2/5 : COPY requirements.txt . ---> 7e2f4c8a1d3b (cached)Step 3/5 : RUN pip install -r requirements.txt ---> 8f4a2c6e9d1b (cached) - REUSED!Step 4/5 : COPY . . ---> (invalidated by changed app.py)Step 5/5 : CMD ["python", "app.py"] ---> 5c2f8e1a3d7bTotal time: 5 seconds vs 3 min 50 sec
```

-   Stable: requirements.txt, package.json rarely change
-   Volatile: app.py, src files change frequently
-   Skips dependency installation when only source files change

#### Cache with External Mounts

Cache mounts preserve package manager caches across builds.

Code

```
1FROM node:18-alpine2WORKDIR /app3COPY package.json package-lock.json .4RUN --mount=type=cache,target=/root/.npm \5    npm ci --prefer-offline --no-audit6COPY . .7RUN npm run build
```

Execution

Terminal window

```
$ docker build --build-context=. \               --progress=plain \               -t app:latest .Step 1/6 : FROM node:18-alpine ---> 52f389ea4d15Step 4/6 : RUN --mount=type=cache,target=/root/.npm ... ---> Running in 5f8a9c2b3d4eadded 1200 packages in 45s
# Second build$ docker build -t app:latest .Step 4/6 : RUN --mount=type=cache,target=/root/.npm ... ---> Running in 3d7f2a1c8e5bup to date (cached packages reused!)added 1200 packages in 2s
```

-   npm/yarn/pip cache reused
-   Requires BuildKit (DOCKER\_BUILDKIT=1)
-   Reuses downloaded packages instead of fetching them again

### .dockerignore File

Exclude files from build context to speed up builds

#### Accessibility

Beginner

#### Best Practices

-   Always use .dockerignore for production
-   Exclude large directories (node\_modules, venv, .git)
-   Exclude temporary and log files
-   Review before each major change

#### Common Errors

-   **Large build context:** Missing .dockerignore file

#### Keywords

dockerignorecontextexcludeefficiency

[Learn more](https://docs.docker.com/build/building/context/)

#### Basic .dockerignore

.dockerignore filters out unnecessary files before sending to Docker daemon.

Code

```
1# .dockerignore content2.git3.gitignore4.github5node_modules6.env7.env.local8dist9build10__pycache__11*.log12.DS_Store13.vscode14.idea15npm-debug.log16.npm17coverage
```

Execution

Terminal window

```
# Build context without excluded files$ docker build --progress=plain -t app:latest .
# File sizes comparison:# Without .dockerignore: 248MB context sent to daemon# With .dockerignore: 42MB context sent to daemon (82% reduction)
```

-   Reduces the build context size
-   Speeds up build (less data to transfer)
-   Similar to .gitignore syntax

#### Comprehensive .dockerignore

A thorough .dockerignore excludes all unnecessary files from context.

Code

```
1# .dockerignore with multiple patterns2# Version control3.git4.gitignore5.github6.gitlab-ci.yml7
8# Dependencies (often excluded from repo)9node_modules10venv11vendor12.bundle13
14# Development files15.env16.env.*.local17.vscode18.idea19.DS_Store20*.swp21*.swo22*~23
24# Build artifacts25dist26build27*.tmp28coverage29
30# Logs31*.log32logs33
34# Docker35Dockerfile36compose.yml37.dockerignore38
39# CI/CD40.circleci41.travis.yml42Jenkinsfile43
44# Documentation45docs46README.md47
48# Testing49tests50__pycache__51.pytest_cache52.coverage
```

Execution

Terminal window

```
$ docker build -t app:latest .# Context drastically reduced
```

-   Reduces build context from 500MB to 11MB
-   Sends less data to the daemon, so docker build starts faster

### Multi-Stage Build Optimization

Advanced multi-stage patterns for minimal images

#### Accessibility

Advanced

#### Best Practices

-   Use multi-stage for any compiled language
-   Name stages meaningfully (builder, dependencies, etc)
-   Clean up artifacts in builder
-   Copy only necessary files to final stage

#### Common Errors

-   **Builder stage not discarded:** Forgotten FROM in later stage
-   **Large final image:** Copying unnecessary files from builder

#### Keywords

multi-stagebuilderoptimizationsizeartifacts

[Learn more](https://docs.docker.com/build/building/multi-stage/)

#### Multi-Stage Node.js Build

Multi-stage Node.js avoids dev dependencies in final image.

Code

```
1FROM node:18-alpine AS builder2WORKDIR /src3COPY package*.json ./4RUN npm ci --only=production5
6FROM node:18-alpine7WORKDIR /app8COPY --from=builder /src/node_modules ./node_modules9COPY . .10USER nobody11CMD ["node", "server.js"]
```

Execution

Terminal window

```
$ docker build -t node-app:latest .Step 1/8 : FROM node:18-alpine AS builder ---> 52f389ea4d15Step 2/8 : WORKDIR /src ---> Running in 5f8a9c2b3d4eStep 4/8 : RUN npm ci --only=production ---> Running in 3d7f2a1c8e5badded 120 packages in 30s ---> 7c9e4f2b3a5dStep 5/8 : FROM node:18-alpine ---> 52f389ea4d15Step 8/8 : CMD ["node", "server.js"] ---> Running in 2f5c8e1a3d7bSuccessfully built 5f9d3a2c8e1b
$ docker images node-app:latestREPOSITORY   TAG       IMAGE ID       SIZEnode-app     latest    5f9d3a2c8e1b   156MB
```

-   Builder stage includes all dependencies
-   Final stage has only production dependencies
-   Size reduction: devDependencies excluded

#### Multi-Stage Rust Build

Rust compiler (2GB) not in final image, only binary (2-5MB).

Code

```
1FROM rust:1.75 AS builder2WORKDIR /usr/src/app3COPY . .4RUN cargo build --release5
6FROM debian:12-slim7COPY --from=builder /usr/src/app/target/release/app /usr/local/bin/8RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*9USER nobody10ENTRYPOINT ["app"]
```

Execution

Terminal window

```
$ docker build -t rust-app:latest .Step 1/6 : FROM rust:1.75 ---> f7d8c1e2a9b3Step 3/6 : RUN cargo build --release ---> Running in 5f8a9c2b3d4eCompiling app v1.0.0 ---> 7c9e4f2b3a5d (5 min 30 sec)Step 4/6 : FROM debian:12-slim ---> 5f8a9c2b3d4eStep 5/6 : COPY --from=builder /usr/src/app/target/release/app /usr/local/bin/ ---> Running in 3d7f2a1c8e5b ---> 4b2f7d9a1c5eSuccessfully built 4b2f7d9a1c5e
$ docker images rust-app:latestREPOSITORY   TAG       IMAGE ID       SIZErust-app     latest    4b2f7d9a1c5e   45MB
```

-   Builder: rust:1.75 (2GB with compiler)
-   Final: debian:12-slim + binary (45MB total)
-   2GB -> 45MB reduction!

## Metadata & Advanced

### LABEL - Image Metadata

Add metadata labels to Docker images

#### Accessibility

Beginner

#### Best Practices

-   Use OpenContainers standard labels
-   Include maintainer contact information
-   Document version and source repo
-   Use consistent naming conventions

#### Common Errors

-   **Labels not visible:** Remember labels are in metadata, not filesystem

#### Keywords

LABELmetadataversionmaintainerdescription

[Learn more](https://docs.docker.com/config/labels-custom-metadata/)

#### Common Docker Labels

LABEL adds metadata to image for documentation and organization.

Code

```
1FROM python:3.11-slim2LABEL maintainer="devops@example.com"3LABEL version="1.2.3"4LABEL description="Python API application for order processing"5LABEL org.opencontainers.image.source="https://github.com/org/repo"6LABEL org.opencontainers.image.created="2026-02-28T00:00:00Z"
```

Execution

Terminal window

```
$ docker build -t order-api:1.2.3 .Successfully built 7c9e4f2b3a5d
$ docker inspect order-api:1.2.3 --format='{{json .Config.Labels}}'{  "maintainer":"devops@example.com",  "version":"1.2.3",  "description":"Python API...",  "org.opencontainers.image.source":"https://github.com/org/repo",  "org.opencontainers.image.created":"2026-02-28T00:00:00Z"}
```

-   Labels appear in docker inspect output
-   Useful for filtering and organizing images
-   No size impact

#### Multi-line Labels

Multiple labels using backslash continuation for readability.

Code

```
1FROM node:18-alpine2LABEL maintainer="platform-team@company.com" \3      org.opencontainers.image.title="Web Server" \4      org.opencontainers.image.description="Production Node.js web server" \5      org.opencontainers.image.version="2.1.0" \6      org.opencontainers.image.authors="John Doe, Jane Smith"
```

Execution

Terminal window

```
$ docker build -t web-server:2.1.0 .Successfully built 5c2f8e1a3d7b
```

-   Backslash extends single instruction across lines
-   Each label can be queried independently

### USER - Container User

Specify user to run container as instead of root

#### Accessibility

Intermediate

#### Best Practices

-   Always run as non-root in production
-   Create user with no shell (/usr/sbin/nologin)
-   Set appropriate file permissions
-   Use USER before ENTRYPOINT/CMD

#### Common Errors

-   **Permission denied:** Files owned by root, not accessible by app user
-   **Running as root:** Forgot USER instruction

#### Keywords

USERsecuritynon-rootpermissionsuid

[Learn more](https://docs.docker.com/engine/reference/builder/#user)

#### Run as Created User

Create dedicated user and switch to it before running application.

Code

```
1FROM ubuntu:20.042RUN apt-get update && apt-get install -y nodejs3RUN groupadd -r appuser && useradd -r -g appuser appuser4WORKDIR /home/appuser/app5COPY --chown=appuser:appuser . .6USER appuser7CMD ["node", "server.js"]
```

Execution

Terminal window

```
$ docker build -t app:latest .Successfully built 5c2f8e1a3d7b
$ docker run app:latest(running as appuser, not root)
$ docker run app:latest iduid=100(appuser) gid=101(appuser) groups=101(appuser)
```

-   Root user is dangerous in containers
-   appuser has no shell (better security)
-   Works with CMD/ENTRYPOINT

#### Run as Numeric UID

Use numeric UID instead of username for better portability.

Code

```
1FROM python:3.11-slim2RUN groupadd -r app && useradd -r -u 1001 -g app app3WORKDIR /app4COPY --chown=1001:1000 . .5USER 10016CMD ["python", "app.py"]
```

Execution

Terminal window

```
$ docker build -t app:latest .Successfully built 7c9e4f2b3a5d
$ docker run app:latest whoami# Returns: 1001 (numeric ID)
```

-   1001 common for first non-system user
-   More portable across systems

### HEALTHCHECK - Container Health

Define health check to determine if container is running properly

#### Accessibility

Advanced

#### Best Practices

-   Always define HEALTHCHECK in Dockerfile
-   Keep checks lightweight and fast
-   Use appropriate intervals for application
-   Test health checks locally first

#### Common Errors

-   **curl: command not found: Install curl in image:**
-   **Health check never runs:** Remember interval in seconds

#### Keywords

HEALTHCHECKhealthcheckcmdinterval

[Learn more](https://docs.docker.com/engine/reference/builder/#healthcheck)

#### HTTP Endpoint Health Check

Check container health by making HTTP request to application.

Code

```
1FROM nginx:1.25-alpine2HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \3  CMD curl -f http://localhost/ || exit 14COPY index.html /usr/share/nginx/html/
```

Execution

Terminal window

```
$ docker build -t web-server:latest .Successfully built 5c2f8e1a3d7b
$ docker run -d web-server:latest4f8e2c7a3b1d
$ sleep 10 && docker inspect 4f8e2c7a3b1d --format='{{.State.Health.Status}}'healthy
```

-   interval=30s: check every 30 seconds
-   timeout=3s: health check must complete in 3 seconds
-   start-period=5s: grace period before checks start
-   retries=3: 3 failed checks = unhealthy

#### Application-Specific Health Check

Use application-specific tools for health checks.

Code

```
1FROM postgres:15-alpine2HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=5 \3  CMD ["pg_isready", "-U", "postgres"]
```

Execution

Terminal window

```
$ docker build -t postgres-custom:latest .Successfully built 7c9e4f2b3a5d
$ docker run -d postgres-custom:latest5f9d3a2c8e1b
$ docker container lsCONTAINER ID   IMAGE                 STATUS5f9d3a2c8e1b   postgres-custom:latest   Up 15s (healthy)
```

-   pg\_isready for PostgreSQL
-   redis-cli PING for Redis
-   mysql -e "SELECT 1" for MySQL

#### Script-Based Health Check

Custom script for complex health checks.

Code

```
1FROM node:18-alpine2COPY health-check.js /usr/local/bin/3HEALTHCHECK --interval=15s --timeout=5s --retries=3 \4  CMD node /usr/local/bin/health-check.js5COPY . .6CMD ["node", "server.js"]
```

Execution

Terminal window

```
$ docker build -t app:latest .Successfully built 8b4f3e2c9a1d
```

-   Exit code 0 = healthy
-   Exit code 1 = unhealthy
-   Script can hit any endpoint

### Dockerfile Shell Form

Control shell behavior with

#### Accessibility

Advanced

#### Best Practices

-   Use bash for complex scripts on Linux
-   Document shell requirements
-   Remember Alpine has different shells
-   Convert CRLF line endings to LF for scripts copied into Linux images

#### Common Errors

-   **Command not found:** Bash not in alpine:latest
-   **Syntax error:** Mix of sh and bash syntax

#### Keywords

directiveshellsyntaxescape

[Learn more](https://docs.docker.com/engine/reference/builder/#run)

#### Use Bash Instead of /bin/sh

Change default shell from /bin/sh to /bin/bash for bash-specific features.

Code

```
1FROM ubuntu:20.042SHELL ["/bin/bash", "-c"]3RUN apt-get update && apt-get install -y curl4RUN <<EOF5for i in {1..5}; do6  echo "Iteration: $i"7done8EOF
```

Execution

Terminal window

```
$ docker build -t app:latest .Step 1/3 : FROM ubuntu:20.04 ---> 1d622ef08d5eStep 2/3 : SHELL ["/bin/bash", "-c"] ---> Running in 5f8a9c2b3d4e ---> 7c9e4f2b3a5dStep 3/3 : RUN <<EOF ---> Running in 3d7f2a1c8e5bIteration: 1Iteration: 2Iteration: 3Iteration: 4Iteration: 5Successfully built 8b4f3e2c9a1d
```

-   SHELL must appear early in Dockerfile
-   Affects all RUN, CMD, ENTRYPOINT, COPY --chown
-   Alpine Linux doesn't have bash by default

#### Multi-Line RUN with Heredoc

Heredoc syntax for multi-line RUN commands without backslash continuation.

Code

```
1FROM python:3.11-slim2RUN <<EOF3  apt-get update4  apt-get install -y curl git vim5  apt-get clean6  rm -rf /var/lib/apt/lists/*7EOF
```

Execution

Terminal window

```
$ docker build -t app:latest .Step 1/2 : FROM python:3.11-slim ---> b9ef8f396e26Step 2/2 : RUN <<EOF ---> Running in 5f8a9c2b3d4eReading package lists...Processing triggers... ---> 8f4a2c6e9d1bSuccessfully built 8f4a2c6e9d1b
```

-   Available in Docker 23.09+
-   Cleaner than backslash continuation
-   Each line individual command (not chained)

Was this useful?

## Share

[Facebook](https://facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fdockerfile "Share on Facebook")[Twitter](https://twitter.com/intent/tweet/?text=Dockerfile&url=https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fdockerfile "Share on Twitter")[LinkedIn](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fdockerfile&title=Dockerfile&summary=Dockerfile%20reference%20guide%20covering%20FROM%2C%20RUN%2C%20COPY%2C%20EXPOSE%2C%20CMD%2C%20ENTRYPOINT%2C%20environment%20variables%2C%20build%20optimization%2C%20best%20practices%2C%20and%20container%20image%20construction.&source=https://mkabumattar.com "Share on LinkedIn")[WhatsApp](https://wa.me/?text=Dockerfile%20https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fdockerfile "Share on WhatsApp")[Telegram](https://t.me/share/url?url=https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fdockerfile&text=Dockerfile "Share on Telegram")[Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fdockerfile&title=Dockerfile "Share on Reddit")[Hacker News](http://news.ycombinator.com/submitlink?u=https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fdockerfile&t=Dockerfile "Share on Hacker News")[Pinterest](https://pinterest.com/pin/create/button/?url=https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fdockerfile&media=&description=Dockerfile%20reference%20guide%20covering%20FROM%2C%20RUN%2C%20COPY%2C%20EXPOSE%2C%20CMD%2C%20ENTRYPOINT%2C%20environment%20variables%2C%20build%20optimization%2C%20best%20practices%2C%20and%20container%20image%20construction. "Share on Pinterest")[Email](<mailto:?subject=Dockerfile&body=Check out this article: https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fdockerfile>)

## Comments

## You might also enjoy

More posts on similar topics

## [Docker Compose](/cheatsheets/docker-compose)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   Reference

Docker Compose is a tool for defining and running multi-container Docker applications. It uses a YAML file to configure application services, networks, and volumes. This cheatsheet provides a quick re

[read more](/cheatsheets/docker-compose)

## [Docker Swarm](/cheatsheets/docker-swarm)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   DevOps
-   Docker
-   Containers
-   Orchestration
-   Deployment

This cheatsheet is a reference for managing Docker Swarm clusters, services, and stacks. It covers the commands and best practices for scaling, updating, and monitoring your swarm applications.

#Docker Swarm#Orchestration#Containers+4 tags

[read more](/cheatsheets/docker-swarm)

## [kubectl](/cheatsheets/kubectl)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   DevOps
-   Kubernetes
-   CLI
-   Container Orchestration
-   Cloud Native

kubectl is the command-line tool you use to talk to a Kubernetes cluster. Whatever you can do through a dashboard, you can do faster here: deploy apps, inspect resources, stream logs, run commands ins

#Kubectl#Kubernetes#K8s+5 tags

[read more](/cheatsheets/kubectl)

## [Docker](/cheatsheets/docker)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   DevOps
-   Docker
-   Containers
-   Deployment

Docker is a containerization platform that packages applications with their dependencies into isolated, portable environments called containers. It enables developers to build, ship, and run applicati

#Docker#Containers#Images+3 tags

[read more](/cheatsheets/docker)

## [Helm](/cheatsheets/helm)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   DevOps
-   Kubernetes
-   Helm
-   Package Management
-   Cloud Native

Helm is the package manager for Kubernetes that simplifies the deployment, management, and upgrade of applications. It uses charts, which are templated Kubernetes manifests, to build reusable, configu

#Helm#Kubernetes#Package Manager+5 tags

[read more](/cheatsheets/helm)

## [Kubernetes](/cheatsheets/kubernetes)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   DevOps
-   Kubernetes
-   Container Orchestration
-   Cloud Native
-   Cluster Management

Kubernetes is the de facto standard for container orchestration. It automates deployment, scaling, and management of containerized applications across clusters of machines. Configuration is declarativ

#Kubernetes#Kubectl#Containers+5 tags

[read more](/cheatsheets/kubernetes)

6 related posts
