Docker — Ship It The Same Everywhere
You built SplitEase, it runs on your laptop, and then a teammate clones the repo and it explodes — wrong Java version, no Postgres, a missing env var, “works on my machine” turning into a two-hour debugging call. Docker is how you stop that forever: package the app and everything it needs to run into one sealed box that behaves identically on your machine, your teammate’s, the CI server and a rented cloud box. This track teaches you to build that box, run it, wire it to a database, and ship it.
The Goal
By the end of this track you can:
- Explain what a container actually is — an isolated process, not a tiny virtual machine — and why it starts in milliseconds
- Write a correct, small, non-root Dockerfile for the SplitEase Spring Boot API and understand every line
- Run containers fluently — ports, logs, env vars, exec, restart policies — without copy-pasting commands blindly
- Persist data with volumes and connect containers over a network so the app can actually reach Postgres
- Compose the whole SplitEase stack (app + Postgres + Redis) and bring it up with one command
- Push a tagged image to a registry and hand it cleanly to a CI/CD pipeline or a cloud deploy
The Lesson
What Docker actually is
Strip the whale logo and the buzzwords, and Docker is one idea: package your app together with its entire environment into a single image, then run that image as an isolated process called a container — and it runs the same everywhere.
“Its entire environment” is the important part. The image holds your compiled SplitEase JAR and the exact Java 21 runtime it needs, the right OS libraries, the file layout, the startup command — everything except the kernel. When you run it, you get a container: a real process on your machine, but walled off so it can’t see your other files, your other processes, or anyone else’s. To the process inside, it looks like it has the whole machine to itself. To your laptop, it’s just one more program running.
That’s the whole magic. No “but it needs Java 17 and you have 21,” no “did you set the DB password,” no “it needs this one library that’s only on the Linux box.” The box carries all of it.
The “works on my machine” pain — a real story
Picture it concretely. You finish the SplitEase API on a Friday. It runs perfectly: mvn spring-boot:run, Postgres is installed locally on port 5432, your application.properties points at it, your JAVA_HOME is set to Java 21. Done. Green.
Monday, a teammate pulls the repo to add a feature.
- Their machine has Java 17. The build fails on a Java 21 syntax. (30 minutes lost.)
- They install Java 21. Now it builds, but crashes on boot — no Postgres on their machine. (Another 30.)
- They install Postgres. Wrong version, different default password, the schema isn’t there. (An hour.)
- It finally starts — and behaves differently because their OS rounds something your OS didn’t.
That’s a half-day gone, and you’ve now had this exact conversation three times this month. The bug was never in the code. It was in the environment — all the invisible stuff around the code that has to be identical for the code to behave the same.
Docker deletes this entire category of problem. You define the environment once, in a file (the Dockerfile), check it into the repo, and everyone — every teammate, every CI run, every server — runs the same sealed box. “Works on my machine” becomes “works in the container,” and the container is the same machine everywhere. That single guarantee is why Docker is on nearly every backend job description you’ll read.
Where Docker sits — vs CI/CD, Cloud, and Networking
Docker is the packaging and local-runtime layer. It is not your pipeline, it is not your server, and it is not the wire. It’s easy to blur these, so pin them down now — and this track links to its neighbours instead of re-teaching them:
| Layer | What it owns | The track |
|---|---|---|
| Docker | Build the image, run it locally, define the environment | this track |
| CI/CD | A pipeline that builds the image automatically on every push and tests it | CI/CD track |
| Cloud | Rented servers that run your image where users can reach it | Cloud track |
| Networking | TCP, ports, DNS — the actual wire at runtime | Networking track |
The clean mental model of how an image flows from your editor to a running service:
flowchart LR
A["Dockerfile: the recipe you write"] --> B["Image: the sealed, built box"]
B --> C["Registry: the shared shelf, like Docker Hub"]
C --> D["Container: the box running as a live process"]
Read it left to right. You write a Dockerfile (the recipe). Docker builds an image from it (the sealed box — built once, never changes). You push the image to a registry (a shared shelf like Docker Hub or GHCR, so other machines can grab it). Anywhere — your laptop, the CI server, a cloud box — pulls the image and runs it as a container (a live process). Same image in, same behaviour out, every time.
Docker owns the first two boxes and the act of running. CI/CD automates the build-and-push so you don’t do it by hand. Cloud is the machine on the far right that pulls and runs it for real users. Networking is how the bytes actually move once it’s running. Keep those boundaries straight and the whole stack stops feeling like one confusing blob.
This track
Eight modules, in order. Each one containerises or runs SplitEase — the same Spring Boot expense-splitter API you’ve been building — so you’re never learning Docker on a toy.
| Module | What it gives you |
|---|---|
| Docker 01 — What Containers Actually Are | Process isolation, namespaces and cgroups; why a container is NOT a VM; image vs container, made concrete |
| Docker 02 — Images & Layers | How a Dockerfile becomes stacked layers, the union filesystem, and the build cache that makes rebuilds fast |
| Docker 03 — Dockerfile Deep Dive | Every instruction; ENTRYPOINT vs CMD; ordering layers for cache; multi-stage builds; small, non-root images; .dockerignore |
| Docker 04 — Running Containers | The CLI vs the daemon; run, ps, logs, exec, stop, rm; ports, detached mode, env vars, restart policies |
| Docker 05 — Data & Networking | Volumes (named vs bind) and why your data vanishes without them; container networks, service DNS, publishing ports |
| Docker 06 — Docker Compose | The whole SplitEase stack (app + Postgres + Redis) in one file, with depends_on, healthchecks, and one-command up |
| Docker 07 — Images In Production | Registries (push/pull, Docker Hub vs GHCR); tags vs digests; scanning and shrinking images; the CI/CD and Cloud handoff |
| Docker 08 — Capstone: Containerise SplitEase | The full build end to end — Dockerfile, compose, run, push, a definition-of-done checklist, and staged debugging drills |
Start with Docker 01 — once “a container is just an isolated process” clicks, every command in the track stops feeling like memorisation.
Before you start — a quick honesty check
You probably already have some beliefs about Docker, half-true and half from a YouTube thumbnail. Take this now and don’t worry about the score — the point is to see which ideas this track will fix.
Module done? Add it to today’s tracker