Dockerfile Instructions
Dockerfile Instructions
A Dockerfile is a text file containing instructions that Docker reads sequentially to build an image. Each instruction creates a layer or sets metadata.
Core Instructions
# FROM: Base image (must be first instruction)
FROM node:20-alpine
# LABEL: Metadata
LABEL maintainer="team@company.com" \
version="1.0"
# ENV: Environment variables (persist in image)
ENV NODE_ENV=production
ENV APP_HOME=/app
# ARG: Build-time variables (not in final image)
ARG NODE_VERSION=20
# WORKDIR: Set working directory (creates if missing)
WORKDIR $APP_HOME
# COPY: Copy files from build context to image
COPY package*.json ./
COPY --chown=node:node . .
# RUN: Execute commands during build
RUN npm ci --only=production && \
npm cache clean --force
# EXPOSE: Document listening ports (does not publish)
EXPOSE 3000
# USER: Run as non-root user
USER node
# HEALTHCHECK: Container health monitoring
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# CMD: Default command (can be overridden)
CMD ["node", "server.js"]
# ENTRYPOINT: Fixed entry command
ENTRYPOINT ["node"]
Instruction Priority
Docker executes instructions top-to-bottom. Order matters for caching:
FROM— Choose the smallest base that meets your needsLABEL— Add metadata earlyARG/ENV— Set build-time and runtime variablesWORKDIR— Set the working directoryCOPYdependency files first, thenRUN install, thenCOPY sourceRUN— Minimize layers by chaining commandsEXPOSE— Document portsUSER— Switch to non-rootHEALTHCHECK— Define health monitoringCMD/ENTRYPOINT— Define the startup command
Common Mistakes
# BAD: Installs build tools in final image
RUN apt-get update && apt-get install -y gcc make python3
RUN pip install mypackage
# GOOD: Use multi-stage build instead
FROM python:3.12-slim AS builder
RUN pip install mypackage
FROM python:3.12-slim
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
Multi-Stage Builds
Multi-Stage Builds
Multi-stage builds let you use multiple FROM statements in a single Dockerfile. Each FROM begins a new stage, and you can copy artifacts from one stage to another, leaving build tools behind.
Node.js Example
# Stage 1: Build
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Production
FROM node:20-alpine
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
USER appuser
EXPOSE 3000
CMD ["node", "dist/server.js"]
Result: Final image is ~150MB instead of ~1.2GB with build tools.
Go Example
# Stage 1: Build binary
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server
# Stage 2: Scratch (empty image)
FROM scratch
COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/server"]
Result: Final image is ~10MB — just the compiled binary and CA certificates.
Copying from Named Images
You can copy files from images without defining a stage:
FROM nginx:1.25
COPY --from=node:20 /usr/local/lib/node_modules /usr/local/lib/node_modules
Building Specific Stages
# Build only the builder stage
docker build --target builder -t myapp:build .
# Build the default (last) stage
docker build -t myapp:latest .
Stage Naming
FROM node:20 AS dependencies
# ...
FROM dependencies AS development
# ...
FROM node:20-alpine AS production
# ...
Cache Mounts (BuildKit)
# syntax=docker/dockerfile:1
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
RUN --mount=type=cache,target=/app/.next/cache npm run build
Cache mounts persist between builds, dramatically speeding up npm install and pip install.
Dockerfile Best Practices
Dockerfile Best Practices
1. Order Instructions for Cache Efficiency
# BAD: Code change invalidates npm install cache
COPY . .
RUN npm ci
# GOOD: Dependencies change less often than code
COPY package*.json ./
RUN npm ci
COPY . .
2. Minimize Layer Count
# BAD: Three layers
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean
# GOOD: One layer, cleaned in same RUN
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
3. Use Specific Base Image Tags
# BAD: Unpredictable, may break builds
FROM node
FROM node:latest
# GOOD: Pinned version
FROM node:20.10-alpine
4. Run as Non-Root
# Create user before switching
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
# Set ownership during COPY
COPY --chown=appuser:appgroup . /app
# Switch user
USER appuser
5. Use .dockerignore
Always include a .dockerignore to exclude:
.git
node_modules
*.md
.env
.vscode
6. Combine Related Commands
# BAD
RUN apt-get update
RUN apt-get install -y python3
RUN apt-get install -y pip
RUN apt-get clean
# GOOD
RUN apt-get update && \
apt-get install -y --no-install-recommends \
python3 \
python3-pip && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
7. Use Health Checks
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
8. Clean Up in Same Layer
RUN pip install --no-cache-dir -r requirements.txt
RUN npm ci --only=production && npm cache clean --force
Complete Production Dockerfile
FROM node:20.10-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
RUN npm run build
FROM node:20.10-alpine
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
WORKDIR /app
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
BuildKit Advanced Features
BuildKit Advanced Features
BuildKit is Docker's modern build engine, enabled by default in Docker 23.0+. It provides parallel builds, cache mounts, and advanced syntax.
Enable BuildKit
# Docker 23.0+ (default)
docker build -t myapp .
# Older versions
DOCKER_BUILDKIT=1 docker build -t myapp .
# Or set in daemon.json
{
"features": { "buildkit": true }
}
Syntax Directive
# syntax=docker/dockerfile:1
This line at the top of your Dockerfile enables BuildKit features.
Cache Mounts
# syntax=docker/dockerfile:1
FROM node:20
WORKDIR /app
# Cache npm download cache between builds
RUN --mount=type=cache,target=/root/.npm npm ci
# Cache apt package lists
RUN --mount=type=cache,target=/var/cache/apt \
--mount=type=cache,target=/var/lib/apt/lists \
apt-get update && apt-get install -y curl
# Cache Go build cache
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
go build -o server .
Secret Mounts
# syntax=docker/dockerfile:1
FROM node:20
WORKDIR /app
# Mount secret during build only (not in final image)
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
# Build with secret
docker build --secret id=npmrc,src=.npmrc -t myapp .
SSH Mounts
# syntax=docker/dockerfile:1
FROM node:20
RUN --mount=type=ssh git clone git@github.com:company/private-repo.git
docker build --ssh default -t myapp .
Bind Mounts (Read-Only)
FROM nginx:1.25
RUN --mount=type=bind,source=./nginx.conf,target=/etc/nginx/nginx.conf nginx -t
Checkpoint Builds
# List build cache
docker builder du
# Prune build cache
docker builder prune
# Prune all cache
docker builder prune --all
Common Dockerfile Patterns
Common Dockerfile Patterns
Python Application
FROM python:3.12-slim AS builder
WORKDIR /app
RUN pip install --no-cache-dir poetry
COPY pyproject.toml poetry.lock ./
RUN poetry export -f requirements.txt -o requirements.txt
RUN pip install --no-cache-dir -r requirements.txt --target /deps
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /deps /usr/local/lib/python3.12/site-packages
COPY . .
EXPOSE 8000
CMD ["gunicorn", "main:app", "--bind", "0.0.0.0:8000"]
Java Spring Boot
FROM eclipse-temurin:21-jdk-jammy AS builder
WORKDIR /app
COPY gradle gradle
COPY gradlew build.gradle settings.gradle ./
RUN ./gradlew dependencies --no-daemon
COPY src src
RUN ./gradlew bootJar --no-daemon
FROM eclipse-temurin:21-jre-jammy
RUN addgroup --system spring && adduser --system --ingroup spring spring
USER spring:spring
COPY --from=builder /app/build/libs/*.jar /app/app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
Rust Application
FROM rust:1.75 AS builder
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo build --release
COPY src src
RUN touch src/main.rs && cargo build --release
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/myapp /usr/local/bin/
ENTRYPOINT ["myapp"]
Static Site (Nginx)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.25-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
Dockerfile Snippets
Wait for service:
RUN apt-get update && apt-get install -y --no-install-recommends wait-for-it
Change user mid-Dockerfile:
USER root
RUN apt-get update && apt-get install -y vim
USER node
Copy with specific permissions:
COPY --chmod=755 entrypoint.sh /usr/local/bin/
Platform-specific builds:
FROM --platform=linux/amd64 node:20