Learn / Programming / Docker / Writing a Dockerfile

Docker · Lesson 2 of 3

Writing a Dockerfile

Build your own image, use layer caching and keep images small.

  • Intermediate
  • 16 min read
  • 3 objectives

Before this lessonLesson 1: Containers and Docker Basics

What you will learn

  • Write a Dockerfile
  • Use .dockerignore
  • Use multi-stage builds

A Dockerfile is a text file of instructions for building your own image. Each instruction adds a layer. Docker caches layers, so ordering the file well makes rebuilds very fast.

A Python example

FROM python:3.12-slim

WORKDIR /app

# copy dependency list first so this layer is cached
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# then the source code (changes often)
COPY . .

EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
docker build -t my-api:1.0 .
docker run -d -p 8000:8000 my-api:1.0

Instructions

  • FROM: the base image to start from.
  • WORKDIR: set the working directory (creates it if needed).
  • COPY: copy files from your project into the image.
  • RUN: execute a command at build time, such as installing packages.
  • ENV: set an environment variable.
  • EXPOSE: document the port the app listens on (does not publish it).
  • CMD / ENTRYPOINT: the command that runs when the container starts.

Why order matters

When you change a file, Docker rebuilds from the first changed layer onward. Copying requirements.txt and installing dependencies before copying the rest of your code means everyday code edits reuse the cached dependency layer, cutting build time from minutes to seconds.

.dockerignore

Keep junk and secrets out of the build context:

.git
.venv
__pycache__
node_modules
.env
*.log

Multi-stage builds

Build tools such as compilers are needed to build the app but not to run it. A multi-stage build compiles in one stage and copies only the result into a small final image.

# Stage 1: build
FROM node:20 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: serve
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80

Good habits

  • Use small base images (-slim, alpine) and pin versions.
  • Run as a non-root user: RUN useradd -m app then USER app.
  • Never bake secrets into the image; pass them at runtime.
  • One main process per container.
# Write your solution here
Up next · Lesson 3Docker ComposeRun an app with a database and other services from one file.