You finish a feature, it works, you push it, and someone else pulls it and it does not run. A different Java. A different Node. A library the other machine has and yours does not. Docker exists to make that conversation unnecessary.
What a container actually is
A container is one process, running on your kernel, with a lie told to it about what the filesystem looks like.
That is nearly the whole idea. The process sees a root directory you gave it, its own network interface, its own process table — but it is scheduled by the same kernel as everything else on the machine. There is no second operating system underneath it.
Which is why people reach for the comparison with a virtual machine, and why the comparison is worth being precise about:
virtual machine container
───────────────────── ─────────────────────
your app your app
libraries libraries
GUEST KERNEL + full OS ←→ (nothing — shares the host kernel)
hypervisor container runtime
host kernel host kernel
hardware hardwareA VM boots an operating system, so it starts in tens of seconds and costs a gigabyte or two before your app has done anything. A container starts a process, so it starts in milliseconds and costs whatever your app costs.
The trade is isolation. A VM is separated from its neighbours by a hypervisor; a container is separated by kernel features — namespaces and cgroups — and a kernel bug is a shared problem. For running your own services that is a fine trade. For running untrusted code from strangers, people put a VM boundary back in.
Image and container are not the same word
This distinction causes more early confusion than anything else in Docker, so it is worth nailing down now.
An image is a filesystem plus some metadata, sitting on disk, not running. It is inert. It is closer to a class than to a zip file, in that you can make many things from it.
A container is one running (or stopped) instance of an image, with a thin writable layer of its own on top. Ten containers from one image share the image's bytes on disk and each get their own scratch space.
docker image ls # images: the templates
docker ps # containers: the running things
docker ps -a # containers: including the ones that have stoppedThe practical consequence: anything you change inside a running container is gone when that container is removed. Edit a config file in there, and you have edited the writable layer, not the image. Lesson 11 is about where to put things you actually want to keep.
What it is genuinely good at
Three things, in rough order of how much people care:
The environment travels with the code. The image contains the JRE, the libraries, the config file layout — all of it. "Works on my machine" becomes "works, and here is the machine".
One machine runs many services without them fighting. The demo application this track uses needs MySQL 8.4. Your laptop may already have MySQL 5.7 for something else. In containers that is not a conflict, it is two containers.
The thing you tested is the thing you deploy. Not a rebuild of it on the CI runner from the same source — the identical image, identified by a digest.
What it does not do
Worth saying early, because a lot of writing about Docker implies otherwise.
It does not make your application faster. It does not make it secure — a container running as root is running as root on your kernel, which is lesson 9. It does not restart your app when the machine reboots unless you ask it to, does not load-balance, does not roll out a new version without dropping requests, and does not move work off a failed host. Those are orchestrator jobs, and lesson 21 is honest about where the line falls.
And it is not free. An image is a few hundred megabytes you now have to build, store, pull and keep patched.
The versions this track is written against
Everything here was run on this machine, not copied from documentation. Where a number appears — an image size, a build time — it was measured.
$ docker version --format '{{.Server.Version}}'
27.4.0
$ docker compose version --short
2.31.0
$ docker buildx version
github.com/docker/buildx v0.19.2
$ docker info --format '{{.OperatingSystem}} | {{.Architecture}} | {{.Driver}}'
Docker Desktop | aarch64 | overlay2That aarch64 matters more than it looks. It is an Apple Silicon
Mac, so every image built here is arm64 by default, and most servers are not. That is
the whole of lesson 19, and it is written from a machine that actually has the problem rather than
one describing it.
The application every example comes from
Rather than a hello-world that teaches nothing about a real deployment, the examples
are from a working pizza-ordering application:
pizza/
├── pizza-springboot-backend/ Java 21, Spring Boot 4.1, MySQL, Liquibase
├── pizza-react-frontend/ React 19 + Vite
├── pizza-angular-frontend/ Angular 21
└── compose.yaml all of it, one commandSo the multi-stage build is a real Maven build producing a real jar; the layer-caching lesson is
about a real pom.xml; and the compose file starts a database, an API and a web server
that actually serve a menu. Every file quoted in this track was built and run before it was quoted.
The lessons, in order
Foundations
Building images
- Writing a Dockerfile
- The Build Context and .dockerignore
- Layer Caching and Build Speed
- Multi-Stage Builds
- Choosing a Base Image and Keeping It Small
- Don't Run as Root
Running containers
- Ports, Networks and Container DNS
- Volumes, Bind Mounts and Keeping Your Data
- Environment Variables, Build Args and Secrets
- Logs, Exec and Debugging a Container
Compose
- Running a Multi-Container Stack
- depends_on, Healthchecks and Startup Order
- Profiles, Overrides and Multiple Files
- The Whole Application in One File
Shipping