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"]
  • ENTRYPOINT defines what runs. docker run image args appends args to it.
  • CMD defines defaults that are replaced by anything you pass to docker run.
  • Use docker run --entrypoint to 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 ubuntu or latest without a version. Builds change silently. Pin a tag such as node:20-alpine.
  • Running as root. Without a USER instruction 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 .env end up in the context, or even in the image.
  • Putting secrets in ENV or ARG. 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 RUN commands that belong together.
  • Use COPY instead of ADD unless you specifically need archive extraction.
  • Use the exec form for CMD and ENTRYPOINT.
  • Add a .dockerignore, and switch to a non-root USER.

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 .dockerignore to keep the context small.
  • ENTRYPOINT is the fixed executable, CMD is the overridable default; prefer exec form.
  • Pin base images, run as non-root, and never store secrets in ENV or ARG.

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.