Metadata Card
- Prerequisites: Terminal basics (Ch2), Package manager (Ch5)
- Estimated time: 40 min
- Core difficulty:
- Completion marker: Can write a Dockerfile, build an image, run a container; understand the difference between images and containers
Your Progress
You're in the workshop, and you've packaged your project beautifully. Dependencies are managed, code compiles cleanly. But when you hand it to a colleague — their machine doesn't have the right JDK version. Another colleague gets a Python version mismatch. A third one is on Windows and your shell script doesn't run.
You've finished your project setup — dependencies, Git, everything. A friend wants to run your project. You send them the code. They try to run it: "JDK version mismatch." "Oh, and Python 3.9 is required, not 3.8." "Also, my Windows doesn't have GNU Make."
You think: "Is there a way to package the entire environment — the operating system bits, the runtime, the dependencies, my code — into one thing they can just run?"
Docker is that "one thing."
Chapter Layers
- Required reading: Docker image vs container, Dockerfile basics (FROM, RUN, COPY, CMD/ENTRYPOINT), building and running
- Optional reading: Docker Compose, Volumes for persistent data, .dockerignore
- Advanced: Multi-stage builds, Docker networking, image layer optimization
This chapter will NOT require you to understand
- Docker Compose full syntax — just common actions
- Kubernetes (Docker is the container runtime, K8s is the orchestrator — two different things)
- Container security and user permission configuration
The Breakthrough · Tracing the Origins
Scenario: The Everywhere-and-Nowhere Workshop
"Let me show you something." The workshop master walks to a whiteboard and draws three boxes — one labeled "Windows," one "macOS," one "Linux." "Your workshop is in the first box. Your friend's is in the second. Your server is in the third. You need your tools to work in all three places — without rebuilding them each time."
He draws a fourth box, crossing all three. "This box is Docker. It's like a standardized shipping container — the same shape everywhere, the same interface everywhere. What you put inside — your tools, your code, the exact version of the JDK — is sealed in. It runs on any machine, in any dock."
Your setup is working perfectly on your machine. But if you send the code to someone else, or to a server, there will always be differences: different OS, different runtime versions, different environment variables.
Docker solves this. It's like a standardized shipping container. The worker at the port doesn't care whether the container is full of vacuum tubes or steel ingots — they just pick it up and put it on the ship. Container is the same shape, same handling method, anywhere in the world.
Your Task
After this chapter, you'll be able to:
- Write a Dockerfile — the blueprint that describes how to build your container
- Build an image — like making a mold of your entire environment
- Run a container — actually start the thing inside Docker
- Understand images vs containers — like a program vs a process
First Battle: Packaging the Workshop
You have a simple Python script. It reads a file, processes it, and outputs a result. You want to send it to a friend who uses Windows. They have Python installed, but version 3.6 — yours uses 3.10 syntax. You could ask them to upgrade, but maybe they have other dependencies that break.
Instead, you package the entire environment into a Docker image.
First, create a file called Dockerfile:
# Dockerfile — the workshop blueprint
# FROM: start from a base image (like buying a pre-built workbench)
FROM python:3.10-slim
# WORKDIR: set the working directory (clear a spot on the workbench)
WORKDIR /app
# COPY: copy your files into the image (move materials from your cabinet)
COPY requirements.txt .
COPY main.py .
# RUN: execute commands during image build (assemble while building the mold)
RUN pip install -r requirements.txt
# CMD: the default command when the container starts (what to do when powered on)
CMD ["python", "main.py"]Language: Dockerfile How to run: docker build -t my-tool . then docker run my-tool
Step by step:
python:3.10-slimis like a "Python workbench" from Docker Hub — the standard registryCOPYbrings your materials into the container — including your codeRUN pip installinstalls the dependencies during the buildCMDsays "when this container starts, runpython main.py"
# Build the image (create the mold)
docker build -t my-tool .
# Run the container (start the process in the mold)
docker run my-toolThat's it. Now your friend can run your tool without installing anything except Docker. No Python version conflicts, no missing packages, no "it works on my machine."
What really happened?
When your friend does docker run my-tool, Docker:
- Downloads the
python:3.10-slimimage (if not cached) - On top of it, adds the layers you defined (your files, installed packages)
- Starts the container in an isolated environment
- Runs
python main.py
Your project runs inside this container, isolated from the host OS. The machine has Python 3.6? Doesn't matter — the container has Python 3.10. The machine runs Windows? Doesn't matter — the container runs Linux (the base image is Debian-based).
Second Battle: Images vs Containers
You've built your Docker image. You've run it. Now let's go deeper.
"Image? Container? I'm using these words, but I don't really know the difference." You look at the workshop master.
He picks up a cookie cutter and presses it into dough. "The cutter is the image — it's a fixed mold, defining the shape. The cookie is the container — it's what you get when you use the mold."
- Image (
docker image): The read-only template. It contains the filesystem, dependencies, and configuration. You build it once withdocker build. It's a blueprint. - Container (
docker container): A runnable instance of an image. You create it withdocker run. You can start, stop, delete, and create again from the same image.
One image → Many containers.
# See your images
docker images
# See running containers
docker ps
# See all containers (including stopped)
docker ps -aThird Battle: Packing for Different Tools
Your toolkit grows. Now you have:
- A Python tool (calculator)
- A Java backend (order service)
- A PostgreSQL database (data storage)
You don't want to install Python, Java, and PostgreSQL on your machine directly. You'd rather each has its own container.
# Dockerfile for your Java order service
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY target/order-service.jar .
EXPOSE 8080
CMD ["java", "-jar", "order-service.jar"]# Build and run
docker build -t order-service .
docker run -p 8080:8080 order-serviceAdd -p 8080:8080 to expose the container's port 8080 to your host machine.
# Dockerfile for your Python calculator
FROM python:3.10-slim
WORKDIR /app
COPY calculator.py .
CMD ["python", "calculator.py"]What about the database? You don't need to write a Dockerfile for PostgreSQL — it's already on Docker Hub:
docker run --name my-postgres -e POSTGRES_PASSWORD=secret -d postgres:16Docker Hub has official images for almost everything. PostgreSQL, MySQL, Redis, Nginx — just docker run them.
Fourth Battle: Multiple Containers Working Together
Now you have three containers. Your backend needs to connect to the database. Your Python tool needs to call your backend.
How do they find each other?
With docker compose. Create a docker-compose.yml file:
services:
db:
image: postgres:16
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: secret
volumes:
- db_data:/var/lib/postgresql/data
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- db
environment:
DB_URL: jdbc:postgresql://db:5432/myapp
tool:
build: ./tool
depends_on:
- backend
volumes:
db_data:# Start everything
docker compose up -d
# Stop everything
docker compose downNow your backend can reach the database at db:5432 (Docker's internal DNS), and your whole system runs together.
Advanced: The full Docker Compose syntax and Docker networking model are covered in Vol 0's supplementary content. For now, just know that
docker composemakes multi-container apps much easier to manage.
Common Pitfalls
Story: The 2GB Image
Your first Docker image was 2GB. You copied the entire JDK, then installed Maven, downloaded all dependencies, copied your source code, compiled inside the image... That's like bringing an entire steel mill to forge a single nail.
The lesson: Multi-stage builds. Build with Maven in one stage, then copy only the final jar into a smaller runtime image.
# Stage 1: Build
FROM maven:3.9 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn package -DskipTests
# Stage 2: Run (much smaller!)
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /app/target/order-service.jar .
CMD ["java", "-jar", "order-service.jar"]The final image only contains the JRE and the jar. No Maven, no source code, no build dependencies. The image drops from 2GB to ~200MB.
Trap: Volume = Persistence
By default, everything in a container is ephemeral. When you delete the container, the data is gone. Like writing on a fogged-up window — it's there until the heat turns on.
If you're running a database in Docker, you need volumes:
docker run -v mydata:/var/lib/postgresql/data postgres:16Or in Docker Compose, as shown above: volumes: - db_data:/var/lib/postgresql/data. This mounts a persistent volume from your host, so even if the container is deleted, the data survives.
Final Challenge
- Warm-up (5 min, required): Run
docker run hello-world. If Docker is not installed, install it first. - Challenge (30 min, optional): Write a Dockerfile for [a simple project of your choice]. Build it. Run it. Change the code, rebuild, re-run.
- Observe: Run
docker imagesand see how many layers each image has. Rundocker history <image>to see what each layer does. - Troubleshooting:
docker buildfails with "COPY failed: file not found." Check: is the file you're copying actually in the build context (the directory you passed todocker build)? Docker can only copy files from within the build context.
Checklist
- [ ] Can explain the difference between an image and a container
- [ ] Can write a basic Dockerfile with FROM, WORKDIR, COPY, RUN, CMD
- [ ] Can build an image and run a container
- [ ] Can expose a port with
-p - [ ] Knows what Docker Compose is for
- [ ] Knows how to use volumes for persistent data
- [ ] Knows that each
RUNin a Dockerfile creates a new image layer
No Need to Understand Now
- Docker networking internals (bridge, host, overlay networks)
- Docker Swarm or Kubernetes
- Container security, user namespaces, seccomp profiles
- Image layer sharing and caching optimization details
Traveler's Notes
Docker is like a standardized shipping container for your software.
Image = mold. Container = the thing made from the mold.
One image → many identical containers.
FROM picks your base, COPY brings your code, RUN sets it up, CMD defines its purpose.
Volumes persist data across container restarts.
Docker Compose orchestrates multiple containers as one system.
→ Preview of Next Stop
Docker packages your entire environment into a portable container. But how does code actually go from your editor to running on a real server? Next chapter traces the full pipeline — from git push to a running production service.