Docker
Read the transcript
1. No VM, no guest kernel: what a container and an image actually are
Host: So let’s start with the thing people get wrong on day one: what actually is a container? Most folks picture a tiny virtual machine, a little box with its own mini operating system inside. Is that even close?
Guest: Not really, and that misconception causes half the confusion later. A container is just a regular process running on the host kernel — there’s no guest kernel, no hypervisor, nothing virtualized underneath it. What makes it look like a separate machine is two kernel features: namespaces, which restrict what the process can see — its own PIDs, network, mounts, users — and cgroups, which restrict what it can consume, like CPU and memory. That’s it. That’s the whole trick, which is also why containers start in milliseconds.
Host: Okay, so if the container itself is just a fenced-in process, what’s the image sitting behind it — is that the ‘disk’ for this pretend machine?
Guest: Sort of, but again simpler than it sounds. An image is a stack of read-only filesystem layers plus some metadata, where each layer is just the filesystem delta produced by one build instruction. Once you see it that way, most build headaches stop being mysterious rules to memorize and become one question: which layer changed, and what did the cache have to throw away because of it. Everything we’re going to talk about today — caching, PID 1 weirdness, why containers are insecure by default — all of it traces back to these two ideas: restricted process, layered filesystem.
2. Why builds are slow, why ‘RUN rm’ lies, and how multi-stage fixes both
Host: So if a layer is just the delta from one instruction, that means the order I write my Dockerfile instructions in isn’t just style, it’s actually deciding how much gets rebuilt every time. Walk me through why that’s such a big deal in practice.
Guest: Right — the cache rule is brutal and simple: the first changed layer invalidates itself and every single layer after it, no matter how trivial the change was. So the classic mistake is copying your entire source tree first, then installing dependencies. Now every source edit, even a comment change, reinvalidates the dependency install, and you’re reinstalling packages that never changed. Flip it — copy just the lockfile, install dependencies, then copy source — and that expensive layer stays cached, since layers are reused whenever the instruction and its inputs are unchanged. And none of that helps if your build context itself is huge, which is why a missing .dockerignore is usually the actual reason someone’s build is slow: the whole directory, node_modules and all, gets shipped to the daemon before the build even starts.
Host: Okay, and that same layer logic is exactly why ‘RUN rm’ feels like a lie, isn’t it — you delete the file but the image doesn’t get smaller.
Guest: Exactly, because the earlier layer that created the file is still sitting in the stack, immutable, content-addressed, untouched. Your delete just adds a new layer on top saying ‘this file is gone,’ but the bytes are still shipped and stored underneath. The fix is either delete it in the same RUN instruction that created it, or better, go multi-stage: build everything — compilers, dev headers, caches — in one throwaway stage, then copy only the final artifact into a clean minimal stage. That’s not a cosmetic trick, that’s routinely an order-of-magnitude size drop, because the entire toolchain that produced your binary simply never makes it into the image people actually pull and run.
3. PID 1, root by default, and the shutdown that silently becomes a SIGKILL
Host: So walk me through something that’s bitten a lot of people: you send a graceful shutdown, your app has drain logic and everything, and the container just gets killed anyway. What’s actually going on there?
Guest: It comes down to two facts colliding. PID 1 in Linux gets no default signal handlers at all, so if your CMD is written in shell form, Docker wraps it in a shell, that shell becomes PID 1, and it typically just doesn’t forward SIGTERM to your app. Docker sends the stop signal, waits its default ten seconds, hears nothing back, and SIGKILLs the whole thing — your drain logic never even ran. Exec form, the array syntax, makes your process PID 1 directly so it actually receives the signal it’s supposed to handle.
Host: So it’s the same pattern every time — nobody explicitly decided the behavior, so you get whatever the kernel or the daemon does by default. Is that the same story behind containers running as root, wide-open egress, pulling ‘latest’, secrets sitting in build args?
4. Where Docker’s guarantees stop
Guest: Exactly the same story, and it’s worth saying plainly: every one of those is a default nobody chose, not a design goal. The daemon doesn’t have an opinion, so you inherit whatever’s easiest to implement, not whatever’s safest to run. That’s the whole lesson of treating a container as a restricted process instead of a magic box — the restrictions are only the ones you actually asked for.
Host: And that’s also where a single container’s promises just run out — one process, one lifecycle, one image can’t tell you how to schedule across machines, enforce resource limits, or roll out safely, that’s a different layer entirely. So if you want the rest of the story, that’s Kubernetes and the cloud modules — scheduling, drains done right, registries and scanning. For now, the mental model stands: it’s not a tiny VM, it’s a process with guardrails, and every mystery we covered today was just that guardrail, or the absence of one. Thanks for walking through it.
Not covered
The planner wanted these and found nothing in the source to support them:
- A live demo of building and running a Dockerfile step by step
- Comparing Docker to other container runtimes like Podman or containerd
- Docker Compose or multi-container local development workflows
Generated from this page by Claude Sonnet 5 on , spoken by Kokoro-82M running locally. Two synthetic voices, not a recorded conversation. Every claim is drawn from this page — where it differs from the text above, the text is correct.
At a Glance
Section titled “At a Glance”A container is a process on the host kernel with restricted visibility — namespaces limit what it can see, cgroups limit what it can consume. There is no virtual machine and no guest kernel. That single fact explains both why containers start in milliseconds and why kernel-level isolation is weaker than a VM’s.
An image is a stack of read-only filesystem layers plus metadata. Almost every build problem is really a question about which layer changed and therefore what the cache had to discard.
Key Concepts
Section titled “Key Concepts”| Concept | What it actually is |
|---|---|
| Namespace | Kernel isolation of what a process sees: PID, network, mount, user, UTS, IPC |
| cgroup | Kernel limits on what it consumes: CPU, memory, I/O |
| Image layer | The filesystem delta from one build instruction, content-addressed and shared between images |
| Build cache | Layers reused when the instruction and its inputs are unchanged. Invalidated for every layer after the first change |
| Multi-stage build | Build in one stage, copy only the artifacts into a minimal final stage |
ENTRYPOINT vs CMD |
Entrypoint is the executable; command is the default arguments, overridable at docker run |
| Exec vs shell form | CMD ["a","b"] runs directly as PID 1; CMD a b wraps in a shell, which usually swallows signals |
| PID 1 | The first process gets no default signal handlers and does not reap orphans |
| Volume vs bind mount | A managed volume versus a host path; the latter carries host permissions and paths into the container |
.dockerignore |
Excludes files from the build context. Missing it is the usual reason a build is slow |
| Distroless / scratch | Final images with no shell or package manager — smaller and far less to attack |
| OCI | The image and runtime specification, which is why images are portable beyond Docker |
Numbers That Matter
Section titled “Numbers That Matter”| Quantity | Value | Why it matters |
|---|---|---|
| Guest kernels | 0 | Containers share the host kernel; a kernel exploit crosses the boundary |
| Layers invalidated by a change | That one and everything after | Instruction order determines cache hit rate |
| Default PID 1 signal handling | None | Without an init or explicit handling, SIGTERM is ignored and shutdown becomes SIGKILL |
Default docker stop grace |
10 seconds | Then SIGKILL. In-flight requests need draining faster than that, or a longer timeout |
| Default container user | root |
Unless a USER instruction says otherwise |
| Default network | Bridge, all ports outbound | Egress is unrestricted unless you restrict it |
| Build context | Everything in the directory | Sent to the daemon before the build starts, .dockerignore notwithstanding |
| Layer sharing | By content digest | Identical base layers are stored and pulled once across images |
| Typical size reduction, multi-stage | Often an order of magnitude | Compilers and build dependencies never reach the final image |
Common Gotchas
Section titled “Common Gotchas”- Copying source before installing dependencies destroys the cache. Copy the lockfile, install, then copy source — otherwise every source edit reinstalls everything.
RUN rmdoes not shrink the image. The file still exists in the earlier layer. Remove it in the sameRUNthat created it, or use a multi-stage build.- Shell-form
CMDbreaks graceful shutdown. The shell becomes PID 1 and often does not forwardSIGTERM, so your drain logic never runs and the container is killed mid-request. latestis not a version. It is a mutable tag, so two builds a week apart can produce different images from identical Dockerfiles. Pin by digest for anything reproducible.- Containers default to root. A container escape then starts as root on the host. Add a
USER. - Secrets in build arguments persist in image history, readable by anyone with the image. Use build secrets or inject at runtime.
- Zombie processes accumulate without an init. PID 1 is expected to reap orphans; most
application processes do not, which is what
--initexists for. - A container’s memory limit is not what the process sees. Runtimes that read host memory rather than the cgroup limit will over-allocate and get OOM-killed.
- Architecture mismatches surface at runtime. An image built on arm64 fails on amd64 unless it is multi-arch, and the error rarely says so plainly.
Where to Go Deeper
Section titled “Where to Go Deeper”- Module 10: Kubernetes — scheduling, resource requests and limits, and what an orchestrator adds on top of these primitives.
- Module 1 → Production Example — graceful drain, which is exactly what PID 1 signal handling either allows or silently breaks.
- Module 11: Cloud — where images are stored, scanned, and deployed.
- Kubernetes — the next layer up.