[{"data":1,"prerenderedAt":730},["ShallowReactive",2],{"mdc-aw7q56-key":3,"mdc--m7v9ud-key":38,"mdc--xdzy1f-key":66,"mdc-swsui6-key":80,"mdc-h4npth-key":186,"mdc--f14q4m-key":198,"mdc--gfyybo-key":291,"mdc-ifeojf-key":301,"mdc-dkhhep-key":385,"mdc--bjf2ez-key":501,"mdc-rdga6p-key":713},{"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","A container needs a ",{"type":9,"tag":17,"props":18,"children":20},"code",{"className":19},[],[21],{"type":14,"value":22},"/",{"type":14,"value":24},". Not a copy of your machine's — its own, holding whatever userland the image ships: Alpine's BusyBox and musl, or Debian's coreutils and glibc, or nothing at all but a single static binary.",{"type":9,"tag":10,"props":26,"children":27},{},[28,30,36],{"type":14,"value":29},"The mechanism is ",{"type":9,"tag":17,"props":31,"children":33},{"className":32},[],[34],{"type":14,"value":35},"chroot",{"type":14,"value":37},", and it is the oldest piece of this whole story: it landed in Version 7 Unix in 1979, twenty-five years before anyone said \"container\".",{"data":39,"body":40},{},{"type":6,"children":41},[42,49,54],{"type":9,"tag":43,"props":44,"children":46},"h2",{"id":45},"get-a-userland",[47],{"type":14,"value":48},"Get a userland",{"type":9,"tag":10,"props":50,"children":51},{},[52],{"type":14,"value":53},"An image is, stripped of its metadata, a tarball of a filesystem. You can download one directly — no registry, no Docker:",{"type":9,"tag":55,"props":56,"children":60},"pre",{"className":57,"code":59,"language":14},[58],"language-text","curl -O https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/x86_64/alpine-minirootfs-3.19.1-x86_64.tar.gz\n\nmkdir -p ./rootfs\nsudo tar -xf alpine-minirootfs-3.19.1-x86_64.tar.gz -C ./rootfs\n",[61],{"type":9,"tag":17,"props":62,"children":64},{"__ignoreMap":63},"",[65],{"type":14,"value":59},{"data":67,"body":68},{},{"type":6,"children":69},[70,75],{"type":9,"tag":71,"props":72,"children":74},"terminal-teaser",{":lines":73},"[{\"cmd\":\"ls ./rootfs\",\"out\":\"bin  dev  etc  home  lib  media  mnt  opt  proc  root  sbin  srv  sys  tmp  usr  var\"},{\"cmd\":\"du -sh ./rootfs\",\"out\":\"7.8M\\t./rootfs\"}]",[],{"type":9,"tag":10,"props":76,"children":77},{},[78],{"type":14,"value":79},"Under eight megabytes, and that is a complete Linux userland: a shell, a package manager, an init, the lot. It is small because it has no kernel in it — it doesn't need one, because it will use yours.",{"data":81,"body":82},{},{"type":6,"children":83},[84,100,109,114,123,159],{"type":9,"tag":43,"props":85,"children":87},{"id":86},"chroot-moves",[88,93,95],{"type":9,"tag":17,"props":89,"children":91},{"className":90},[],[92],{"type":14,"value":35},{"type":14,"value":94}," moves ",{"type":9,"tag":17,"props":96,"children":98},{"className":97},[],[99],{"type":14,"value":22},{"type":9,"tag":55,"props":101,"children":104},{"className":102,"code":103,"language":14},[58],"sudo chroot ./rootfs /bin/sh\n",[105],{"type":9,"tag":17,"props":106,"children":107},{"__ignoreMap":63},[108],{"type":14,"value":103},{"type":9,"tag":10,"props":110,"children":111},{},[112],{"type":14,"value":113},"Inside:",{"type":9,"tag":55,"props":115,"children":118},{"className":116,"code":117,"language":14},[58],"/ # ls /\nbin    dev    etc    home   lib    media  mnt    opt    proc   root\n/ # cat /etc/os-release\nNAME=\"Alpine Linux\"\n/ # ls /home/you\nls: /home/you: No such file or directory\n",[119],{"type":9,"tag":17,"props":120,"children":121},{"__ignoreMap":63},[122],{"type":14,"value":117},{"type":9,"tag":10,"props":124,"children":125},{},[126,128,134,136,142,144,150,151,157],{"type":14,"value":127},"The shell is now resolving every absolute path against ",{"type":9,"tag":17,"props":129,"children":131},{"className":130},[],[132],{"type":14,"value":133},"./rootfs",{"type":14,"value":135},". ",{"type":9,"tag":17,"props":137,"children":139},{"className":138},[],[140],{"type":14,"value":141},"/bin/sh",{"type":14,"value":143}," means ",{"type":9,"tag":17,"props":145,"children":147},{"className":146},[],[148],{"type":14,"value":149},"./rootfs/bin/sh",{"type":14,"value":135},{"type":9,"tag":17,"props":152,"children":154},{"className":153},[],[155],{"type":14,"value":156},"/etc/os-release",{"type":14,"value":158}," reports Alpine on a machine running Ubuntu. Your home directory is not merely hidden — as far as this process can express it, there is no path that names it.",{"type":9,"tag":10,"props":160,"children":161},{},[162,164,169,171,176,178,184],{"type":14,"value":163},"That last point is the important one. ",{"type":9,"tag":17,"props":165,"children":167},{"className":166},[],[168],{"type":14,"value":35},{"type":14,"value":170}," doesn't hide files. It changes what the string ",{"type":9,"tag":17,"props":172,"children":174},{"className":173},[],[175],{"type":14,"value":22},{"type":14,"value":177}," resolves to, which means paths above the new root become ",{"type":9,"tag":179,"props":180,"children":181},"strong",{},[182],{"type":14,"value":183},"unnameable",{"type":14,"value":185}," rather than forbidden.",{"data":187,"body":188},{},{"type":6,"children":189},[190],{"type":9,"tag":191,"props":192,"children":197},"quiz",{":answer":193,":options":194,"explanation":195,"question":196},"0","[\"Every absolute path is resolved against `./rootfs`, so it reads `./rootfs/etc/os-release`\",\"chroot replaces the running kernel with Alpine's\",\"The file was rewritten during the chroot\"]","Only path resolution changed. The kernel, the CPU, the process table, and the network are all still the host's — which is why `uname -r` inside the chroot still reports the Ubuntu kernel version.","After `chroot ./rootfs`, why does `cat /etc/os-release` report Alpine on an Ubuntu host?",[],{"data":199,"body":200},{},{"type":6,"children":201},[202,214,219,228,256,265],{"type":9,"tag":43,"props":203,"children":205},{"id":204},"proc-has-to-be-mounted",[206,212],{"type":9,"tag":17,"props":207,"children":209},{"className":208},[],[210],{"type":14,"value":211},"/proc",{"type":14,"value":213}," has to be mounted",{"type":9,"tag":10,"props":215,"children":216},{},[217],{"type":14,"value":218},"Try this inside the chroot:",{"type":9,"tag":55,"props":220,"children":223},{"className":221,"code":222,"language":14},[58],"/ # ps\nPID   USER     TIME  COMMAND\n",[224],{"type":9,"tag":17,"props":225,"children":226},{"__ignoreMap":63},[227],{"type":14,"value":222},{"type":9,"tag":10,"props":229,"children":230},{},[231,233,239,241,246,248,254],{"type":14,"value":232},"Empty — not even the shell you are typing into. ",{"type":9,"tag":17,"props":234,"children":236},{"className":235},[],[237],{"type":14,"value":238},"ps",{"type":14,"value":240}," reads ",{"type":9,"tag":17,"props":242,"children":244},{"className":243},[],[245],{"type":14,"value":211},{"type":14,"value":247},", and ",{"type":9,"tag":17,"props":249,"children":251},{"className":250},[],[252],{"type":14,"value":253},"./rootfs/proc",{"type":14,"value":255}," is an empty directory. The kernel's process information is exposed through a virtual filesystem, and nobody has mounted it here.",{"type":9,"tag":55,"props":257,"children":260},{"className":258,"code":259,"language":14},[58],"/ # mount -t proc proc /proc\n/ # ps\nPID   USER     TIME  COMMAND\n    1 root      0:00 /sbin/init\n    2 root      0:00 [kthreadd]\n  ...\n",[261],{"type":9,"tag":17,"props":262,"children":263},{"__ignoreMap":63},[264],{"type":14,"value":259},{"type":9,"tag":10,"props":266,"children":267},{},[268,270,275,277,282,284,289],{"type":14,"value":269},"Now ",{"type":9,"tag":17,"props":271,"children":273},{"className":272},[],[274],{"type":14,"value":238},{"type":14,"value":276}," works — and shows ",{"type":9,"tag":179,"props":278,"children":279},{},[280],{"type":14,"value":281},"every process on the host",{"type":14,"value":283},", because a plain ",{"type":9,"tag":17,"props":285,"children":287},{"className":286},[],[288],{"type":14,"value":35},{"type":14,"value":290}," gives no PID namespace. This is the pairing from the last lesson seen from the other side: the namespace provides the isolation, the mount provides the view, and you need both.",{"data":292,"body":293},{},{"type":6,"children":294},[295],{"type":9,"tag":191,"props":296,"children":300},{":answer":193,":options":297,"explanation":298,"question":299},"[\"Yes — it is running as real root on the host with no PID isolation\",\"No — chroot prevents signals from crossing the boundary\",\"Only processes whose binaries exist inside the chroot\"]","chroot restricts path resolution and nothing else. Signals, the process table, the network, and every capability of root are untouched. A root shell in a chroot is a root shell on the host that is inconvenienced about filenames.","Inside a chroot with `/proc` mounted but no PID namespace, `ps` lists every host process. Can that shell kill them?",[],{"data":302,"body":303},{},{"type":6,"children":304},[305,316,321,365],{"type":9,"tag":43,"props":306,"children":308},{"id":307},"chroot-is-not-a-security-boundary",[309,314],{"type":9,"tag":17,"props":310,"children":312},{"className":311},[],[313],{"type":14,"value":35},{"type":14,"value":315}," is not a security boundary",{"type":9,"tag":10,"props":317,"children":318},{},[319],{"type":14,"value":320},"This has been true since the 1980s and is still worth stating plainly, because the intuition points the other way.",{"type":9,"tag":10,"props":322,"children":323},{},[324,326,331,333,339,341,347,349,355,357,363],{"type":14,"value":325},"A process running as root inside a chroot can escape it. The classic method is a dozen lines of C: create a directory, ",{"type":9,"tag":17,"props":327,"children":329},{"className":328},[],[330],{"type":14,"value":35},{"type":14,"value":332}," into it, then ",{"type":9,"tag":17,"props":334,"children":336},{"className":335},[],[337],{"type":14,"value":338},"chdir(\"../../../..\")",{"type":14,"value":340}," past the new root and ",{"type":9,"tag":17,"props":342,"children":344},{"className":343},[],[345],{"type":14,"value":346},"chroot(\".\")",{"type":14,"value":348},". The kernel does not clamp the relative path, so you walk out into the real filesystem. Root inside a chroot keeps every capability — it can ",{"type":9,"tag":17,"props":350,"children":352},{"className":351},[],[353],{"type":14,"value":354},"mknod",{"type":14,"value":356}," a device node for the host's disk and read it directly, load a kernel module, or ",{"type":9,"tag":17,"props":358,"children":360},{"className":359},[],[361],{"type":14,"value":362},"ptrace",{"type":14,"value":364}," a host process.",{"type":9,"tag":10,"props":366,"children":367},{},[368,370,376,378,383],{"type":14,"value":369},"What actually contains a container is namespaces plus dropped capabilities plus seccomp, and the filesystem part uses ",{"type":9,"tag":17,"props":371,"children":373},{"className":372},[],[374],{"type":14,"value":375},"pivot_root",{"type":14,"value":377}," rather than ",{"type":9,"tag":17,"props":379,"children":381},{"className":380},[],[382],{"type":14,"value":35},{"type":14,"value":384},".",{"data":386,"body":387},{},{"type":6,"children":388},[389],{"type":9,"tag":390,"props":391,"children":393},"deep-dive",{"title":392},"`pivot_root`, which is what runtimes actually use",[394,411,420,449,458,479,489],{"type":9,"tag":10,"props":395,"children":396},{},[397,402,404,409],{"type":9,"tag":17,"props":398,"children":400},{"className":399},[],[401],{"type":14,"value":375},{"type":14,"value":403}," moves the root mount rather than just changing where ",{"type":9,"tag":17,"props":405,"children":407},{"className":406},[],[408],{"type":14,"value":22},{"type":14,"value":410}," points:",{"type":9,"tag":55,"props":412,"children":415},{"className":413,"code":414,"language":14},[58],"pivot_root new_root put_old\n",[416],{"type":9,"tag":17,"props":417,"children":418},{"__ignoreMap":63},[419],{"type":14,"value":414},{"type":9,"tag":10,"props":421,"children":422},{},[423,425,431,433,439,441,447],{"type":14,"value":424},"It makes ",{"type":9,"tag":17,"props":426,"children":428},{"className":427},[],[429],{"type":14,"value":430},"new_root",{"type":14,"value":432}," the process's root and relocates the ",{"type":9,"tag":434,"props":435,"children":436},"em",{},[437],{"type":14,"value":438},"old",{"type":14,"value":440}," root to ",{"type":9,"tag":17,"props":442,"children":444},{"className":443},[],[445],{"type":14,"value":446},"put_old",{"type":14,"value":448},", where it can then be unmounted:",{"type":9,"tag":55,"props":450,"children":453},{"className":451,"code":452,"language":14},[58],"mount --bind ./rootfs ./rootfs        # new_root must be a mount point\nmkdir -p ./rootfs/.old\npivot_root ./rootfs ./rootfs/.old\ncd /\numount -l /.old\nrmdir /.old\n",[454],{"type":9,"tag":17,"props":455,"children":456},{"__ignoreMap":63},[457],{"type":14,"value":452},{"type":9,"tag":10,"props":459,"children":460},{},[461,463,469,471,477],{"type":14,"value":462},"After that ",{"type":9,"tag":17,"props":464,"children":466},{"className":465},[],[467],{"type":14,"value":468},"umount",{"type":14,"value":470},", the host filesystem is not mounted anywhere in this mount namespace. There is no ",{"type":9,"tag":17,"props":472,"children":474},{"className":473},[],[475],{"type":14,"value":476},"..",{"type":14,"value":478}," to climb, because the thing you would climb into is genuinely no longer attached — a stronger claim than \"unreachable by name\".",{"type":9,"tag":10,"props":480,"children":481},{},[482,487],{"type":9,"tag":17,"props":483,"children":485},{"className":484},[],[486],{"type":14,"value":375},{"type":14,"value":488}," requires a mount namespace, which is the other reason runtimes always create one. runc uses this sequence; so does every other OCI runtime.",{"type":9,"tag":10,"props":490,"children":491},{},[492,494,499],{"type":14,"value":493},"For this course we stay with ",{"type":9,"tag":17,"props":495,"children":497},{"className":496},[],[498],{"type":14,"value":35},{"type":14,"value":500},", because it is one word and demonstrates the idea. Just don't carry the impression that it is what production does.",{"data":502,"body":503},{},{"type":6,"children":504},[505,511,523,700],{"type":9,"tag":43,"props":506,"children":508},{"id":507},"the-pieces-a-real-container-mounts",[509],{"type":14,"value":510},"The pieces a real container mounts",{"type":9,"tag":10,"props":512,"children":513},{},[514,516,521],{"type":14,"value":515},"A runtime sets up more than ",{"type":9,"tag":17,"props":517,"children":519},{"className":518},[],[520],{"type":14,"value":211},{"type":14,"value":522}," before handing over control. The minimum set:",{"type":9,"tag":524,"props":525,"children":526},"table",{},[527,546],{"type":9,"tag":528,"props":529,"children":530},"thead",{},[531],{"type":9,"tag":532,"props":533,"children":534},"tr",{},[535,541],{"type":9,"tag":536,"props":537,"children":538},"th",{},[539],{"type":14,"value":540},"Mount",{"type":9,"tag":536,"props":542,"children":543},{},[544],{"type":14,"value":545},"Why",{"type":9,"tag":547,"props":548,"children":549},"tbody",{},[550,590,607,666,683],{"type":9,"tag":532,"props":551,"children":552},{},[553,562],{"type":9,"tag":554,"props":555,"children":556},"td",{},[557],{"type":9,"tag":17,"props":558,"children":560},{"className":559},[],[561],{"type":14,"value":211},{"type":9,"tag":554,"props":563,"children":564},{},[565,567,573,575,580,582,588],{"type":14,"value":566},"process table, ",{"type":9,"tag":17,"props":568,"children":570},{"className":569},[],[571],{"type":14,"value":572},"/proc/self",{"type":14,"value":574},", kernel tunables — ",{"type":9,"tag":17,"props":576,"children":578},{"className":577},[],[579],{"type":14,"value":238},{"type":14,"value":581},", ",{"type":9,"tag":17,"props":583,"children":585},{"className":584},[],[586],{"type":14,"value":587},"top",{"type":14,"value":589},", most language runtimes",{"type":9,"tag":532,"props":591,"children":592},{},[593,602],{"type":9,"tag":554,"props":594,"children":595},{},[596],{"type":9,"tag":17,"props":597,"children":599},{"className":598},[],[600],{"type":14,"value":601},"/sys",{"type":9,"tag":554,"props":603,"children":604},{},[605],{"type":14,"value":606},"device and kernel object tree; usually read-only",{"type":9,"tag":532,"props":608,"children":609},{},[610,619],{"type":9,"tag":554,"props":611,"children":612},{},[613],{"type":9,"tag":17,"props":614,"children":616},{"className":615},[],[617],{"type":14,"value":618},"/dev",{"type":9,"tag":554,"props":620,"children":621},{},[622,624,630,632,638,639,645,646,652,653,659,660],{"type":14,"value":623},"a small ",{"type":9,"tag":17,"props":625,"children":627},{"className":626},[],[628],{"type":14,"value":629},"tmpfs",{"type":14,"value":631}," with ",{"type":9,"tag":17,"props":633,"children":635},{"className":634},[],[636],{"type":14,"value":637},"null",{"type":14,"value":581},{"type":9,"tag":17,"props":640,"children":642},{"className":641},[],[643],{"type":14,"value":644},"zero",{"type":14,"value":581},{"type":9,"tag":17,"props":647,"children":649},{"className":648},[],[650],{"type":14,"value":651},"random",{"type":14,"value":581},{"type":9,"tag":17,"props":654,"children":656},{"className":655},[],[657],{"type":14,"value":658},"urandom",{"type":14,"value":581},{"type":9,"tag":17,"props":661,"children":663},{"className":662},[],[664],{"type":14,"value":665},"tty",{"type":9,"tag":532,"props":667,"children":668},{},[669,678],{"type":9,"tag":554,"props":670,"children":671},{},[672],{"type":9,"tag":17,"props":673,"children":675},{"className":674},[],[676],{"type":14,"value":677},"/dev/pts",{"type":9,"tag":554,"props":679,"children":680},{},[681],{"type":14,"value":682},"pseudo-terminals, needed for an interactive shell",{"type":9,"tag":532,"props":684,"children":685},{},[686,695],{"type":9,"tag":554,"props":687,"children":688},{},[689],{"type":9,"tag":17,"props":690,"children":692},{"className":691},[],[693],{"type":14,"value":694},"/dev/shm",{"type":9,"tag":554,"props":696,"children":697},{},[698],{"type":14,"value":699},"POSIX shared memory — the default 64 MB that Chrome and Postgres both complain about",{"type":9,"tag":10,"props":701,"children":702},{},[703,705,711],{"type":14,"value":704},"Miss ",{"type":9,"tag":17,"props":706,"children":708},{"className":707},[],[709],{"type":14,"value":710},"/dev/urandom",{"type":14,"value":712}," and half of userspace fails in ways that make no sense — TLS handshakes, UUID generation, anything seeding a random number generator.",{"data":714,"body":715},{},{"type":6,"children":716},[717,725],{"type":9,"tag":718,"props":719,"children":724},"fill-blank",{":answer":720,"hint":721,"placeholder":722,"prompt":723},"[\"mount -t proc proc /proc\",\"mount -t proc none /proc\",\"mount -t proc proc /proc/\"]","The filesystem type is `proc`; the source argument is conventional and ignored.","mount ...","Mount the proc filesystem at `/proc`, from inside the chroot.",[],{"type":9,"tag":10,"props":726,"children":727},{},[728],{"type":14,"value":729},"Next up: copy-on-write — how a hundred containers share one base image without any of them being able to damage it.",1787908868359]