{"id":"l-dk-1-1","courseId":"docker-kubernetes","lang":"en","kind":"intro","summary":"Explain what a container actually is (an ordinary Linux process with namespaces, cgroups and an image filesystem) and how images and containers relate.","html":"<p>At 2:40 one morning a Skylane service started failing health checks on every node at once. The code hadn’t changed. The cause was a base library on the build server that had been quietly upgraded, so the binary we shipped no longer matched the one we tested. Containers exist to end that kind of night: you ship the program <em>and</em> everything it needs as one artifact, and the artifact you tested is the artifact that runs.</p>\n<p>I’m Amara Diallo. I run on-call for Skylane’s clusters, and everything in this course is something I’ve either broken or watched break. We’ll work on one service the whole way through.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">The project: notes-api</h2>\n<p>notes-api is a small HTTP API for notes, written in TypeScript and running on Node.js 24. It stores data in Postgres and exposes four routes: <code dir=\"ltr\">GET /notes</code>, <code dir=\"ltr\">POST /notes</code>, <code dir=\"ltr\">GET /healthz</code> (is the process alive?) and <code dir=\"ltr\">GET /readyz</code> (can it reach the database?). It listens on port 3000 and reads its settings from environment variables such as <code dir=\"ltr\">DATABASE_URL</code>.</p>\n<p>By the end you’ll have built its image, run it with Postgres on your laptop, and deployed it to Kubernetes with health checks, autoscaling and a rollback plan.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">A container is a process</h2>\n<p>There is no “container” object inside the Linux kernel. When you run a container, the runtime starts an ordinary process and wraps it with three things:</p>\n<ul>\n<li><strong>Namespaces</strong> change what the process can <em>see</em>: its own process tree (PID 1 is your app), its own network interfaces, its own hostname, its own view of mounted filesystems.</li>\n<li><strong>Control groups (cgroups)</strong> limit what it can <em>use</em>: CPU time, memory, I/O.</li>\n<li><strong>An image</strong> supplies its root filesystem: the Node.js binary, your compiled code, <code dir=\"ltr\">node_modules</code>, a few system libraries.</li>\n</ul>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 300\" role=\"img\" aria-labelledby=\"l-dk-1-1-t1\"><defs><marker id=\"arrow-l-dk-1-1-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-dk-1-1-t1\">Left: three virtual machines, each with its own guest kernel on a hypervisor. Right: three containers, each an isolated process, sharing the host kernel directly.</title>\n  <text class=\"d-label-strong\" x=\"170\" y=\"24\" text-anchor=\"middle\">Virtual machines</text>\n  <text class=\"d-label-strong\" x=\"510\" y=\"24\" text-anchor=\"middle\">Containers</text>\n  <rect class=\"d-box-accent\" x=\"20\" y=\"40\" width=\"92\" height=\"56\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"66\" y=\"73\" text-anchor=\"middle\">app</text>\n  <rect class=\"d-box-accent\" x=\"124\" y=\"40\" width=\"92\" height=\"56\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"170\" y=\"73\" text-anchor=\"middle\">app</text>\n  <rect class=\"d-box-accent\" x=\"228\" y=\"40\" width=\"92\" height=\"56\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"274\" y=\"73\" text-anchor=\"middle\">app</text>\n  <rect class=\"d-box-warn\" x=\"20\" y=\"104\" width=\"92\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"66\" y=\"131\" text-anchor=\"middle\">guest OS</text>\n  <rect class=\"d-box-warn\" x=\"124\" y=\"104\" width=\"92\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"170\" y=\"131\" text-anchor=\"middle\">guest OS</text>\n  <rect class=\"d-box-warn\" x=\"228\" y=\"104\" width=\"92\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"274\" y=\"131\" text-anchor=\"middle\">guest OS</text>\n  <rect class=\"d-box\" x=\"20\" y=\"158\" width=\"300\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"170\" y=\"185\" text-anchor=\"middle\">hypervisor</text>\n  <rect class=\"d-box\" x=\"20\" y=\"212\" width=\"300\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"170\" y=\"239\" text-anchor=\"middle\">host kernel + hardware</text>\n  <rect class=\"d-box-primary\" x=\"360\" y=\"40\" width=\"92\" height=\"108\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"406\" y=\"88\" text-anchor=\"middle\">process</text>\n  <text class=\"d-label-muted\" x=\"406\" y=\"108\" text-anchor=\"middle\">+ image</text>\n  <rect class=\"d-box-primary\" x=\"464\" y=\"40\" width=\"92\" height=\"108\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"510\" y=\"88\" text-anchor=\"middle\">process</text>\n  <text class=\"d-label-muted\" x=\"510\" y=\"108\" text-anchor=\"middle\">+ image</text>\n  <rect class=\"d-box-primary\" x=\"568\" y=\"40\" width=\"92\" height=\"108\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"614\" y=\"88\" text-anchor=\"middle\">process</text>\n  <text class=\"d-label-muted\" x=\"614\" y=\"108\" text-anchor=\"middle\">+ image</text>\n  <rect class=\"d-box-success\" x=\"360\" y=\"158\" width=\"300\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"510\" y=\"185\" text-anchor=\"middle\">namespaces + cgroups</text>\n  <rect class=\"d-box\" x=\"360\" y=\"212\" width=\"300\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"510\" y=\"239\" text-anchor=\"middle\">host kernel + hardware</text>\n  <text class=\"d-label-muted\" x=\"170\" y=\"286\" text-anchor=\"middle\">seconds to boot, strong isolation</text>\n  <text class=\"d-label-muted\" x=\"510\" y=\"286\" text-anchor=\"middle\">milliseconds to start, shared kernel</text>\n</svg><figcaption>Containers share one kernel; VMs each bring their own</figcaption></figure>\n<p>Because there’s no guest OS to boot, a container starts as fast as the program inside it. The trade-off is that every container on a host shares one kernel. A VM is a stronger wall; a container is a well-locked door. That’s why later lessons insist on non-root users and small images: you shrink what an attacker can do if they get through the door.</p>\n<p>On macOS and Windows, Docker Desktop runs one lightweight Linux VM and puts your containers inside it. The model is the same; there’s one extra layer underneath.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Image versus container</h2>\n<p>An <strong>image</strong> is a read-only, layered snapshot of a filesystem plus metadata: which command to run, which port the app expects, which user to run as. A <strong>container</strong> is a running instance of an image with a thin writable layer on top.</p>\n<p>The class-and-object comparison holds up well: one image, many containers. When we scale notes-api to six replicas in Kubernetes, that’s one image and six containers. Each container’s writable layer is private and disposable, so the rule from day one is: <strong>containers are cattle, data lives elsewhere</strong>. Postgres will keep its files in a volume, never in a container’s writable layer.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Treating a container like a server</span></p>\n<p>Teams new to containers <code dir=\"ltr\">docker exec</code> into a running container, patch a config file and move on. The next deploy replaces the container and the fix vanishes, usually during an incident. Change the image or the configuration you pass in, never the running container.</p>\n</aside>\n<p>Next you’ll write notes-api’s first Dockerfile and turn source code into an image you can run.</p>\n","toc":[{"id":"sec-1","text":"The project: notes-api","level":2},{"id":"sec-2","text":"A container is a process","level":2},{"id":"sec-3","text":"Image versus container","level":2}],"takeaways":["A container is a normal Linux process that the kernel isolates with namespaces and limits with cgroups.","Containers share the host kernel, which is why they start in milliseconds and why they are a weaker boundary than a virtual machine.","An image is a read-only template of filesystem layers plus metadata; a container is one running instance of it with a thin writable layer on top.","Anything a container writes to its own filesystem disappears when the container is removed unless you put it in a volume."],"further":[{"title":"What is a container?","url":"https://docs.docker.com/get-started/docker-concepts/the-basics/what-is-a-container/"},{"title":"What is an image?","url":"https://docs.docker.com/get-started/docker-concepts/the-basics/what-is-an-image/"},{"title":"Kubernetes Overview","url":"https://kubernetes.io/docs/concepts/overview/"}],"quiz":[{"q":"<p>You run <code dir=\"ltr\">ps aux</code> on a Linux host while a container is running a Node.js server. What do you see?</p>","options":[{"text":"Nothing, because the container runs inside its own small virtual machine.","why":"Docker on Linux doesn’t start a VM per container. Docker Desktop on macOS and Windows runs one shared Linux VM, but the containers inside it are still plain processes."},{"text":"Only a <code dir=\"ltr\">dockerd</code> process; the Node.js server is hidden inside it.","why":"The daemon starts containers but doesn’t host them. The container’s process is its own entry in the host’s process table."},{"text":"The <code dir=\"ltr\">node</code> process, listed like any other process on the host.","why":"Correct. A container is a host process. Namespaces hide the rest of the host from it, not the other way round."},{"text":"A copy of the container’s whole operating system booting in the background.","why":"Containers don’t boot an OS. They reuse the host kernel and start only the command you asked for."}],"answer":2},{"q":"<p>Which kernel feature stops a runaway container from using all of the host’s memory?</p>","options":[{"text":"Control groups (cgroups), which cap and account for CPU, memory and I/O.","why":"Correct. cgroups enforce resource limits; later in the course Kubernetes limits compile down to exactly these settings."},{"text":"The PID namespace, which gives the container its own process tree.","why":"Namespaces control what a process can see, not how much it can use. A process in its own PID namespace can still eat every byte of RAM."},{"text":"The image’s read-only layers, which can’t grow at runtime.","why":"Read-only layers limit what’s on disk, not memory use. A process can allocate RAM regardless of how its files are stored."},{"text":"The container’s writable layer, which is sized to the memory limit.","why":"The writable layer is disk storage for file changes. It has nothing to do with RAM."}],"answer":0},{"q":"<p>You start three containers from the image <code dir=\"ltr\">notes-api:1.0.0</code>. One of them writes a file to <code dir=\"ltr\">/tmp/cache.json</code>. What do the other two see?</p>","options":[{"text":"The new file, because all three share the image’s filesystem.","why":"They share the read-only image layers, but each container writes to its own private writable layer."},{"text":"The new file after a restart, once Docker syncs the layers.","why":"Docker never syncs writable layers between containers. That’s what volumes are for."},{"text":"An error, because image filesystems are read-only.","why":"Containers get a writable layer on top of the image, so writes succeed; they’re just private and temporary."},{"text":"Nothing new; the file exists only in that one container’s writable layer.","why":"Correct. Each container is an instance with its own thin writable layer. Remove the container and the file is gone too."}],"answer":3},{"q":"<p>Why is a container a weaker security boundary than a virtual machine?</p>","options":[{"text":"Containers can’t restrict network access at all.","why":"Network namespaces give each container its own network stack, and you can restrict it further. Networking is not the gap."},{"text":"All containers on a host share one kernel, so a kernel exploit can cross between them.","why":"Correct. A VM has its own kernel behind a hypervisor; a container talks straight to the host kernel. That’s the price of millisecond start-up."},{"text":"Container images can’t be signed or verified.","why":"Images can be signed and pinned by digest; that is image supply-chain security, not an isolation boundary."},{"text":"Containers always run as root on the host.","why":"Many do by default, which is bad, but you can and should run them as a non-root user. The shared kernel is the structural difference."}],"answer":1}],"exercise":{"kind":"choice","multiple":true,"options":["shared-kernel","own-writable","vm-each","cgroups-see","image-many"],"correct":["shared-kernel","own-writable","image-many"],"title":"True or false: what a container is","prompt":"<p>A teammate is writing an onboarding doc for notes-api and asks you to review five claims about containers. Select <strong>every</strong> statement that is correct.</p>\n","hints":["Ask of each claim: is this about what a process can see, what it can use, or what’s on its disk?","Namespaces are about visibility; cgroups are about resource limits.","Three of the five are correct."],"explanation":"<p>Containers are host processes that share the host kernel, which is why they start fast and why isolation is weaker than a VM. Each container gets its own writable layer on top of the shared read-only image, and one image can back any number of containers. Docker on Linux does not create a VM per container, and cgroups limit resource <em>use</em>; it’s namespaces that control what a process can <em>see</em>.</p>\n","checkLabels":{},"optionText":{"shared-kernel":{"text":"Every container on a Linux host uses the same kernel as the host.","why":"Correct. There is no guest kernel. That is the source of both the speed and the weaker isolation."},"own-writable":{"text":"Each container gets its own writable layer; changes there vanish when the container is removed.","why":"Correct. That’s why data that must survive goes in a volume."},"vm-each":{"text":"Docker creates a small virtual machine for each container so they can’t affect each other.","why":"Not on Linux. Docker Desktop uses one shared VM on macOS and Windows, but containers inside it are still processes on one kernel."},"cgroups-see":{"text":"cgroups decide which files and processes a container is allowed to see.","why":"That’s namespaces. cgroups cap and account for CPU, memory and I/O."},"image-many":{"text":"One image can be the template for many running containers at once.","why":"Correct. Scaling to six replicas means six containers from one image."}}},"words":559,"readMinutes":3}