Dockerfile Basics
Write Dockerfiles that build correctly and cache well: FROM, COPY, RUN, CMD, ENTRYPOINT, ENV, ARG, USER, and .dockerignore explained.
A Dockerfile is a text file containing the instructions Docker follows to build an image. Each instruction creates a step in the build, and Docker caches the result so unchanged steps are reused. Writing a Dockerfile well means producing images that are small, reproducible, secure, and fast to rebuild.
A First Dockerfile
This example packages a small Node.js application:
FROM node:20-alpine
WORKDIR /app
# Copy dependency manifests first so this layer is cached
COPY package*.json ./
RUN npm ci --omit=dev
# Copy the application source last
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
USER node
CMD ["node", "server.js"]
Build and run it:
docker build -t my-node-app:1.0 .
docker run -d --name app -p 3000:3000 my-node-app:1.0
Common Instructions
| Instruction | Purpose |
|---|---|
FROM |
Sets the base image; every Dockerfile starts with it (after optional ARG) |
WORKDIR |
Sets the working directory for following instructions and for the container |
COPY |
Copies files from the build context into the image |
ADD |
Like COPY, plus URL download and automatic extraction of local tar archives |
RUN |
Executes a command at build time and commits the result as a layer |
ENV |
Sets environment variables available at build time and run time |
ARG |
Defines a build-time variable passed with --build-arg |
EXPOSE |
Documents the port the application listens on (does not publish it) |
USER |
Sets the user for subsequent instructions and the running container |
CMD |
Default command or default arguments when the container starts |
ENTRYPOINT |
The executable that always runs; CMD supplies default arguments to it |
HEALTHCHECK |
Defines how Docker tests whether the container is healthy |
VOLUME |
Declares a mount point for external data |
CMD vs ENTRYPOINT
ENTRYPOINT ["java", "-jar", "app.jar"]
CMD ["--spring.profiles.active=prod"]
ENTRYPOINTdefines what runs.docker run image argsappendsargsto it.CMDdefines defaults that are replaced by anything you pass todocker run.- Use
docker run --entrypointto override the entrypoint itself.
Prefer the exec form (["executable", "arg"]) over the shell form (CMD java -jar app.jar). The exec form runs your process directly as PID 1 so it receives signals such as SIGTERM and can shut down gracefully. The shell form wraps the command in /bin/sh -c, and the shell may not forward signals.
Layer Caching
Every RUN, COPY, and ADD creates a layer. Docker reuses a cached layer when the instruction and its inputs are unchanged; once one layer is invalidated, all layers after it are rebuilt. Order instructions from least to most frequently changing:
# Good: dependencies rarely change, source changes often
COPY package*.json ./
RUN npm ci
COPY . .
# Bad: any source change invalidates the dependency install
COPY . .
RUN npm ci
ENV vs ARG
| Aspect | ARG |
ENV |
|---|---|---|
| Available during build | Yes | Yes |
| Available in running container | No | Yes |
| Set with | --build-arg NAME=value |
ENV NAME=value, overridable with docker run -e |
| Suitable for secrets | No (visible in image history) | No (visible in docker inspect) |
ARG APP_VERSION=1.0.0
ENV APP_VERSION=${APP_VERSION}
docker build --build-arg APP_VERSION=1.2.3 -t my-app:1.2.3 .
The .dockerignore File
The build context (the directory you pass to docker build) is sent to the builder. A .dockerignore file excludes files from it, which speeds up builds, improves cache hits, and keeps secrets out of images.
.git
node_modules
target
*.log
.env
Dockerfile
.dockerignore
Combining RUN Steps
Chain related commands and clean up in the same layer, because deleting files in a later layer does not shrink the image:
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
Common Mistakes
- Using
FROM ubuntuorlatestwithout a version. Builds change silently. Pin a tag such asnode:20-alpine. - Running as root. Without a
USERinstruction the process runs as root. Create or switch to an unprivileged user. - Copying everything before installing dependencies.
COPY . .first destroys layer caching. - Missing
.dockerignore..git,node_modules, and.envend up in the context, or even in the image. - Putting secrets in
ENVorARG. They remain in image metadata and history. Use runtime secrets or BuildKit secret mounts.
Best Practices
- Choose a small, official base image and pin its version.
- Order instructions for cache efficiency, and chain
RUNcommands that belong together. - Use
COPYinstead ofADDunless you specifically need archive extraction. - Use the exec form for
CMDandENTRYPOINT. - Add a
.dockerignore, and switch to a non-rootUSER.
Interview Questions
1. What is a Dockerfile?
A Dockerfile is a declarative text file of instructions (FROM, COPY, RUN, CMD, and so on) that docker build uses to create an image. Each filesystem-changing instruction adds a layer.
2. What is the difference between CMD and ENTRYPOINT?
ENTRYPOINT sets the executable that always runs, while CMD provides default arguments or a default command that is easily overridden at docker run. They are commonly combined: ENTRYPOINT ["java","-jar","app.jar"] with CMD as default arguments.
3. What is the difference between COPY and ADD?
Both copy files into the image. ADD can also fetch remote URLs and auto-extract local tar archives, which makes behaviour less predictable. Use COPY by default.
4. How does Docker layer caching work?
Docker checks each instruction against the cache. If the instruction and its input files (for COPY) are unchanged, the cached layer is reused. When a layer changes, every later layer is rebuilt, so put stable steps such as dependency installation before frequently changing source copies.
5. What is the difference between ARG and ENV?
ARG exists only during the build and is set with --build-arg. ENV persists in the image and is visible to the running container. Neither is a safe place for secrets.
6. Why use the exec form of CMD?
The exec form runs the process directly as PID 1, so it receives SIGTERM from docker stop and can shut down cleanly. The shell form puts /bin/sh -c in between, which may swallow signals.
7. Does EXPOSE publish a port?
No. EXPOSE only documents the port. To make it reachable from the host you must publish it with -p or -P at docker run.
Summary
- A Dockerfile defines how an image is built, one cached layer per step.
- Put rarely changing steps first and use
.dockerignoreto keep the context small. ENTRYPOINTis the fixed executable,CMDis the overridable default; prefer exec form.- Pin base images, run as non-root, and never store secrets in
ENVorARG.
Continue Learning
Previous: Docker Architecture | Next: Docker Image vs Container
Was this guide helpful?
CodeWithVenu is free to read, with no paywall. If this guide saved you time or helped you prepare, a small voluntary contribution helps me write more practical tutorials, architecture guides, and interview-prep content.