---
title: "Dockerfile"
description: "Comprehensive Dockerfile reference guide covering FROM, RUN, COPY, EXPOSE, CMD, ENTRYPOINT, environment variables, build optimization, best practices, and container image construction."
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/cheatsheets/dockerfile
---

# Dockerfile

This cheat sheet covers essential Dockerfile instructions, best practices, and common pitfalls for building efficient and secure Docker images. Whether you're new to Docker or looking to optimize your builds, this guide provides practical examples and explanations to help you master Dockerfile authoring.

## Getting Started

### FROM - Base Image Selection

Understand base image selection and versioning for Dockerfile foundation

**Keywords:** FROM, base, image, version, tag, registry

#### FROM Ubuntu LTS Base Image

```dockerfile
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y curl
```

_exec_
```bash
$ docker build -t myapp:latest .
Step 1/2 : FROM ubuntu:20.04
 ---> 1d622ef08d5e
Step 2/2 : RUN apt-get update && apt-get install -y curl
 ---> Running in 5f8a9c2b3d4e
Collecting packages...
 ---> 7c9e4f2b3a5d
Successfully built 7c9e4f2b3a5d
```

Select specific Ubuntu version as base image. Using specific versions ensures reproducibility and security patches.

- 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

```dockerfile
FROM alpine:3.18
RUN apk add --no-cache python3 py3-pip
```

_exec_
```bash
$ docker build -t lightweight-app:latest .
Step 1/2 : FROM alpine:3.18
 ---> a24bb4aacf12
Step 2/2 : RUN apk add --no-cache python3 py3-pip
 ---> Running in 3f7e2c1a9b5d
 ---> 9c2d4e7f1a3b
Successfully built 9c2d4e7f1a3b

# Final image size: 89MB vs 77MB (Ubuntu) - but lighter for specific apps
```

Alpine-based images are minimal and lightweight, ideal for production microservices where size matters.

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

#### FROM Official Python Image

```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
```

_exec_
```bash
$ docker build -t python-app:latest .
Step 1/4 : FROM python:3.11-slim
 ---> b9ef8f396e26
Step 2/4 : WORKDIR /app
 ---> Running in 3a5f8d2c1e4b
 ---> 6e2a7f9d4c1b
Step 3/4 : COPY requirements.txt .
 ---> c9d4e2f7a1b5
Step 4/4 : RUN pip install -r requirements.txt
 ---> Running in 7f2e4c9a3d1b
Successfully built 8d3f5e2a7c4b
```

Language-specific official images come with runtime pre-installed, simplifying Dockerfiles.

- 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)

**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:**

- **undefined**: undefined
- **undefined**: undefined

### Image Naming and Tags

Understand Docker image naming conventions and tagging strategies

**Keywords:** tag, naming, registry, version, semantic

#### Tag Image with Domain Registry

```dockerfile
FROM nginx:1.25
COPY index.html /usr/share/nginx/html/
```

_exec_
```bash
$ 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.0
The push refers to repository [gcr.io/my-project/web-server]
8c3f7e2d4a1b: Pushed
1.0.0: digest: sha256:abc123...
```

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

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

#### Multiple Tags for Single Image

```dockerfile
FROM node:18-alpine
COPY app.js .
CMD ["node", "app.js"]
```

_exec_
```bash
$ docker build -t myapp:1.2.3 -t myapp:latest -t myapp:stable .
Successfully built 5f9d3a2c8e1b

$ docker images | grep myapp
myapp         1.2.3        5f9d3a2c8e1b
myapp         latest       5f9d3a2c8e1b
myapp         stable       5f9d3a2c8e1b
```

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

- 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

**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:**

- **undefined**: undefined

### Multi-Stage Dockerfile Overview

Introduction to multi-stage builds for optimized image sizes

**Keywords:** multi-stage, builder, production, size, optimization

#### Single Stage vs Multi-Stage Comparison

```dockerfile
# Inefficient single stage
FROM golang:1.21
WORKDIR /src
COPY . .
RUN go build -o /usr/local/bin/app .
ENTRYPOINT ["app"]
```

_exec_
```bash
$ docker build -t app:single .
Successfully built a2b8f4d7c3e1

$ docker images app:single
REPOSITORY   TAG       IMAGE ID       SIZE
app          single    a2b8f4d7c3e1   1.3GB
```

Single stage includes build tools, resulting in large images with unnecessary dependencies.

- 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

```dockerfile
FROM golang:1.21 AS builder
WORKDIR /src
COPY . .
RUN go build -o /usr/local/bin/app .

FROM alpine:3.18
RUN apk add --no-cache ca-certificates
COPY --from=builder /usr/local/bin/app /usr/local/bin/app
ENTRYPOINT ["app"]
```

_exec_
```bash
$ docker build -t app:multi .
Step 1/5 : FROM golang:1.21 AS builder
 ---> 2d8c4f1a9e3b
Step 3/5 : RUN go build -o /usr/local/bin/app .
 ---> Running in 4c5f8a2d7e1b
 ---> 7e3f4c6b1a9d
Step 5/8 : FROM alpine:3.18
 ---> a24bb4aacf12
Step 6/8 : COPY --from=builder /usr/local/bin/app /usr/local/bin/app
 ---> c8d2f5a1e7b4
Successfully built 8f2a4e9d3c1b

$ docker images
app          multi      8f2a4e9d3c1b   15MB
```

Multi-stage builds separate compilation from runtime, drastically reducing final image size.

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

**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:**

- **undefined**: undefined

## Copying & Adding Files

### COPY - Basic File Operations

Copy files and directories from build context to container

**Keywords:** COPY, files, directories, context, source, destination

#### Copy Single File

```dockerfile
FROM nginx:1.25-alpine
COPY index.html /usr/share/nginx/html/
```

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

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

- 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

```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY *.py ./
COPY requirements.txt .
```

_exec_
```bash
$ docker build -t python-app:latest .
Step 1/4 : FROM python:3.11-slim
 ---> b9ef8f396e26
Step 2/4 : WORKDIR /app
 ---> Running in a1b2c3d4e5f6
 ---> 7e2f4c8a1d3b
Step 3/4 : COPY *.py ./
 ---> 3c5f9a2e7d1b
Step 4/4 : COPY requirements.txt .
 ---> 8f4a2c6e9d1b
Successfully built 8f4a2c6e9d1b
```

Use wildcards to copy multiple files matching pattern into container.

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

#### Copy Directory Recursively

```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY src ./src
COPY public ./public
```

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

Copy entire directories with all subdirectories and files.

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

**Best practices:**

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

**Common errors:**

- **undefined**: undefined
- **undefined**: undefined

### COPY with Ownership

Copy files and set ownership to non-root user

**Keywords:** COPY, CHOWN, user, ownership, permissions

#### Copy with User Ownership

```dockerfile
FROM node:18-alpine
RUN addgroup -S nodejs && adduser -S nodejs -G nodejs
WORKDIR /app
COPY --chown=nodejs:nodejs . .
USER nodejs
CMD ["node", "index.js"]
```

_exec_
```bash
$ docker build -t secure-app:latest .
Successfully built 7c3e5f1a2d8b

$ docker run secure-app:latest
# App runs as nodejs user, not root
```

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

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

#### Copy with Numeric User ID

```dockerfile
FROM python:3.11-slim
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /home/appuser/app
COPY --chown=1000:1000 . .
USER appuser
ENTRYPOINT ["python", "main.py"]
```

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

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

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

**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:**

- **undefined**: undefined

### ADD - Advanced File Operations

Add files, directories, or remote URLs with automatic extraction

**Keywords:** ADD, files, URL, tar, extraction, automatic

#### ADD from Remote URL

```dockerfile
FROM ubuntu:20.04
ADD https://example.com/app.tar.gz /tmp/
WORKDIR /app
RUN tar -xzf /tmp/app.tar.gz && rm /tmp/app.tar.gz
```

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

Download files from URLs and automatically extract tar archives.

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

#### ADD Automatically Extracts TAR

```dockerfile
FROM alpine:3.18
ADD https://releases.example.com/v1.2.3/app.tar.gz /opt/
WORKDIR /opt
RUN ls -la
```

_exec_
```bash
$ docker build -t app:latest .
Successfully built 3f9e2d5a1c7b

Step 4/4 : RUN ls -la
 ---> Running in 5d8c3f2a1e9b
total 48
drwxr-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
```

TAR archive automatically extracted to destination directory.

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

**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:**

- **undefined**: undefined
- **undefined**: undefined

## Running Commands

### RUN - Basic Command Execution

Execute commands during image build

**Keywords:** RUN, command, shell, build, dependencies

#### RUN Single Command

```dockerfile
FROM ubuntu:20.04
RUN apt-get update
RUN apt-get install -y curl
```

_exec_
```bash
$ docker build -t app:latest .
Step 1/3 : FROM ubuntu:20.04
 ---> 1d622ef08d5e
Step 2/3 : RUN apt-get update
 ---> Running in 5f8a9c2b3d4e
Reading package lists... Done
 ---> 7c9e4f2b3a5d
Step 3/3 : RUN apt-get install -y curl
 ---> Running in 3d7f2a1c8e5b
Setting up curl (7.68.0-1ubuntu4)...
 ---> 8b4f3e2c9a1d
Successfully built 8b4f3e2c9a1d
```

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

- 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

```dockerfile
FROM ubuntu:20.04
RUN apt-get update && \
    apt-get install -y curl git vim && \
    apt-get clean
```

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

$ docker history app:latest
IMAGE              CREATED        SIZE
9d5e3f2a8c1b       30 seconds ago 187MB
```

Chain multiple commands with && to create single layer and improve image efficiency.

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

#### RUN Install Multiple Packages

```dockerfile
FROM debian:12-slim
RUN apt-get update && apt-get install -y \
    build-essential \
    git \
    curl \
    wget \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*
```

_exec_
```bash
$ docker build -t build-tools:latest .
Step 1/2 : FROM debian:12-slim
 ---> 7b2f8e3a9c1d
Step 2/2 : RUN apt-get update && apt-get install -y ...
 ---> Running in 4f9d2c5a1e8b
Processing triggers...
 ---> 8c3f7e2d4a9b
Successfully built 8c3f7e2d4a9b
```

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

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

**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:**

- **undefined**: undefined
- **undefined**: undefined

### RUN - Exec Form vs Shell Form

Understand differences between exec form and shell form in RUN

**Keywords:** RUN, exec, shell, form, JSON, signal

#### Shell Form (Default)

```dockerfile
FROM alpine:3.18
RUN echo "Building application"
RUN echo $PATH
```

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

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

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

#### Exec Form (JSON Array)

```dockerfile
FROM alpine:3.18
RUN ["apk", "add", "--no-cache", "curl"]
RUN ["echo", "Building"]
```

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

Exec form calls command directly without shell interpretation.

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

#### Shell Parameter in RUN

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

_exec_
```bash
$ 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 4f9d2c5a1e8b
Number: 1
Number: 2
Number: 3
 ---> 8f4a2c6e9d1b
```

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

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

**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:**

- **undefined**: undefined
- **undefined**: undefined

## Environment & Variables

### ENV - Environment Variables

Set environment variables that persist in running containers

**Keywords:** ENV, environment, variables, persistence, containers

#### Set Single Environment Variable

```dockerfile
FROM python:3.11-slim
ENV PYTHONUNBUFFERED=1
ENV APP_ENV=production
RUN echo "Environment: $APP_ENV"
```

_exec_
```bash
$ docker build -t app:latest .
Step 1/4 : FROM python:3.11-slim
 ---> b9ef8f396e26
Step 2/4 : ENV PYTHONUNBUFFERED=1
 ---> Running in a1b2c3d4e5f6
 ---> 7e2f4c8a1d3b
Step 3/4 : ENV APP_ENV=production
 ---> Running in 5f8a9c2b3d4e
 ---> 8f4a2c6e9d1b
Step 4/4 : RUN echo "Environment: $APP_ENV"
 ---> Running in 3d7f2a1c8e5b
Environment: production
 ---> 9c2d4e7f1a3b
Successfully built 9c2d4e7f1a3b
```

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

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

#### Multiple Variables and Defaults

```dockerfile
FROM node:18-alpine
ENV NODE_ENV=production \
    PORT=3000 \
    LOG_LEVEL=info \
    DATABASE_URL=postgresql://localhost/db
RUN echo "Running in $NODE_ENV mode on port $PORT"
```

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

$ docker run app:latest env
NODE_ENV=production
PORT=3000
LOG_LEVEL=info
DATABASE_URL=postgresql://localhost/db
```

Set multiple environment variables with backslash continuation.

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

#### Variable Substitution in Dockerfile

```dockerfile
FROM ubuntu:20.04
ENV APP_VERSION=2.5.1
ENV APP_PATH=/opt/app-${APP_VERSION}
RUN mkdir -p $APP_PATH && echo "App path: $APP_PATH"
LABEL version="${APP_VERSION}"
```

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

Use variables in subsequent ENV declarations and RUN instructions.

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

**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:**

- **undefined**: undefined
- **undefined**: undefined

### ARG - Build Arguments

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

**Keywords:** ARG, arguments, build-time, variable, substitution

#### ARG for Build-Time Configuration

```dockerfile
FROM python:3.11-slim
ARG BUILD_DATE
ARG VCS_REF
ENV BUILD_DATE=${BUILD_DATE}
ENV VCS_REF=${VCS_REF}
RUN echo "Built on: $BUILD_DATE from commit: $VCS_REF"
```

_exec_
```bash
$ 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
 ---> b9ef8f396e26
Step 5/5 : RUN echo "Built on: $BUILD_DATE from commit: $VCS_REF"
 ---> Running in 5f8a9c2b3d4e
Built on: 2026-02-28T12:00:00Z from commit: abc123xyz
 ---> 8f4a2c6e9d1b
Successfully built 8f4a2c6e9d1b
```

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

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

#### ARG with Default Values

```dockerfile
FROM node:18-alpine
ARG NODE_ENV=development
ARG VERSION=1.0.0
ARG PORT=3000
ENV NODE_ENV=${NODE_ENV} \
    VERSION=${VERSION} \
    PORT=${PORT}
RUN echo "Building v$VERSION for $NODE_ENV on port $PORT"
```

_exec_
```bash
$ docker build -t app:latest .
Step 5/5 : RUN echo "Building v$VERSION for $NODE_ENV on port $PORT"
 ---> Running in 5f8a9c2b3d4e
Building 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 3d7f2a1c8e5b
Building v2.0.0 for production on port 3000
 ---> 8b4f3e2c9a1d
```

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

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

**Best practices:**

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

**Common errors:**

- **undefined**: undefined
- **undefined**: undefined

### Variable Substitution and Defaults

Advanced variable substitution patterns in Dockerfile

**Keywords:** variables, substitution, defaults, expansion, patterns

#### Variable Expansion with Defaults

```dockerfile
ARG UBUNTU_VERSION=20.04
FROM ubuntu:${UBUNTU_VERSION}
ARG APP_PATH=/opt/app
ARG APP_USER=appuser
RUN mkdir -p ${APP_PATH} && \
    useradd -r -m -d ${APP_PATH} ${APP_USER}
```

_exec_
```bash
$ 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
 ---> 6790c6674aaa
Successfully built 4b2f7d9a1c5e
```

Use variables in FROM and subsequent instructions for flexible builds.

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

#### Build-Stage Variables

```dockerfile
ARG GOLANG_VERSION=1.21
FROM golang:${GOLANG_VERSION} AS builder
ARG VERSION=1.0.0
WORKDIR /src
RUN echo "Building with Go $GOLANG_VERSION, version $VERSION"

FROM alpine:3.18
ARG VERSION=1.0.0
COPY --from=builder /src /app
ENV VERSION=${VERSION}
```

_exec_
```bash
$ 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 5f8a9c2b3d4e
Step 2/6 : FROM golang:1.22
 ---> 2d8c4f1a9e3b
Step 3/6 : ARG VERSION=1.0.0
 ---> Running in 3d7f2a1c8e5b
Successfully built 5c2f8e1a3d7b
```

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

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

**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:**

- **undefined**: undefined
- **undefined**: undefined

## Working Directory & Volumes

### WORKDIR - Working Directory

Set working directory for subsequent instructions

**Keywords:** WORKDIR, directory, path, cd, working

#### WORKDIR Basic Usage

```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN ls -la
```

_exec_
```bash
$ docker build -t app:latest .
Step 1/4 : FROM python:3.11-slim
 ---> b9ef8f396e26
Step 2/4 : WORKDIR /app
 ---> Running in a1b2c3d4e5f6
 ---> 7e2f4c8a1d3b
Step 3/4 : COPY . .
 ---> 3c5f9a2e7d1b
Step 4/4 : RUN ls -la
 ---> Running in 5f8a9c2b3d4e
total 48
drwxr-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
 ---> 8f4a2c6e9d1b
Successfully built 8f4a2c6e9d1b
```

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

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

#### Multiple WORKDIR Instructions

```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package.json .
RUN npm install
WORKDIR /app/src
COPY src .
```

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

Multiple WORKDIR instructions change directory progressively.

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

#### WORKDIR with Variables

```dockerfile
FROM ubuntu:20.04
ARG APP_HOME=/usr/local/app
ENV APP_HOME=${APP_HOME}
WORKDIR ${APP_HOME}
RUN echo "Application home: $(pwd)"
```

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

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

- Variables interpolated at build time

**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:**

- **undefined**: undefined

### VOLUME - Data Persistence Points

Define mount points for persistent data

**Keywords:** VOLUME, mount, persistent, data, storage

#### VOLUME for Database Storage

```dockerfile
FROM postgres:15-alpine
VOLUME ["/var/lib/postgresql/data"]
EXPOSE 5432
CMD ["postgres"]
```

_exec_
```bash
$ docker build -t postgres-custom:latest .
Successfully built 7c9e4f2b3a5d

$ docker run -d postgres-custom:latest
4f8e2c7a3b1d

$ docker inspect 4f8e2c7a3b1d --format='{{json .Mounts}}'
[{"Type":"volume","Name":"abc123def456","Source":"...","Destination":"/var/lib/postgresql/data"...}]
```

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

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

#### Multiple Volumes

```dockerfile
FROM mysql:8.0
VOLUME ["/var/lib/mysql", "/var/log/mysql"]
EXPOSE 3306
ENV MYSQL_ROOT_PASSWORD=secret
CMD ["mysqld"]
```

_exec_
```bash
$ docker run -d mysql-custom:latest
5f9d3a2c8e1b

$ docker volume ls | grep mysql
volumes     abc123def456        (database files)
volumes     def789ghi012        (log files)
```

Multiple VOLUME instructions create separate mount points for different data.

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

**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:**

- **undefined**: undefined

## Exposing Ports

### EXPOSE - Port Declarations

Document which ports the application listens on

**Keywords:** EXPOSE, port, network, listening, documentation

#### EXPOSE Single Port

```dockerfile
FROM nginx:1.25-alpine
EXPOSE 80
EXPOSE 443
COPY index.html /usr/share/nginx/html/
```

_exec_
```bash
$ docker build -t web-server:latest .
Successfully built 5c2f8e1a3d7b

$ docker inspect web-server:latest --format='{{json .ExposedPorts}}'
{"80/tcp":{},"443/tcp":{}}
```

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

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

#### EXPOSE Multiple Ports

```dockerfile
FROM node:18-alpine
EXPOSE 3000 3001 9229
COPY . .
RUN npm install
CMD ["npm", "start"]
```

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

Multiple EXPOSE declarations for application and debug ports.

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

#### EXPOSE with Port Range

```dockerfile
FROM ubuntu:20.04
EXPOSE 8000-8100
RUN apt-get update && apt-get install -y netcat
CMD ["nc", "-l", "-p", "8000"]
```

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

$ docker run -p 8000-8100:8000-8100 range-app:latest
```

EXPOSE range of ports for applications using dynamic port allocation.

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

**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:**

- **undefined**: undefined
- **undefined**: undefined

## Entry Points & Commands

### CMD - Default Command

Specify default command to run when container starts

**Keywords:** CMD, command, default, startup, execution

#### CMD Shell Form

```dockerfile
FROM python:3.11-slim
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
CMD python app.py
```

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

$ docker run python-app:latest
Starting application...
App is running
```

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

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

#### CMD Exec Form (JSON)

```dockerfile
FROM node:18-alpine
COPY . /app
WORKDIR /app
RUN npm install
CMD ["node", "server.js"]
```

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

$ docker run node-app:latest
Server listening on port 3000
```

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

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

#### Override CMD at Runtime

```dockerfile
FROM python:3.11-slim
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
```

_exec_
```bash
$ docker run python-app:latest
Running app.py...

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

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

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

**Best practices:**

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

**Common errors:**

- **undefined**: undefined
- **undefined**: undefined

### ENTRYPOINT - Entry Point

Configure container as executable with ENTRYPOINT

**Keywords:** ENTRYPOINT, entry, point, wrapper, executable

#### ENTRYPOINT with Exec Form

```dockerfile
FROM python:3.11-slim
COPY entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["python", "app.py"]
```

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

$ docker run app:latest
Setup: Creating database...
Starting application...

$ docker run app:latest python -c "import sys; print(sys.version)"
Setup: Creating database...
3.11.2
```

ENTRYPOINT with exec form makes container behave as executable.

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

#### ENTRYPOINT with Shell Script

```dockerfile
FROM alpine:3.18
RUN apk add --no-cache bash curl
COPY health-check.sh /usr/local/bin/health-check
RUN chmod +x /usr/local/bin/health-check
ENTRYPOINT ["bash", "/usr/local/bin/health-check"]
```

_exec_
```bash
$ docker build -t health-checker:latest .
Successfully built 7c9e4f2b3a5d

$ docker run health-checker:latest api.example.com
Checking health of api.example.com
Status: OK
```

ENTRYPOINT executes shell script as main process.

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

#### ENTRYPOINT with CMD

```dockerfile
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y curl tini
COPY . /app
WORKDIR /app
ENTRYPOINT ["tini", "--"]
CMD ["./app", "start"]
```

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

$ docker run tini-app:latest
Starting app with tini init system...
```

ENTRYPOINT + CMD combination where tini manages signals properly.

- tini is lightweight init system for containers
- Ensures proper signal handling
- CMD provides default arguments to ENTRYPOINT

**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:**

- **undefined**: undefined
- **undefined**: undefined

### CMD and ENTRYPOINT Interaction

Understand how CMD and ENTRYPOINT work together

**Keywords:** CMD, ENTRYPOINT, interaction, override, arguments

#### ENTRYPOINT as Wrapper with CMD Arguments

```dockerfile
FROM python:3.11-slim
COPY wrapper.sh /usr/local/bin/
COPY app.py .
RUN chmod +x /usr/local/bin/wrapper.sh
ENTRYPOINT ["/usr/local/bin/wrapper.sh"]
CMD ["python", "app.py"]
```

_exec_
```bash
$ 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
```

ENTRYPOINT and CMD together allow flexible command execution with wrapper.

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

#### Pure ENTRYPOINT (No CMD)

```dockerfile
FROM golang:1.21-alpine
WORKDIR /src
COPY . .
RUN go build -o /app .
ENTRYPOINT ["/app"]
```

_exec_
```bash
$ docker build -t go-app:latest .
Successfully built 7c9e4f2b3a5d

$ docker run go-app:latest --help
Usage: app [OPTIONS]
```

ENTRYPOINT alone without CMD for single-purpose containers.

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

**Best practices:**

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

**Common errors:**

- **undefined**: undefined
- **undefined**: undefined

## Build Optimization

### Layer Caching Strategy

Understand Docker layer caching to speed up builds

**Keywords:** layers, caching, efficiency, build, speed

#### Inefficient Layer Caching

```dockerfile
FROM python:3.11-slim
COPY . .
RUN pip install -r requirements.txt
RUN python app.py
```

_exec_
```bash
$ docker build -t app:latest .
Step 1/4 : FROM python:3.11-slim
 ---> b9ef8f396e26
Step 2/4 : COPY . .
 ---> 7e2f4c8a1d3b
Step 3/4 : RUN pip install -r requirements.txt
 ---> Running in 5f8a9c2b3d4e
Collecting 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
 ---> b9ef8f396e26
Step 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)
```

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

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

#### Optimized Layer Caching

```dockerfile
FROM python:3.11-slim
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]
```

_exec_
```bash
$ docker build -t app:latest .
Step 1/5 : FROM python:3.11-slim
 ---> b9ef8f396e26
Step 2/5 : COPY requirements.txt .
 ---> 7e2f4c8a1d3b
Step 3/5 : RUN pip install -r requirements.txt
 ---> Running in 5f8a9c2b3d4e
Collecting requests...
 ---> 8f4a2c6e9d1b  (3 min 45 sec)
Step 4/5 : COPY . .
 ---> 9d5e3f2a8c1b
Step 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"]
 ---> 5c2f8e1a3d7b
Total time: 5 seconds vs 3 min 50 sec
```

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

- Stable: requirements.txt, package.json rarely change
- Volatile: app.py, src files change frequently
- Saves significant build time

#### Cache with External Mounts

```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package.json package-lock.json .
RUN --mount=type=cache,target=/root/.npm \
    npm ci --prefer-offline --no-audit
COPY . .
RUN npm run build
```

_exec_
```bash
$ docker build --build-context=. \
               --progress=plain \
               -t app:latest .
Step 1/6 : FROM node:18-alpine
 ---> 52f389ea4d15
Step 4/6 : RUN --mount=type=cache,target=/root/.npm ...
 ---> Running in 5f8a9c2b3d4e
added 1200 packages in 45s

# Second build
$ docker build -t app:latest .
Step 4/6 : RUN --mount=type=cache,target=/root/.npm ...
 ---> Running in 3d7f2a1c8e5b
up to date (cached packages reused!)
added 1200 packages in 2s
```

Cache mounts preserve package manager caches across builds.

- npm/yarn/pip cache reused
- Requires BuildKit (DOCKER_BUILDKIT=1)
- Dramatically speeds up dependency installation

**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:**

- **undefined**: undefined
- **undefined**: undefined

### .dockerignore File

Exclude files from build context to speed up builds

**Keywords:** dockerignore, context, exclude, efficiency

#### Basic .dockerignore

```dockerfile
# .dockerignore content
.git
.gitignore
.github
node_modules
.env
.env.local
dist
build
__pycache__
*.log
.DS_Store
.vscode
.idea
npm-debug.log
.npm
coverage
```

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

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

#### Comprehensive .dockerignore

```dockerfile
# .dockerignore with multiple patterns
# Version control
.git
.gitignore
.github
.gitlab-ci.yml

# Dependencies (often excluded from repo)
node_modules
venv
vendor
.bundle

# Development files
.env
.env.*.local
.vscode
.idea
.DS_Store
*.swp
*.swo
*~

# Build artifacts
dist
build
*.tmp
coverage

# Logs
*.log
logs

# Docker
Dockerfile
compose.yml
.dockerignore

# CI/CD
.circleci
.travis.yml
Jenkinsfile

# Documentation
docs
README.md

# Testing
tests
__pycache__
.pytest_cache
.coverage
```

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

Comprehensive .dockerignore excludes all unnecessary files from context.

- Reduces build context from 500MB to 11MB
- Speeds up docker build command significantly

**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:**

- **undefined**: undefined

### Multi-Stage Build Optimization

Advanced multi-stage patterns for minimal images

**Keywords:** multi-stage, builder, optimization, size, artifacts

#### Multi-Stage Node.js Build

```dockerfile
FROM node:18-alpine AS builder
WORKDIR /src
COPY package*.json ./
RUN npm ci --only=production

FROM node:18-alpine
WORKDIR /app
COPY --from=builder /src/node_modules ./node_modules
COPY . .
USER nobody
CMD ["node", "server.js"]
```

_exec_
```bash
$ docker build -t node-app:latest .
Step 1/8 : FROM node:18-alpine AS builder
 ---> 52f389ea4d15
Step 2/8 : WORKDIR /src
 ---> Running in 5f8a9c2b3d4e
Step 4/8 : RUN npm ci --only=production
 ---> Running in 3d7f2a1c8e5b
added 120 packages in 30s
 ---> 7c9e4f2b3a5d
Step 5/8 : FROM node:18-alpine
 ---> 52f389ea4d15
Step 8/8 : CMD ["node", "server.js"]
 ---> Running in 2f5c8e1a3d7b
Successfully built 5f9d3a2c8e1b

$ docker images node-app:latest
REPOSITORY   TAG       IMAGE ID       SIZE
node-app     latest    5f9d3a2c8e1b   156MB
```

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

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

#### Multi-Stage Rust Build

```dockerfile
FROM rust:1.75 AS builder
WORKDIR /usr/src/app
COPY . .
RUN cargo build --release

FROM debian:12-slim
COPY --from=builder /usr/src/app/target/release/app /usr/local/bin/
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
USER nobody
ENTRYPOINT ["app"]
```

_exec_
```bash
$ docker build -t rust-app:latest .
Step 1/6 : FROM rust:1.75
 ---> f7d8c1e2a9b3
Step 3/6 : RUN cargo build --release
 ---> Running in 5f8a9c2b3d4e
Compiling app v1.0.0
 ---> 7c9e4f2b3a5d (5 min 30 sec)
Step 4/6 : FROM debian:12-slim
 ---> 5f8a9c2b3d4e
Step 5/6 : COPY --from=builder /usr/src/app/target/release/app /usr/local/bin/
 ---> Running in 3d7f2a1c8e5b
 ---> 4b2f7d9a1c5e
Successfully built 4b2f7d9a1c5e

$ docker images rust-app:latest
REPOSITORY   TAG       IMAGE ID       SIZE
rust-app     latest    4b2f7d9a1c5e   45MB
```

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

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

**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:**

- **undefined**: undefined
- **undefined**: undefined

## Metadata & Advanced

### LABEL - Image Metadata

Add metadata labels to Docker images

**Keywords:** LABEL, metadata, version, maintainer, description

#### Common Docker Labels

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

_exec_
```bash
$ 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"
}
```

LABEL adds metadata to image for documentation and organization.

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

#### Multi-line Labels

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

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

Multiple labels using backslash continuation for readability.

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

**Best practices:**

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

**Common errors:**

- **undefined**: undefined

### USER - Container User

Specify user to run container as instead of root

**Keywords:** USER, security, non-root, permissions, uid

#### Run as Created User

```dockerfile
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y nodejs
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /home/appuser/app
COPY --chown=appuser:appuser . .
USER appuser
CMD ["node", "server.js"]
```

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

$ docker run app:latest
(running as appuser, not root)

$ docker run app:latest id
uid=100(appuser) gid=101(appuser) groups=101(appuser)
```

Create dedicated user and switch to it before running application.

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

#### Run as Numeric UID

```dockerfile
FROM python:3.11-slim
RUN groupadd -r app && useradd -r -u 1001 -g app app
WORKDIR /app
COPY --chown=1001:1000 . .
USER 1001
CMD ["python", "app.py"]
```

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

$ docker run app:latest whoami
# Returns: 1001 (numeric ID)
```

Use numeric UID instead of username for better portability.

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

**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:**

- **undefined**: undefined
- **undefined**: undefined

### HEALTHCHECK - Container Health

Define health check to determine if container is running properly

**Keywords:** HEALTHCHECK, health, check, cmd, interval

#### HTTP Endpoint Health Check

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

_exec_
```bash
$ docker build -t web-server:latest .
Successfully built 5c2f8e1a3d7b

$ docker run -d web-server:latest
4f8e2c7a3b1d

$ sleep 10 && docker inspect 4f8e2c7a3b1d --format='{{.State.Health.Status}}'
healthy
```

Check container health by making HTTP request to application.

- 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

```dockerfile
FROM postgres:15-alpine
HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=5 \
  CMD ["pg_isready", "-U", "postgres"]
```

_exec_
```bash
$ docker build -t postgres-custom:latest .
Successfully built 7c9e4f2b3a5d

$ docker run -d postgres-custom:latest
5f9d3a2c8e1b

$ docker container ls
CONTAINER ID   IMAGE                 STATUS
5f9d3a2c8e1b   postgres-custom:latest   Up 15s (healthy)
```

Use application-specific tools for health checks.

- pg_isready for PostgreSQL
- redis-cli PING for Redis
- mysql -e "SELECT 1" for MySQL

#### Script-Based Health Check

```dockerfile
FROM node:18-alpine
COPY health-check.js /usr/local/bin/
HEALTHCHECK --interval=15s --timeout=5s --retries=3 \
  CMD node /usr/local/bin/health-check.js
COPY . .
CMD ["node", "server.js"]
```

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

Custom script for complex health checks.

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

**Best practices:**

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

**Common errors:**

- **undefined**: undefined
- **undefined**: undefined

### Dockerfile Shell Form

Control shell behavior with

**Keywords:** directive, shell, syntax, escape

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

```dockerfile
FROM ubuntu:20.04
SHELL ["/bin/bash", "-c"]
RUN apt-get update && apt-get install -y curl
RUN <<EOF
for i in {1..5}; do
  echo "Iteration: $i"
done
EOF
```

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

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

- 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

```dockerfile
FROM python:3.11-slim
RUN <<EOF
  apt-get update
  apt-get install -y curl git vim
  apt-get clean
  rm -rf /var/lib/apt/lists/*
EOF
```

_exec_
```bash
$ docker build -t app:latest .
Step 1/2 : FROM python:3.11-slim
 ---> b9ef8f396e26
Step 2/2 : RUN <<EOF
 ---> Running in 5f8a9c2b3d4e
Reading package lists...
Processing triggers...
 ---> 8f4a2c6e9d1b
Successfully built 8f4a2c6e9d1b
```

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

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

**Best practices:**

- Use bash for complex scripts on Linux
- Document shell requirements
- Remember Alpine has different shells
- Use cmarctrlc for Windows line endings

**Common errors:**

- **undefined**: undefined
- **undefined**: undefined
