DevDocsDev Docs
Dockerfile

Dockerfile Instructions

Complete reference for all Dockerfile instructions with examples and best practices

This reference covers all Dockerfile instructions with detailed examples and use cases.

Build Instructions Flow

FROM

Sets the base image for subsequent instructions.

# Official image
FROM ubuntu:22.04

# Alpine variant (smaller)
FROM node:20-alpine

# Specific digest (immutable)
FROM nginx@sha256:abc123...

# Platform specific
FROM --platform=linux/amd64 node:20
# Named stage for building
FROM node:20-alpine AS builder
WORKDIR /app
COPY . .
RUN npm run build

# Production stage
FROM nginx:alpine AS production
COPY --from=builder /app/dist /usr/share/nginx/html
# Empty base image (for static binaries)
FROM scratch
COPY myapp /myapp
CMD ["/myapp"]

Image Tags

Always use specific version tags in production. Avoid latest as it can change unexpectedly.

ARG and ENV

ARG defines build-time variables that can be passed with --build-arg.

# Define with default value
ARG NODE_VERSION=20
ARG APP_VERSION

# Use in instructions
FROM node:${NODE_VERSION}-alpine

# ARG after FROM needs redeclaration
ARG APP_VERSION
RUN echo "Building version ${APP_VERSION}"
# Build with arguments
docker build --build-arg APP_VERSION=1.0.0 -t myapp .

ENV sets environment variables available at runtime.

# Single variable
ENV NODE_ENV=production

# Multiple variables
ENV NODE_ENV=production \
    PORT=3000 \
    HOST=0.0.0.0

# Use in subsequent instructions
RUN echo "Environment: $NODE_ENV"
# ARG for build-time, ENV for runtime
ARG VERSION=latest
ENV APP_VERSION=${VERSION}

# Now APP_VERSION is available at runtime
# VERSION is only available during build

WORKDIR

Sets the working directory for subsequent instructions.

# Create and set directory
WORKDIR /app

# Relative paths work too
WORKDIR src
# Now at /app/src

# Multiple WORKDIR instructions
WORKDIR /app
WORKDIR backend
WORKDIR api
# Now at /app/backend/api

Best Practice

Always use WORKDIR instead of RUN cd. WORKDIR creates the directory if it doesn't exist.

COPY and ADD

COPY copies files from build context to image.

# Copy single file
COPY package.json .

# Copy multiple files
COPY package.json package-lock.json ./

# Copy directory
COPY src/ ./src/

# Copy with glob patterns
COPY *.json ./

# Copy from previous stage
COPY --from=builder /app/dist ./dist

# Copy with ownership
COPY --chown=node:node . .

ADD has additional features: URL downloads and tar extraction.

# Auto-extract tar files
ADD archive.tar.gz /app/

# Download from URL (not recommended)
ADD https://example.com/file.txt /app/

# Same as COPY
ADD src/ ./src/

Prefer COPY

Use COPY unless you need tar extraction. ADD behavior can be unpredictable.

FeatureCOPYADD
Copy from context
Copy from stage
Set ownership
Extract tar files
Download URLs
RecommendedOnly for tar

RUN

Executes commands during image build.

# Shell form (runs in /bin/sh -c)
RUN apt-get update && apt-get install -y curl

# Environment variable expansion works
RUN echo "Home is $HOME"

# Pipe support
RUN curl -sL https://example.com | tar xz
# Exec form (no shell processing)
RUN ["apt-get", "install", "-y", "curl"]

# No variable expansion
RUN ["echo", "$HOME"]  # Prints literal $HOME

# Different shell
RUN ["/bin/bash", "-c", "echo Hello"]
# ✅ Combine commands to reduce layers
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        curl \
        wget \
        git && \
    rm -rf /var/lib/apt/lists/*

# ✅ Clean up in the same layer
RUN npm install && \
    npm cache clean --force

# ❌ Don't split update and install
RUN apt-get update
RUN apt-get install -y curl  # May use stale cache

CMD and ENTRYPOINT

# Exec form (preferred)
CMD ["npm", "start"]

# Shell form
CMD npm start

# As ENTRYPOINT arguments
CMD ["--port", "3000"]
# CMD can be overridden
docker run myapp npm test  # Overrides CMD
# Exec form (preferred)
ENTRYPOINT ["node", "server.js"]

# Shell form (not recommended)
ENTRYPOINT node server.js
# ENTRYPOINT is always executed
docker run myapp  # Runs: node server.js
docker run myapp --port 3000  # Runs: node server.js --port 3000
# ENTRYPOINT + CMD pattern
ENTRYPOINT ["python", "app.py"]
CMD ["--host", "0.0.0.0"]

# Results in: python app.py --host 0.0.0.0
# Override CMD but keep ENTRYPOINT
docker run myapp --port 8080
# Runs: python app.py --port 8080

# Override ENTRYPOINT
docker run --entrypoint bash myapp
# Runs: bash
ScenarioENTRYPOINTCMDResult
Default["python"]["app.py"]python app.py
Override CMD["python"]["test.py"] (runtime)python test.py
Override bothbash (runtime)-bash

USER

Sets the user for RUN, CMD, and ENTRYPOINT.

# Create non-root user
RUN groupadd -r app && useradd -r -g app app

# Switch to user
USER app

# All subsequent commands run as 'app'
RUN whoami  # Output: app

# Can use UID:GID
USER 1000:1000

Security

Always run containers as non-root in production. Root inside container = root on host.

EXPOSE

Documents which ports the container listens on.

# Single port
EXPOSE 3000

# Multiple ports
EXPOSE 80 443

# With protocol
EXPOSE 80/tcp
EXPOSE 53/udp

Note

EXPOSE doesn't publish the port. Use -p flag when running: docker run -p 8080:80 myapp

VOLUME

Creates a mount point for external storage.

# Single volume
VOLUME /data

# Multiple volumes
VOLUME ["/data", "/logs"]

HEALTHCHECK

Defines how to check 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

# Custom script
HEALTHCHECK CMD /healthcheck.sh

# Disable health check
HEALTHCHECK NONE
OptionDefaultDescription
--interval30sTime between checks
--timeout30sCheck timeout
--start-period0sGrace period on startup
--retries3Failures before unhealthy

LABEL

Adds metadata to the image.

LABEL maintainer="dev@example.com"
LABEL version="1.0.0"
LABEL description="My application"

# Multiple labels
LABEL maintainer="dev@example.com" \
      version="1.0.0" \
      org.opencontainers.image.source="https://github.com/user/repo"

SHELL

Changes the default shell.

# Default: ["/bin/sh", "-c"]
SHELL ["/bin/bash", "-c"]

# Now RUN uses bash
RUN echo "Using bash: $BASH_VERSION"

# PowerShell on Windows
SHELL ["powershell", "-Command"]

STOPSIGNAL

Sets the signal to stop the container.

# Default is SIGTERM
STOPSIGNAL SIGTERM

# Use SIGINT for graceful shutdown
STOPSIGNAL SIGINT

# Can use signal number
STOPSIGNAL 9

ONBUILD

Triggers instruction when image is used as base.

# Base image Dockerfile
FROM node:20-alpine
ONBUILD COPY package*.json ./
ONBUILD RUN npm install
ONBUILD COPY . .

# Child image automatically runs ONBUILD instructions
FROM my-base-image
# COPY and RUN are executed automatically

Use Sparingly

ONBUILD can make debugging difficult. Document its usage clearly.

On this page