FROM node:20-alpine
FROM python:3.12-slim AS builder
Note Every Dockerfile must start with FROM (or ARG before FROM). Always pin a specific tag — FROM node:latest today might be Node 20, but tomorrow it could be 22, breaking your build silently.
base imagefrom instructionparent imagestarting image
RUN — Execute Build Commands
syntax
RUN <command>
RUN ["executable", "arg1", "arg2"]
example
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir -r requirements.txt
Note Each RUN creates a new image layer. Combine related commands with && to reduce layers and image size. Always clean up package manager caches in the same RUN statement, or the cache ends up in a previous layer and is never actually removed.
run commandinstall packagesexecute during buildbuild step
Note COPY only handles local files from the build context. Copy dependency manifests (package.json, requirements.txt) before the rest of the source code so that dependency-install layers are cached when only your application code changes.
copy filesadd files to imagecopy into containercopy source code
Note ADD can auto-extract tar archives and fetch URLs. However, COPY is preferred for simple file copies because ADD's extra behavior can be surprising. Using ADD for URL downloads also defeats layer caching — use RUN curl instead for better control.
add filesextract archivedownload urlcopy vs add
WORKDIR — Set Working Directory
syntax
WORKDIR /path/to/dir
example
WORKDIR /app
COPY . .
RUN npm ci
Note All subsequent RUN, CMD, COPY, and ENTRYPOINT instructions use this directory. WORKDIR creates the directory if it does not exist. Avoid using RUN cd /app — it only affects that single RUN layer.
working directoryset directorycd in dockerfilechange directory
Note There can only be one CMD. If you specify multiple, only the last one takes effect. CMD is overridden by any command you pass to docker run. Always prefer the exec form (JSON array) so the process runs as PID 1 and receives signals properly.
Note ENTRYPOINT sets the main executable. CMD then provides default arguments that users can override. Together: ENTRYPOINT ["python", "manage.py"] + CMD ["runserver"] means the user can run docker run myapp migrate to swap the subcommand.
entrypointmain processentry pointfixed commandentrypoint vs cmd
Note ENV values persist into the running container and into child images. If you only need a variable during build time (e.g., a version number), use ARG instead to avoid leaking it into the final image.
environment variableset env in dockerfileenv instructionbuild-time variable
ARG — Build-Time Variables
syntax
ARG <name>[=<default_value>]
example
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-alpine
ARG BUILD_DATE
LABEL build-date=$BUILD_DATE
Note ARG values are only available during build (not at runtime). Pass them with docker build --build-arg NODE_VERSION=22. ARG before FROM is only in scope for the FROM line itself — redeclare it after FROM if needed later.
Note EXPOSE is documentation only — it does NOT publish the port. You still need -p at runtime. Think of it as a note to anyone reading the Dockerfile about which ports the application listens on.
Note Creates an anonymous volume at that path when no explicit mount is provided. This prevents data from growing the container's writable layer. However, anonymous volumes are hard to manage — prefer named volumes via docker run -v.
volume instructiondeclare volumepersistent storagemount point
USER — Set Runtime User
syntax
USER <username>[:<group>]
example
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
USER appuser
Note All subsequent RUN, CMD, and ENTRYPOINT instructions run as this user. Running as root in production is a security risk — always create and switch to a non-root user for the final stage.
set usernon-rootrun as usersecurity userdrop privileges
Note Labels are key-value metadata stored in the image. They show up in docker inspect and can be used as filters: docker images --filter label=version=2.1. Use the OCI standard keys (org.opencontainers.image.*) for interoperability.
Note Docker marks the container as healthy, unhealthy, or starting based on exit codes. Orchestrators use this to decide when to route traffic or restart containers. Install curl or wget in your image if you use HTTP checks.
FROM <image> AS <stage>
...
FROM <image>
COPY --from=<stage> <src> <dest>
example
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]
Note The final image only contains the layers from the last FROM stage. Build tools, dev dependencies, and source code from earlier stages are discarded, dramatically reducing image size. A Go API might go from 1.2GB (with SDK) to 15MB (static binary on scratch).
Note Place this file next to your Dockerfile. Without it, Docker sends everything in the build context to the daemon, including node_modules (hundreds of MB), .git history, and possibly secrets in .env files. This is one of the highest-impact optimizations you can make.
dockerignoreexclude filesbuild contextignore filesspeed up build