[{"data":1,"prerenderedAt":871},["ShallowReactive",2],{"mdc-rhsys7-key":3,"mdc-2wfu6i-key":30,"mdc--ivecnx-key":83,"mdc-txzm0x-key":121,"mdc-h2vlwm-key":133,"mdc--esk3hb-key":537,"mdc--b2tkv5-key":547,"mdc-5v5muz-key":643,"mdc-nzmdo3-key":818},{"data":4,"body":5},{},{"type":6,"children":7},"root",[8,25],{"type":9,"tag":10,"props":11,"children":12},"element","p",{},[13,16,23],{"type":14,"value":15},"text","Docker's most under-appreciated feature is that ",{"type":9,"tag":17,"props":18,"children":20},"code",{"className":19},[],[21],{"type":14,"value":22},"docker rm",{"type":14,"value":24}," works. Nothing you built in this course cleans itself up: the bridge stays, the namespace stays, the cgroup stays, the loopback mount stays, and the iptables rules stay until the machine reboots.",{"type":9,"tag":10,"props":26,"children":27},{},[28],{"type":14,"value":29},"Taking it apart is the last piece of understanding — because the teardown order tells you what depends on what.",{"data":31,"body":32},{},{"type":6,"children":33},[34,41,53,74],{"type":9,"tag":35,"props":36,"children":38},"h2",{"id":37},"tear-it-down-in-reverse",[39],{"type":14,"value":40},"Tear it down in reverse",{"type":9,"tag":42,"props":43,"children":47},"pre",{"className":44,"code":46,"language":14},[45],"language-text","# 1. The cgroup\nsudo cgdelete -g cpu,memory:/$CONTAINER_ID\n\n# 2. The network namespace (this destroys veth_cont with it)\nsudo ip netns delete netns_$CONTAINER_ID\n\n# 3. The host end of the cable, and the bridge\nsudo ip link delete veth_host\nsudo ip link set bridge0 down && sudo ip link delete bridge0\n\n# 4. The container's filesystem, then the loopback mount\nsudo btrfs subvolume delete ./btrfs-mount/$CONTAINER_ID\nsudo umount ./btrfs-mount\nrm -rf ./btrfs-mount && rm -f ./btrfs-disk.img alpine.tar.gz\n",[48],{"type":9,"tag":17,"props":49,"children":51},{"__ignoreMap":50},"",[52],{"type":14,"value":46},{"type":9,"tag":10,"props":54,"children":55},{},[56,58,64,66,72],{"type":14,"value":57},"The iptables rules need removing separately — ",{"type":9,"tag":17,"props":59,"children":61},{"className":60},[],[62],{"type":14,"value":63},"-D",{"type":14,"value":65}," with the same arguments as the ",{"type":9,"tag":17,"props":67,"children":69},{"className":68},[],[70],{"type":14,"value":71},"-A",{"type":14,"value":73}," that added them:",{"type":9,"tag":42,"props":75,"children":78},{"className":76,"code":77,"language":14},[45],"sudo iptables -t nat -D POSTROUTING -o $DIF -j MASQUERADE\nsudo iptables -D FORWARD -i $BRIDGE_IFACE -o $DIF -j ACCEPT\nsudo iptables -D FORWARD -o $BRIDGE_IFACE -m state --state RELATED,ESTABLISHED -j ACCEPT\n",[79],{"type":9,"tag":17,"props":80,"children":81},{"__ignoreMap":50},[82],{"type":14,"value":77},{"data":84,"body":85},{},{"type":6,"children":86},[87,92],{"type":9,"tag":88,"props":89,"children":91},"terminal-teaser",{":lines":90},"[{\"cmd\":\"ip netns list\",\"out\":\"\"},{\"cmd\":\"ip link show bridge0\",\"out\":\"Device \\\"bridge0\\\" does not exist.\"},{\"cmd\":\"ls /sys/fs/cgroup/my-container\",\"out\":\"ls: cannot access ... No such file or directory\"},{\"cmd\":\"mount | grep btrfs-mount\",\"out\":\"\"}]",[],{"type":9,"tag":10,"props":93,"children":94},{},[95,97,103,105,111,113,119],{"type":14,"value":96},"Two details worth noticing. Deleting the network namespace takes ",{"type":9,"tag":17,"props":98,"children":100},{"className":99},[],[101],{"type":14,"value":102},"veth_cont",{"type":14,"value":104}," with it, because an interface cannot outlive the namespace it lives in — and deleting either end of a veth pair destroys both. And the btrfs subvolume needs ",{"type":9,"tag":17,"props":106,"children":108},{"className":107},[],[109],{"type":14,"value":110},"btrfs subvolume delete",{"type":14,"value":112},", not ",{"type":9,"tag":17,"props":114,"children":116},{"className":115},[],[117],{"type":14,"value":118},"rm -rf",{"type":14,"value":120},": it is a filesystem object, not a directory.",{"data":122,"body":123},{},{"type":6,"children":124},[125],{"type":9,"tag":126,"props":127,"children":132},"quiz",{":answer":128,":options":129,"explanation":130,"question":131},"0","[\"It fails: a cgroup with member processes cannot be removed until they exit or are moved out\",\"It succeeds and kills the processes\",\"It succeeds and the processes silently lose their limits\"]","`rmdir` on a non-empty cgroup returns EBUSY. This is why a container runtime kills the process first and removes the cgroup second, and why leftover cgroups on a host usually mean a process nobody noticed is still running in one.","Why must the cgroup be deleted before the processes in it are gone — or rather, what happens if you try while a process is still inside?",[],{"data":134,"body":135},{},{"type":6,"children":136},[137,143,148,494],{"type":9,"tag":35,"props":138,"children":140},{"id":139},"the-debugging-map",[141],{"type":14,"value":142},"The debugging map",{"type":9,"tag":10,"props":144,"children":145},{},[146],{"type":14,"value":147},"This is what the course was actually for. Six layers, and every container problem you will meet belongs to one of them:",{"type":9,"tag":149,"props":150,"children":151},"table",{},[152,176],{"type":9,"tag":153,"props":154,"children":155},"thead",{},[156],{"type":9,"tag":157,"props":158,"children":159},"tr",{},[160,166,171],{"type":9,"tag":161,"props":162,"children":163},"th",{},[164],{"type":14,"value":165},"Symptom",{"type":9,"tag":161,"props":167,"children":168},{},[169],{"type":14,"value":170},"Layer",{"type":9,"tag":161,"props":172,"children":173},{},[174],{"type":14,"value":175},"Where to look",{"type":9,"tag":177,"props":178,"children":179},"tbody",{},[180,199,216,233,265,283,300,324,347,391,414,438,471],{"type":9,"tag":157,"props":181,"children":182},{},[183,189,194],{"type":9,"tag":184,"props":185,"children":186},"td",{},[187],{"type":14,"value":188},"\"No such file or directory\" for a binary that exists",{"type":9,"tag":184,"props":190,"children":191},{},[192],{"type":14,"value":193},"filesystem",{"type":9,"tag":184,"props":195,"children":196},{},[197],{"type":14,"value":198},"wrong image, missing shared library, wrong architecture",{"type":9,"tag":157,"props":200,"children":201},{},[202,207,211],{"type":9,"tag":184,"props":203,"children":204},{},[205],{"type":14,"value":206},"Config file changes vanish on restart",{"type":9,"tag":184,"props":208,"children":209},{},[210],{"type":14,"value":193},{"type":9,"tag":184,"props":212,"children":213},{},[214],{"type":14,"value":215},"written to the writable layer, not a volume",{"type":9,"tag":157,"props":217,"children":218},{},[219,224,228],{"type":9,"tag":184,"props":220,"children":221},{},[222],{"type":14,"value":223},"Image far bigger than expected",{"type":9,"tag":184,"props":225,"children":226},{},[227],{"type":14,"value":193},{"type":9,"tag":184,"props":229,"children":230},{},[231],{"type":14,"value":232},"files deleted in a later layer are still shipped",{"type":9,"tag":157,"props":234,"children":235},{},[236,241,254],{"type":9,"tag":184,"props":237,"children":238},{},[239],{"type":14,"value":240},"Can reach IPs but not hostnames",{"type":9,"tag":184,"props":242,"children":243},{},[244,246,252],{"type":14,"value":245},"filesystem ",{"type":9,"tag":247,"props":248,"children":249},"em",{},[250],{"type":14,"value":251},"and",{"type":14,"value":253}," network",{"type":9,"tag":184,"props":255,"children":256},{},[257,263],{"type":9,"tag":17,"props":258,"children":260},{"className":259},[],[261],{"type":14,"value":262},"/etc/resolv.conf",{"type":14,"value":264}," in the image; the DNS server it names",{"type":9,"tag":157,"props":266,"children":267},{},[268,273,278],{"type":9,"tag":184,"props":269,"children":270},{},[271],{"type":14,"value":272},"Two containers can't see each other",{"type":9,"tag":184,"props":274,"children":275},{},[276],{"type":14,"value":277},"network",{"type":9,"tag":184,"props":279,"children":280},{},[281],{"type":14,"value":282},"different networks, or the default bridge's lack of embedded DNS",{"type":9,"tag":157,"props":284,"children":285},{},[286,291,295],{"type":9,"tag":184,"props":287,"children":288},{},[289],{"type":14,"value":290},"Published port unreachable",{"type":9,"tag":184,"props":292,"children":293},{},[294],{"type":14,"value":277},{"type":9,"tag":184,"props":296,"children":297},{},[298],{"type":14,"value":299},"DNAT rule, or the process bound to 127.0.0.1 inside",{"type":9,"tag":157,"props":301,"children":302},{},[303,308,313],{"type":9,"tag":184,"props":304,"children":305},{},[306],{"type":14,"value":307},"Container reaches the host but not the internet",{"type":9,"tag":184,"props":309,"children":310},{},[311],{"type":14,"value":312},"NAT",{"type":9,"tag":184,"props":314,"children":315},{},[316,322],{"type":9,"tag":17,"props":317,"children":319},{"className":318},[],[320],{"type":14,"value":321},"ip_forward",{"type":14,"value":323},", the MASQUERADE rule, the FORWARD chain",{"type":9,"tag":157,"props":325,"children":326},{},[327,332,336],{"type":9,"tag":184,"props":328,"children":329},{},[330],{"type":14,"value":331},"Random packet loss under load",{"type":9,"tag":184,"props":333,"children":334},{},[335],{"type":14,"value":312},{"type":9,"tag":184,"props":337,"children":338},{},[339,341],{"type":14,"value":340},"conntrack table full — check ",{"type":9,"tag":17,"props":342,"children":344},{"className":343},[],[345],{"type":14,"value":346},"dmesg",{"type":9,"tag":157,"props":348,"children":349},{},[350,361,366],{"type":9,"tag":184,"props":351,"children":352},{},[353,355],{"type":14,"value":354},"Exit code 137, ",{"type":9,"tag":17,"props":356,"children":358},{"className":357},[],[359],{"type":14,"value":360},"OOMKilled",{"type":9,"tag":184,"props":362,"children":363},{},[364],{"type":14,"value":365},"cgroups",{"type":9,"tag":184,"props":367,"children":368},{},[369,375,377,383,385],{"type":9,"tag":17,"props":370,"children":372},{"className":371},[],[373],{"type":14,"value":374},"memory.max",{"type":14,"value":376}," vs ",{"type":9,"tag":17,"props":378,"children":380},{"className":379},[],[381],{"type":14,"value":382},"memory.current",{"type":14,"value":384},", and ",{"type":9,"tag":17,"props":386,"children":388},{"className":387},[],[389],{"type":14,"value":390},"memory.events",{"type":9,"tag":157,"props":392,"children":393},{},[394,399,403],{"type":9,"tag":184,"props":395,"children":396},{},[397],{"type":14,"value":398},"Latency spikes on an idle host",{"type":9,"tag":184,"props":400,"children":401},{},[402],{"type":14,"value":365},{"type":9,"tag":184,"props":404,"children":405},{},[406,412],{"type":9,"tag":17,"props":407,"children":409},{"className":408},[],[410],{"type":14,"value":411},"cpu.max",{"type":14,"value":413}," throttling at period boundaries",{"type":9,"tag":157,"props":415,"children":416},{},[417,422,427],{"type":9,"tag":184,"props":418,"children":419},{},[420],{"type":14,"value":421},"Zombie processes accumulating",{"type":9,"tag":184,"props":423,"children":424},{},[425],{"type":14,"value":426},"namespaces",{"type":9,"tag":184,"props":428,"children":429},{},[430,432],{"type":14,"value":431},"PID 1 not reaping — needs an init, or ",{"type":9,"tag":17,"props":433,"children":435},{"className":434},[],[436],{"type":14,"value":437},"--init",{"type":9,"tag":157,"props":439,"children":440},{},[441,454,458],{"type":9,"tag":184,"props":442,"children":443},{},[444,446,452],{"type":14,"value":445},"Container ignores ",{"type":9,"tag":17,"props":447,"children":449},{"className":448},[],[450],{"type":14,"value":451},"docker stop",{"type":14,"value":453}," for 10s",{"type":9,"tag":184,"props":455,"children":456},{},[457],{"type":14,"value":426},{"type":9,"tag":184,"props":459,"children":460},{},[461,463,469],{"type":14,"value":462},"PID 1 has no ",{"type":9,"tag":17,"props":464,"children":466},{"className":465},[],[467],{"type":14,"value":468},"SIGTERM",{"type":14,"value":470}," handler",{"type":9,"tag":157,"props":472,"children":473},{},[474,485,489],{"type":9,"tag":184,"props":475,"children":476},{},[477,483],{"type":9,"tag":17,"props":478,"children":480},{"className":479},[],[481],{"type":14,"value":482},"ps",{"type":14,"value":484}," inside shows host processes",{"type":9,"tag":184,"props":486,"children":487},{},[488],{"type":14,"value":426},{"type":9,"tag":184,"props":490,"children":491},{},[492],{"type":14,"value":493},"sharing the host PID namespace",{"type":9,"tag":10,"props":495,"children":496},{},[497,499,505,507,513,514,520,522,528,529,535],{"type":14,"value":498},"Every row is a place you now know how to inspect directly: ",{"type":9,"tag":17,"props":500,"children":502},{"className":501},[],[503],{"type":14,"value":504},"/proc/PID/ns/",{"type":14,"value":506},", ",{"type":9,"tag":17,"props":508,"children":510},{"className":509},[],[511],{"type":14,"value":512},"ip netns exec",{"type":14,"value":506},{"type":9,"tag":17,"props":515,"children":517},{"className":516},[],[518],{"type":14,"value":519},"iptables -L -n -v",{"type":14,"value":521}," with its packet counters, ",{"type":9,"tag":17,"props":523,"children":525},{"className":524},[],[526],{"type":14,"value":527},"/sys/fs/cgroup/*/memory.events",{"type":14,"value":506},{"type":9,"tag":17,"props":530,"children":532},{"className":531},[],[533],{"type":14,"value":534},"btrfs subvolume list",{"type":14,"value":536},".",{"data":538,"body":539},{},{"type":6,"children":540},[541],{"type":9,"tag":126,"props":542,"children":546},{":answer":128,":options":543,"explanation":544,"question":545},"[\"The filesystem — `/etc/resolv.conf` inside the container's root, or the resolver it points at\",\"The network namespace, which is missing a route\",\"cgroups, which are throttling the DNS lookups\"]","Reaching an IP proves the namespace, routing, and NAT are all working. Name resolution is a file in the root filesystem naming a server, plus that server being reachable — which is why this symptom sends you to the image, not the network.","A container can `ping 8.8.8.8` but every hostname fails to resolve. Which layer is at fault?",[],{"data":548,"body":549},{},{"type":6,"children":550},[551,557,562,618,623],{"type":9,"tag":35,"props":552,"children":554},{"id":553},"what-you-actually-built",[555],{"type":14,"value":556},"What you actually built",{"type":9,"tag":10,"props":558,"children":559},{},[560],{"type":14,"value":561},"A container is a process with:",{"type":9,"tag":563,"props":564,"children":565},"ul",{},[566,580,589,600,609],{"type":9,"tag":567,"props":568,"children":569},"li",{},[570,572,578],{"type":14,"value":571},"a ",{"type":9,"tag":573,"props":574,"children":575},"strong",{},[576],{"type":14,"value":577},"root filesystem",{"type":14,"value":579}," of its own, cheaply cloned from a shared base by copy-on-write",{"type":9,"tag":567,"props":581,"children":582},{},[583,587],{"type":9,"tag":573,"props":584,"children":585},{},[586],{"type":14,"value":426},{"type":14,"value":588}," limiting what it can see — processes, mounts, hostname, IPC, network",{"type":9,"tag":567,"props":590,"children":591},{},[592,593,598],{"type":14,"value":571},{"type":9,"tag":573,"props":594,"children":595},{},[596],{"type":14,"value":597},"network stack",{"type":14,"value":599}," of its own, wired to the host by a virtual cable and a bridge",{"type":9,"tag":567,"props":601,"children":602},{},[603,607],{"type":9,"tag":573,"props":604,"children":605},{},[606],{"type":14,"value":312},{"type":14,"value":608}," translating its private address on the way out",{"type":9,"tag":567,"props":610,"children":611},{},[612,616],{"type":9,"tag":573,"props":613,"children":614},{},[615],{"type":14,"value":365},{"type":14,"value":617}," capping what it can consume",{"type":9,"tag":10,"props":619,"children":620},{},[621],{"type":14,"value":622},"Namespaces for isolation, cgroups for limits, copy-on-write for cheap images. Everything above that — image formats, registries, layer caching, health checks, restart policies, service discovery, schedulers — is ergonomics and orchestration built on those three ideas.",{"type":9,"tag":10,"props":624,"children":625},{},[626,628,634,636,642],{"type":14,"value":627},"That is not a dismissal. The ergonomics are the reason anyone uses containers at all, and the nine commands you ran by hand in lesson 8 are nine chances to get it wrong in production. But knowing that the abstraction is this small is what changes how you debug: a container that misbehaves is not a black box, it is a process with a known set of kernel objects attached, and every one of them can be read from ",{"type":9,"tag":17,"props":629,"children":631},{"className":630},[],[632],{"type":14,"value":633},"/proc",{"type":14,"value":635}," and ",{"type":9,"tag":17,"props":637,"children":639},{"className":638},[],[640],{"type":14,"value":641},"/sys",{"type":14,"value":536},{"data":644,"body":645},{},{"type":6,"children":646},[647],{"type":9,"tag":648,"props":649,"children":651},"deep-dive",{"title":650},"Where to go next",[652,681,722,774,784],{"type":9,"tag":10,"props":653,"children":654},{},[655,660,662,671,673,679],{"type":9,"tag":573,"props":656,"children":657},{},[658],{"type":14,"value":659},"Read a runtime's config.",{"type":14,"value":661}," The ",{"type":9,"tag":663,"props":664,"children":668},"a",{"href":665,"rel":666},"https://github.com/opencontainers/runtime-spec",[667],"nofollow",[669],{"type":14,"value":670},"OCI runtime specification",{"type":14,"value":672}," defines ",{"type":9,"tag":17,"props":674,"children":676},{"className":675},[],[677],{"type":14,"value":678},"config.json",{"type":14,"value":680}," — the full description of a container as namespaces, mounts, cgroup limits, capabilities, and a seccomp profile. It is readable in an afternoon, and every field maps onto something in this course.",{"type":9,"tag":10,"props":682,"children":683},{},[684,697,699,705,707,712,714,720],{"type":9,"tag":573,"props":685,"children":686},{},[687,689,695],{"type":14,"value":688},"Run ",{"type":9,"tag":17,"props":690,"children":692},{"className":691},[],[693],{"type":14,"value":694},"runc",{"type":14,"value":696}," directly.",{"type":14,"value":698}," It is the piece Docker and containerd both delegate to. ",{"type":9,"tag":17,"props":700,"children":702},{"className":701},[],[703],{"type":14,"value":704},"runc spec",{"type":14,"value":706}," writes a default ",{"type":9,"tag":17,"props":708,"children":710},{"className":709},[],[711],{"type":14,"value":678},{"type":14,"value":713},"; edit it and ",{"type":9,"tag":17,"props":715,"children":717},{"className":716},[],[718],{"type":14,"value":719},"runc run",{"type":14,"value":721}," it, with no daemon anywhere. This is the smallest step up from what you have just built.",{"type":9,"tag":10,"props":723,"children":724},{},[725,737,739,745,746,752,753,758,759,765,766,772],{"type":9,"tag":573,"props":726,"children":727},{},[728,730,736],{"type":14,"value":729},"Try ",{"type":9,"tag":17,"props":731,"children":733},{"className":732},[],[734],{"type":14,"value":735},"bocker",{"type":14,"value":536},{"type":14,"value":738}," A container runtime in about 100 lines of shell, doing very nearly what lesson 8 does, with ",{"type":9,"tag":17,"props":740,"children":742},{"className":741},[],[743],{"type":14,"value":744},"run",{"type":14,"value":506},{"type":9,"tag":17,"props":747,"children":749},{"className":748},[],[750],{"type":14,"value":751},"images",{"type":14,"value":506},{"type":9,"tag":17,"props":754,"children":756},{"className":755},[],[757],{"type":14,"value":482},{"type":14,"value":506},{"type":9,"tag":17,"props":760,"children":762},{"className":761},[],[763],{"type":14,"value":764},"logs",{"type":14,"value":384},{"type":9,"tag":17,"props":767,"children":769},{"className":768},[],[770],{"type":14,"value":771},"rm",{"type":14,"value":773},". Reading it end to end is the fastest way to see how the pieces compose into a tool.",{"type":9,"tag":10,"props":775,"children":776},{},[777,782],{"type":9,"tag":573,"props":778,"children":779},{},[780],{"type":14,"value":781},"Look at rootless Podman.",{"type":14,"value":783}," It is the same set of primitives with user namespaces added, and it is the practical demonstration of why that one namespace changes the security story.",{"type":9,"tag":10,"props":785,"children":786},{},[787,792,794,800,802,808,810,816],{"type":9,"tag":573,"props":788,"children":789},{},[790],{"type":14,"value":791},"Add the hardening.",{"type":14,"value":793}," Take lesson 8's command and add ",{"type":9,"tag":17,"props":795,"children":797},{"className":796},[],[798],{"type":14,"value":799},"capsh --drop=...",{"type":14,"value":801},", a seccomp profile, ",{"type":9,"tag":17,"props":803,"children":805},{"className":804},[],[806],{"type":14,"value":807},"pivot_root",{"type":14,"value":809}," in place of ",{"type":9,"tag":17,"props":811,"children":813},{"className":812},[],[814],{"type":14,"value":815},"chroot",{"type":14,"value":817},", and a user namespace. Each is a small change; together they are the difference between the isolation boundary you built and a security boundary.",{"data":819,"body":820},{},{"type":6,"children":821},[822,828,852],{"type":9,"tag":35,"props":823,"children":825},{"id":824},"credit",[826],{"type":14,"value":827},"Credit",{"type":9,"tag":10,"props":829,"children":830},{},[831,833,843,845,850],{"type":14,"value":832},"The command sequence in this course follows NH66's write-up of their FOSS Meet '26 container workshop, ",{"type":9,"tag":663,"props":834,"children":837},{"href":835,"rel":836},"https://nh66.ai/blog/what-is-a-container-really-building-one-from-scratch-with-linux-primitives/",[667],[838],{"type":9,"tag":247,"props":839,"children":840},{},[841],{"type":14,"value":842},"What Is a Container, Really? Building One From Scratch With Linux Primitives",{"type":14,"value":844}," (18 June 2026). The btrfs, veth, NAT, cgroup, and composition steps are theirs; the namespace, ",{"type":9,"tag":17,"props":846,"children":848},{"className":847},[],[849],{"type":14,"value":815},{"type":14,"value":851},", and teardown lessons expand on them. Worth reading in its original single-page form once you have finished here — it is the same material at a different pace.",{"type":9,"tag":10,"props":853,"children":854},{},[855,857,862,864,869],{"type":14,"value":856},"If you have not taken ",{"type":9,"tag":247,"props":858,"children":859},{},[860],{"type":14,"value":861},"Linux Basics",{"type":14,"value":863}," or ",{"type":9,"tag":247,"props":865,"children":866},{},[867],{"type":14,"value":868},"Shell Scripting",{"type":14,"value":870},", they are the two courses underneath this one.",1787908868396]