Dockerfile Instructions
This lesson covers all major Dockerfile instructions, when to use them, and best practices for each.
Complete Instruction Reference
| Instruction | Purpose |
|---|---|
| FROM | Set base image |
| WORKDIR | Set working directory |
| COPY | Copy files from build context |
| ADD | Copy files with extra features |
| RUN | Execute build commands |
| CMD | Default container command |
| ENTRYPOINT | Configure container executable |
| ENV | Set environment variables |
| ARG | Define build arguments |
| EXPOSE | Document container ports |
| VOLUME | Create mount points |
| USER | Set user for commands |
| LABEL | Add metadata |
| HEALTHCHECK | Define health check |
FROM
Initializes a new build stage and sets the base image:
# Basic usage
FROM ubuntu:22.04
# With platform specification
FROM --platform=linux/amd64 node:18
# Named build stage (for multi-stage builds)
FROM node:18 AS builder
# Using digest for exact version
FROM nginx@sha256:abc123...
Base Image Selection
# Full OS - largest, most compatible
FROM ubuntu:22.04 # ~77MB
# Slim variants - smaller
FROM python:3.11-slim # ~125MB vs ~1GB for full
# Alpine - smallest but different libc
FROM node:18-alpine # ~175MB vs ~1GB for full
# Distroless - minimal, secure
FROM gcr.io/distroless/base # Only runtime dependencies
# Scratch - empty, for static binaries
FROM scratch
ENV
Sets environment variables available during build and runtime:
# Single variable
ENV NODE_ENV=production
# Multiple variables (preferred syntax)
ENV NODE_ENV=production \
PORT=3000 \
LOG_LEVEL=info
# Old syntax (still works)
ENV NODE_ENV production
Variables can be used later:
ENV APP_DIR=/app
WORKDIR $APP_DIR
COPY . $APP_DIR
ARG
Defines variables for build-time only:
# Define argument with default value
ARG NODE_VERSION=18
FROM node:${NODE_VERSION}
# ARG without default (must be provided)
ARG BUILD_DATE
# Use in build commands
ARG VERSION
RUN echo "Building version ${VERSION}"
# ARG after FROM only available in that stage
FROM node:18
ARG APP_VERSION
LABEL version=${APP_VERSION}
Pass arguments during build:
docker build --build-arg VERSION=1.0.0 --build-arg BUILD_DATE=$(date -u +%Y%m%d) -t myapp .
ARG vs ENV
| Feature | ARG | ENV |
|---|---|---|
| Available during build | Yes | Yes |
| Available at runtime | No | Yes |
| Persists in image | No | Yes |
| Can be overridden | At build time | At run time |
COPY vs ADD
COPY (Preferred)
Simple, predictable file copying:
# Copy file
COPY app.js .
# Copy multiple files
COPY package.json package-lock.json ./
# Copy directory contents
COPY src/ ./src/
# Copy with different name
COPY app.js application.js
# Set ownership
COPY --chown=node:node . .
# From a build stage
COPY --from=builder /app/dist ./dist
ADD (Special cases only)
Extra features beyond COPY:
# Auto-extract tar archives
ADD archive.tar.gz /app/
# Download from URL (not recommended)
ADD https://example.com/file.tar.gz /app/
# Local tar extraction
ADD local-archive.tar /app/
Best Practice: Use COPY unless you need ADD's features.
RUN
Executes commands during build:
# Shell form - runs in /bin/sh -c
RUN apt-get update && apt-get install -y curl
# Exec form - direct execution
RUN ["pip", "install", "--no-cache-dir", "-r", "requirements.txt"]
# Best practice: combine related commands
RUN apt-get update \
&& apt-get install -y \
curl \
git \
vim \
&& rm -rf /var/lib/apt/lists/*
# Don't do this - creates large layers
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
CMD vs ENTRYPOINT
CMD
Provides defaults for container execution:
# Exec form (preferred)
CMD ["node", "app.js"]
# Shell form
CMD npm start
# As parameters to ENTRYPOINT
ENTRYPOINT ["python"]
CMD ["app.py"] # Default file, can be overridden
CMD can be overridden at runtime:
docker run myapp # Uses CMD
docker run myapp bash # Overrides CMD with bash
ENTRYPOINT
Configures the container as an executable:
# Exec form (preferred)
ENTRYPOINT ["python", "app.py"]
# Container always runs python app.py
# Arguments are appended
docker run myapp --debug # Runs: python app.py --debug
Combining ENTRYPOINT and CMD
# ENTRYPOINT = the executable
# CMD = default arguments
ENTRYPOINT ["python"]
CMD ["app.py"]
# docker run myapp → python app.py
# docker run myapp script.py → python script.py
USER
Sets the user for subsequent instructions and container runtime:
# Create user and switch to it
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
USER appuser
# Or use existing user
USER node
# Can switch back to root if needed
USER root
RUN apt-get update
USER appuser
Best Practice: Don't run containers as root in production.
WORKDIR
Sets the working directory:
# Absolute path
WORKDIR /app
# Creates directory if it doesn't exist
WORKDIR /opt/myapp
# Relative paths build on previous WORKDIR
WORKDIR /app
WORKDIR src # Now in /app/src
WORKDIR ../lib # Now in /app/lib
EXPOSE
Documents the ports the container listens on:
# TCP port (default)
EXPOSE 3000
# Multiple ports
EXPOSE 80 443
# UDP port
EXPOSE 53/udp
# Both TCP and UDP
EXPOSE 53/tcp 53/udp
Remember: EXPOSE is documentation. Use -p to actually publish ports.
VOLUME
Creates a mount point for persistent data:
# Single volume
VOLUME /data
# Multiple volumes
VOLUME ["/data", "/logs"]
# Data will persist across container restarts
VOLUME /var/lib/mysql
LABEL
Adds metadata to the image:
LABEL maintainer="developer@example.com"
LABEL version="1.0"
LABEL description="My application"
# Multiple labels
LABEL maintainer="developer@example.com" \
version="1.0" \
org.opencontainers.image.source="https://github.com/user/repo"
View labels:
docker inspect --format='{{json .Config.Labels}}' myimage
HEALTHCHECK
Defines how Docker checks if the container is healthy:
# HTTP health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# Simple process check
HEALTHCHECK CMD pgrep node || exit 1
# Disable health check from base image
HEALTHCHECK NONE
Options:
--interval: Time between checks (default: 30s)--timeout: Timeout for single check (default: 30s)--start-period: Grace period for startup (default: 0s)--retries: Failures before unhealthy (default: 3)
SHELL
Changes the default shell:
# Default is ["/bin/sh", "-c"]
SHELL ["/bin/bash", "-c"]
# Now RUN uses bash
RUN source ~/.bashrc && npm install
ONBUILD
Triggers instructions when image is used as a base:
# In base image
FROM node:18
ONBUILD COPY package*.json ./
ONBUILD RUN npm install
ONBUILD COPY . .
# When someone builds FROM this image,
# the ONBUILD instructions run automatically
Key Takeaways
- Use ENV for runtime configuration, ARG for build-time only
- Prefer COPY over ADD unless you need tar extraction
- Combine RUN commands to reduce layers
- Use ENTRYPOINT for the main executable, CMD for default arguments
- Always run containers as non-root users in production
- Use HEALTHCHECK to monitor container health
- Add LABELs for image metadata and documentation

