Chapter 1: Docker Fundamentals

Before you write your first Dockerfile, understand why Docker exists and what problem it solves. In this chapter we learn the difference between a container and a virtual machine, how an Image relates to a container, and how Docker works under the hood.

We explain each idea generally first, then apply it to a real example — a Spring Boot app — whenever seeing it run helps.

⏱️ Reading time: — 📊 Level: Beginner 🧪 1 hands-on lab

Learning objectives

By the end of this chapter you'll know how to:

  • Explain the "works on my machine" problem and how Docker solves it.
  • Tell a Virtual Machine (VM) apart from a Container.
  • Describe how an Image relates to a Container.
  • Name Docker's three parts: Client, Daemon, and Registry.
  • Trace the stages of the container lifecycle.
  • Connect these ideas to a real app (Spring Boot).

Prerequisites

This chapter is entirely theoretical. You don't need Docker installed yet — we'll do that in the next chapter. The concepts are general and apply to any app. Some examples use Java and Spring Boot, so basic familiarity helps but isn't required.

💡 Note

The commands shown in "Example" and "Hands-on" are a preview only — you'll run them yourself in Chapter 2 after installing Docker.

Why Docker? The "works on my machine" problem

Imagine you just finished building your app on your machine. It runs great. You send it to a colleague or to the server, and it breaks immediately. Why?

Because no app runs in a vacuum — it depends on a whole environment around it:

  • A specific runtime version and libraries.
  • Environment variables and settings.
  • External services such as a database of a certain version.
  • Ports and operating-system libraries.

Any small difference in this environment between your machine and the server can break the app. That's the famous "works on my machine" problem.

A concrete example: a Java app depends on a JRE for Java 21 on your machine, while the server only has Java 17 — so it fails to run.

What does Docker solve?

Docker's idea is simple. Instead of moving the app alone, we package it with its entire environment into a single unit called an Image.

This unit runs the same way on any machine that has Docker: yours, your colleague's, or the server. The differences disappear, and so does the problem.

✅ The core idea

Docker doesn't make your app faster; it makes it run the same everywhere. The real value is consistency between development and production.

Virtual Machines

Before containers, isolation was usually done with a virtual machine. A VM emulates a whole machine: each app gets its own guest OS running on top of a layer called a Hypervisor.

This gives strong isolation, but at a cost:

  • Each VM carries a full OS — gigabytes in size.
  • Booting takes minutes.
  • High memory and CPU usage, because every guest OS runs in full.

Containers

A container solves the same isolation problem far more lightly. Instead of emulating a whole machine, all containers share the host operating system kernel and run as isolated processes on top of it.

The result:

  • Size in megabytes, not gigabytes.
  • Boot in seconds or less.
  • Far lower usage — you can run dozens of containers on one machine.

❗ Very important

A container is not a small virtual machine — it's an isolated process. It has no full OS, only the app's files and environment, and it borrows the kernel from the host.

Quick comparison: VM vs Container

Criterion Virtual Machine Container
IsolationFull guest OSIsolated process on the host kernel
SizeGigabytes (GB)Megabytes (MB)
Boot timeMinutesSeconds
OverheadHighLow
Density per machineLimitedDozens or more

Images and Containers

These two terms are the heart of Docker, and mixing them up is common. The difference is simple:

  • Image: a read-only template that contains your app and everything it needs to run. It doesn't run by itself — it's just a "blueprint."
  • Container: a live, runnable instance of the Image. From a single Image you can run any number of containers.

✅ In Java terms

If you know Java, the analogy is direct: the Image is like a Class, and the Container is like an Object (an instance of the class). Just as you create many objects from one class, you run many containers from one Image.

Real-world analogy

Docker is named after dock workers who ship containers, and the analogy is intentional:

✅ The shipping container

Before standard shipping containers, every kind of cargo was shipped its own way — chaos. Then came the standardized container: one fixed-size box that any crane can lift and any ship or truck can carry, regardless of what's inside.

A Docker container does the same for software: it wraps your app in a standard "box" that runs on any Docker-capable infrastructure, without that infrastructure caring what's inside.

Diagrams

Architecture: VM vs Container

flowchart TB
    subgraph VM["Virtual Machine model"]
        direction TB
        H1["Hardware"] --> HV["Hypervisor"]
        HV --> G1["Guest OS + App A"]
        HV --> G2["Guest OS + App B"]
    end
    subgraph CT["Container model"]
        direction TB
        H2["Hardware"] --> OS["Host OS + Kernel"]
        OS --> DE["Docker Engine"]
        DE --> C1["Container A"]
        DE --> C2["Container B"]
        DE --> C3["Container C"]
    end
              
A VM duplicates the OS for every app, while containers share the host kernel through the Docker Engine.

Docker architecture

Docker works as a client/server model. When you run a command like docker run, you're using the Client, which sends the request to the Docker Daemon (the server) that builds images and runs containers. Ready-made images come from a Registry such as Docker Hub.

flowchart LR
    U["You"] -->|"docker run / build / pull"| CLI["Docker Client (CLI)"]
    CLI -->|"REST API"| D["Docker Daemon (dockerd)"]
    D -->|"manages"| IMG["Images"]
    D -->|"runs"| CON["Containers"]
    D <-->|"pull / push"| REG["Registry — Docker Hub"]
              
The Client sends commands, the Daemon executes them, and the Registry is the source and destination of images.

The container lifecycle

A container moves through clear states, each transition driven by a single command:

stateDiagram-v2
    [*] --> Created: docker create
    Created --> Running: docker start
    Running --> Paused: docker pause
    Paused --> Running: docker unpause
    Running --> Stopped: docker stop
    Stopped --> Running: docker start
    Stopped --> Removed: docker rm
    Removed --> [*]
              
docker run takes a shortcut: it creates the container and starts it in one step.

Example: the problem, concretely

Let's make the "works on my machine" problem concrete. Your Spring Boot app needs Java 21, but the server only has Java 17. On startup:

$ java -jar tasks-service.jar
Error: LinkageError occurred while loading main class
  UnsupportedClassVersionError: class file version 65.0,
  this version only recognizes up to 61.0

With Docker, we bundle the Java version inside the image itself, so what's installed on the server no longer matters:

# The image carries Java 21 with it, wherever it runs
FROM eclipse-temurin:21-jre
COPY tasks-service.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

Now the container runs with Java 21 on any server, even one with no Java at all.

Hands-on (preview)

💡 Preview only

You won't run these commands yet — Docker isn't installed. The goal is to see where we're heading. You'll run them yourself in Chapter 2.

The classic first container to confirm Docker works is hello-world:

docker run hello-world

What happens behind the scenes when you run this?

  1. The Client sends the request to the Daemon.
  2. The Daemon can't find the image locally, so it pulls it from Docker Hub.
  3. It creates a container from the image and runs it.
  4. The container prints a welcome message and stops.
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.

Hands-on lab

Whenever this guide needs a real app, we'll reach for one recurring example: the Tasks Service — a simple REST API built with Spring Boot that stores tasks in PostgreSQL. Docker's concepts stay general; we use this example only to see them applied to something concrete.

🧪 Lab 1: inventory your runtime environment

No code yet. Your task is to identify what makes your app run, because that's exactly what we'll package into the image later.

For any app you have (or the Tasks Service), write down:

  1. The required Java (JRE) version.
  2. The port the app listens on (usually 8080).
  3. Environment variables and settings (database URL, password, the active profile).
  4. External services and their versions (PostgreSQL).

Output: this inventory is the "spec" of your future image — it turns directly into Dockerfile instructions in Chapter 5, and into services in compose.yaml in Chapter 8.

Common mistakes

⚠️ Treating a container as a small VM

A common mistake. A container is an isolated process that shares the host kernel, not a full OS. That's why it's lighter and faster — and also why it can't run a kernel entirely different from the host's.

⚠️ Confusing Image and Container

The Image is a static template; the Container is a live run of it. Changing a container doesn't change the image it was built from.

⚠️ Thinking Docker replaces the runtime

Docker doesn't replace your app's runtime — it bundles it. A Java app still needs a JRE, but now the JRE lives inside the image at a version we know and control.

Best practices

📦 One process per container

Make each container responsible for one thing: the Spring Boot service in one, the database in another.

🧊 The image is an immutable artifact

Treat the image as an immutable artifact: built once, run unchanged across all environments.

♻️ Containers are disposable

Treat a container as ephemeral: you can delete and recreate it at any moment without fear.

Glossary (Cheat Sheet)

Term Short meaning
ImageRead-only template holding the app and its environment
ContainerA live, runnable instance of an image
Docker ClientThe commands you type (docker ...)
Docker DaemonThe server (dockerd) that builds and runs
RegistryImage store (e.g. Docker Hub)
DockerfileA text recipe for building an image
Docker EngineClient + Daemon together on the machine

Summary

Key ideas

  • Docker solves "works on my machine" by packaging the app with its environment.
  • A container is an isolated process sharing the host kernel — lighter and faster than a VM.
  • The Image is a static template; the Container is a live run of it (Class vs Object).
  • Docker's architecture = Client + Daemon + Registry.

Terms to remember

  • Image / Container / Registry
  • Docker Client / Docker Daemon

Mistakes to avoid

  • Treating a container as a small VM.
  • Confusing image and container.
  • Thinking Docker replaces the JVM.