Mastering Dockerfile Commands: A Beginner to Expert Guide
Whether you're just getting started with Docker or looking to level up your container knowledge, understanding every Dockerfile instruction is essential. This guide breaks down all Dockerfile commands — what they do, when to use them, and real examples to cement your understanding.
Quick Reference Table
| Instruction | Runs At | Purpose |
|---|---|---|
FROM |
Build start | Sets the base image |
RUN |
Build time | Executes shell commands |
WORKDIR |
Build time | Sets working directory |
COPY |
Build time | Copies files into image |
ADD |
Build time | Copies files, extracts tarballs, or fetches URLs |
ENV |
Build + Runtime | Sets environment variables |
ARG |
Build time only | Defines build-time variables |
LABEL |
Build time | Adds metadata to image |
EXPOSE |
Documentation | Hints at which port the app uses |
USER |
Build + Runtime | Sets the user for commands |
VOLUME |
Runtime | Declares persistent storage |
ENTRYPOINT |
Runtime | Sets the main executable |
CMD |
Runtime | Sets default command or arguments |
HEALTHCHECK |
Runtime | Monitors container health |
SHELL |
Build time | Changes the default shell |
STOPSIGNAL |
Runtime | Sets the container stop signal |
ONBUILD |
Child build | Deferred instructions for derived images |
The Commands Explained
FROM — Choose Your Base
Every Dockerfile starts here. It defines the base image your container is built on top of.
# Start from an official lightweight Node.js image
FROM node:18-alpine
# Start from bare Ubuntu
FROM ubuntu:22.04
# Multi-stage: name a stage for reference later
FROM node:18-alpine as builder
💡 Tip: Use
-slimor-alpinevariants for smaller image sizes in production.
RUN — Execute Build Commands
Runs any shell command during the build process. Each RUN adds a new layer to the image.
# Install system packages
RUN apt-get update && apt-get install -y curl git
# Chain commands to reduce layers
RUN npm install && npm run build
# Clean up in the same layer to keep image small
RUN apt-get install -y curl && rm -rf /var/lib/apt/lists/*
💡 Tip: Chain related commands with
&&to minimize image layers.
WORKDIR — Set Your Working Directory
Sets the current directory for all subsequent instructions. Creates the folder if it doesn't exist.
WORKDIR /app
# Now all commands below run from /app
COPY . .
RUN npm install
💡 Tip: Always set
WORKDIRinstead of usingRUN cd /some/path— it's cleaner and more predictable.
COPY — Copy Files Into the Image
Copies files or folders from your local machine into the image. Simple and explicit — the preferred way to copy files.
# Copy a single file
COPY package.json /app/
# Copy an entire folder
COPY ./src /app/src
# Copy from a previous build stage (multi-stage builds)
COPY --from=builder /app/dist /app/dist
💡 Tip: Copy
package.jsonbefore your source code so Docker cachesnpm installand only reruns it when dependencies change.
ADD — Copy With Superpowers
Like COPY but with two extra abilities: it can extract .tar archives and download files from URLs.
# Auto-extracts a tar archive
ADD archive.tar.gz /app/
# Download a file from the internet
ADD https://example.com/config.json /app/config.json
# Regular file copy (prefer COPY for this)
ADD package.json /app/
💡 Tip: Use
COPYfor simple file copying. Only useADDwhen you specifically need tar extraction or URL fetching.
ENV — Set Environment Variables
Defines environment variables available both during the build and at runtime.
ENV PORT=8080
ENV NODE_ENV=production
ENV DB_URL="postgres://localhost/mydb"
# Multiple variables in one instruction
ENV APP_HOME=/app \
LOG_LEVEL=info \
MAX_CONNECTIONS=100
💡 Tip: Use
ENVfor values your app needs at runtime. UseARGfor values only needed during the build.
ARG — Build-Time Variables
Defines variables passed in at build time only. They are not available when the container is running.
ARG APP_VERSION=1.0.0
ARG BUILD_ENV=production
RUN echo "Building version $APP_VERSION"
Pass them in at build time:
docker build --build-arg APP_VERSION=2.1.0 .
💡 Tip: Never use
ARGfor secrets — they can be exposed in the image history.
LABEL — Add Metadata
Attaches key-value metadata to your image. Useful for versioning, documentation, and automation.
LABEL maintainer="ibrahim@example.com"
LABEL version="1.0.0"
LABEL description="Node.js production server"
LABEL org.opencontainers.image.source="https://github.com/user/repo"
💡 Tip: Follow the Open Container Initiative label conventions for standardized metadata.
EXPOSE — Document Your Port
Tells Docker (and other developers) which port the container listens on. It is documentation only — it does not actually publish the port.
EXPOSE 8080
EXPOSE 443
You still need to publish ports when running:
docker run -p 8080:8080 myapp
💡 Tip: Always include
EXPOSE— it makes your Dockerfile self-documenting and is required for some Docker tools to work correctly.
USER — Run as Non-Root
Sets which user runs subsequent commands. Running as root inside a container is a security risk.
# Create a user and switch to it
RUN useradd -m appuser
USER appuser
# Or use a numeric UID
USER 1001
💡 Tip: Always switch to a non-root user before your
CMDorENTRYPOINTin production images.
VOLUME — Persist Data
Declares a directory as a mount point for data that should survive container restarts — like databases, uploads, or logs.
# Persist a data folder
VOLUME /app/data
# Persist multiple paths
VOLUME ["/app/data", "/app/logs"]
💡 Tip: Data written inside a
VOLUMEdirectory is stored on the host machine, not inside the container layer.
ENTRYPOINT — Set the Main Executable
Defines the main process of the container. Unlike CMD, it is not easily overridden when running the container.
# Always run node
ENTRYPOINT ["node"]
# Combine with CMD to set a default argument
ENTRYPOINT ["node"]
CMD ["server.js"] # docker run myapp → runs: node server.js
# docker run myapp app.js → runs: node app.js
💡 Tip: Use
ENTRYPOINTfor the executable andCMDfor its default arguments — this gives users flexibility without losing control.
CMD — Default Command
Sets the default command that runs when the container starts. Can be overridden by passing a command to docker run.
# Run the app
CMD ["npm", "start"]
# Or run a specific script
CMD ["node", "server.js"]
Override at runtime:
docker run myapp node other.js # overrides CMD
💡 Tip: Only one
CMDis allowed per Dockerfile. If you define multiple, only the last one takes effect.
HEALTHCHECK — Monitor Container Health
Defines a command Docker runs periodically to check if the container is functioning correctly.
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
| Option | Default | Meaning |
|---|---|---|
--interval |
30s | How often to check |
--timeout |
30s | Max time for the check |
--retries |
3 | Failures before marking unhealthy |
💡 Tip: Always add a health check to production containers so orchestrators like Kubernetes know when to restart them.
SHELL — Change the Default Shell
Changes the shell used by RUN, CMD, and ENTRYPOINT instructions. Default is /bin/sh -c on Linux.
# Switch to bash for more features
SHELL ["/bin/bash", "-c"]
RUN echo "This runs in bash"
# Use PowerShell on Windows
SHELL ["powershell", "-Command"]
💡 Tip: Useful when you need bash-specific features like arrays or
pipefailin your build scripts.
STOPSIGNAL — Control How the Container Stops
Sets the OS signal sent to the container process when it's told to stop. Default is SIGTERM.
# Use SIGQUIT instead of SIGTERM
STOPSIGNAL SIGQUIT
# Use numeric signal
STOPSIGNAL 9
💡 Tip: Some apps (like Nginx) respond better to
SIGQUITfor graceful shutdown. Check your app's documentation.
ONBUILD — Instructions for Child Images
Defines instructions that are dormant in the current image but automatically run when another image uses yours as a FROM base.
# In your base image:
ONBUILD COPY . /app
ONBUILD RUN npm install
When someone else does:
FROM your-base-image # ← ONBUILD instructions fire here automatically
💡 Tip: Great for creating standardized base images for a team — enforces consistent setup without requiring developers to repeat boilerplate.
Putting It All Together
Here's a production-ready Dockerfile using best practices from everything above:
# 1. Base image
FROM node:18-alpine as base
LABEL maintainer="ibrahim@example.com"
ENV NODE_ENV=production PORT=8080
# 2. Install dependencies
FROM base as deps
WORKDIR /app
COPY package*.json ./
RUN npm install
# 3. Build
FROM deps as build
COPY . .
RUN npm run build
# 4. Final lean image
FROM base
WORKDIR /app
RUN adduser -D appuser
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s CMD wget -qO- http://localhost:8080/health || exit 1
CMD ["node", "dist/server.js"]
Summary
Mastering Dockerfile instructions lets you build images that are small, secure, fast to build, and easy to maintain. The key principles:
- Use multi-stage builds to keep final images lean
- Order layers from least to most frequently changed for better caching
- Always run as a non-root user in production
- Use HEALTHCHECK so orchestrators can manage your containers
- Prefer COPY over ADD unless you need its extra features
- Use ENTRYPOINT + CMD together for flexible but controlled container behavior
