[{"data":1,"prerenderedAt":1958},["ShallowReactive",2],{"page-\u002Flinux\u002F17-containers-and-namespaces":3},{"id":4,"title":5,"body":6,"description":1951,"extension":1952,"meta":1953,"navigation":346,"path":1954,"seo":1955,"stem":1956,"__hash__":1957},"content\u002Flinux\u002F17-containers-and-namespaces.md","17 — Containers & Namespaces",{"type":7,"value":8,"toc":1922},"minimark",[9,13,35,40,107,110,114,252,260,413,420,502,506,624,627,631,683,687,746,749,753,767,821,825,986,990,994,1117,1121,1179,1183,1249,1256,1299,1303,1387,1391,1466,1470,1548,1552,1716,1720,1723,1781,1784,1918],[10,11,5],"h1",{"id":12},"_17-containers-namespaces",[14,15,16,17,21,22,25,26,30,31,34],"p",{},"Containers are a Linux-native technology for isolating processes. They use ",[18,19,20],"strong",{},"namespaces"," (isolation) and ",[18,23,24],{},"cgroups"," (resource limits) — both kernel features — to run processes that appear to have their own system, without the overhead of a full virtual machine. This chapter covers the underlying primitives, tools (",[27,28,29],"code",{},"podman",", ",[27,32,33],{},"docker","), images, and security.",[36,37,39],"h2",{"id":38},"containers-vs-virtual-machines","Containers vs Virtual Machines",[41,42,43,59],"table",{},[44,45,46],"thead",{},[47,48,49,53,56],"tr",{},[50,51,52],"th",{},"Feature",[50,54,55],{},"Virtual Machine",[50,57,58],{},"Container",[60,61,62,74,85,96],"tbody",{},[47,63,64,68,71],{},[65,66,67],"td",{},"Isolation",[65,69,70],{},"Full (separate kernel)",[65,72,73],{},"Process-level (shared kernel)",[47,75,76,79,82],{},[65,77,78],{},"Overhead",[65,80,81],{},"Heavy (GBs, seconds to start)",[65,83,84],{},"Light (MBs, milliseconds)",[47,86,87,90,93],{},[65,88,89],{},"Security",[65,91,92],{},"Strong (hardware virt)",[65,94,95],{},"Weaker (shared kernel)",[47,97,98,101,104],{},[65,99,100],{},"Density",[65,102,103],{},"Few per host",[65,105,106],{},"Hundreds per host",[14,108,109],{},"Containers share the host's kernel — each container is just a set of isolated processes. This makes them lightweight but means a kernel exploit affects all containers.",[36,111,113],{"id":112},"namespaces-the-isolation-primitives","Namespaces — The Isolation Primitives",[41,115,116,129],{},[44,117,118],{},[47,119,120,123,126],{},[50,121,122],{},"Namespace",[50,124,125],{},"Isolates",[50,127,128],{},"Flag",[60,130,131,146,161,176,191,206,221,236],{},[47,132,133,138,141],{},[65,134,135],{},[27,136,137],{},"pid",[65,139,140],{},"Process IDs (container sees its own PID 1)",[65,142,143],{},[27,144,145],{},"CLONE_NEWPID",[47,147,148,153,156],{},[65,149,150],{},[27,151,152],{},"net",[65,154,155],{},"Network stack (interfaces, routes, ports)",[65,157,158],{},[27,159,160],{},"CLONE_NEWNET",[47,162,163,168,171],{},[65,164,165],{},[27,166,167],{},"mnt",[65,169,170],{},"Mount points (filesystem view)",[65,172,173],{},[27,174,175],{},"CLONE_NEWNS",[47,177,178,183,186],{},[65,179,180],{},[27,181,182],{},"uts",[65,184,185],{},"Hostname, domainname",[65,187,188],{},[27,189,190],{},"CLONE_NEWUTS",[47,192,193,198,201],{},[65,194,195],{},[27,196,197],{},"ipc",[65,199,200],{},"IPC (System V, POSIX message queues)",[65,202,203],{},[27,204,205],{},"CLONE_NEWIPC",[47,207,208,213,216],{},[65,209,210],{},[27,211,212],{},"user",[65,214,215],{},"UID\u002FGID mappings (container root ≠ host root)",[65,217,218],{},[27,219,220],{},"CLONE_NEWUSER",[47,222,223,228,231],{},[65,224,225],{},[27,226,227],{},"cgroup",[65,229,230],{},"Cgroup view",[65,232,233],{},[27,234,235],{},"CLONE_NEWCGROUP",[47,237,238,243,246],{},[65,239,240],{},[27,241,242],{},"time",[65,244,245],{},"Clock offsets",[65,247,248,251],{},[27,249,250],{},"CLONE_NEWTIME"," (5.6+)",[253,254,256,259],"h3",{"id":255},"unshare-create-a-namespace",[27,257,258],{},"unshare"," — Create a Namespace",[261,262,264],"code-wrapper",{"language":263},"bash",[265,266,270],"pre",{"className":267,"code":268,"language":263,"meta":269,"style":269},"language-bash shiki shiki-themes github-light github-dark","# Complex Implementation: create a minimal \"container\" with unshare\nsudo unshare -p -f -m -u -n bash\n# -p: new PID namespace (be PID 1)\n# -f: fork (needed for PID namespace)\n# -m: new mount namespace\n# -u: new UTS namespace (hostname)\n# -n: new network namespace\n\necho $$           # 1 (you're PID 1 in this namespace)\nhostname mycontainer\nmount -t proc proc \u002Fproc    # remount \u002Fproc to see only this namespace's processes\nps aux            # only sees this namespace's processes\nip link           # only loopback (isolated network)\n","",[27,271,272,281,311,317,323,329,335,341,348,360,369,389,401],{"__ignoreMap":269},[273,274,277],"span",{"class":275,"line":276},"line",1,[273,278,280],{"class":279},"sdCPZ","# Complex Implementation: create a minimal \"container\" with unshare\n",[273,282,284,288,292,296,299,302,305,308],{"class":275,"line":283},2,[273,285,287],{"class":286},"sIsaT","sudo",[273,289,291],{"class":290},"sJ6F3"," unshare",[273,293,295],{"class":294},"snvgF"," -p",[273,297,298],{"class":294}," -f",[273,300,301],{"class":294}," -m",[273,303,304],{"class":294}," -u",[273,306,307],{"class":294}," -n",[273,309,310],{"class":290}," bash\n",[273,312,314],{"class":275,"line":313},3,[273,315,316],{"class":279},"# -p: new PID namespace (be PID 1)\n",[273,318,320],{"class":275,"line":319},4,[273,321,322],{"class":279},"# -f: fork (needed for PID namespace)\n",[273,324,326],{"class":275,"line":325},5,[273,327,328],{"class":279},"# -m: new mount namespace\n",[273,330,332],{"class":275,"line":331},6,[273,333,334],{"class":279},"# -u: new UTS namespace (hostname)\n",[273,336,338],{"class":275,"line":337},7,[273,339,340],{"class":279},"# -n: new network namespace\n",[273,342,344],{"class":275,"line":343},8,[273,345,347],{"emptyLinePlaceholder":346},true,"\n",[273,349,351,354,357],{"class":275,"line":350},9,[273,352,353],{"class":294},"echo",[273,355,356],{"class":294}," $$",[273,358,359],{"class":279},"           # 1 (you're PID 1 in this namespace)\n",[273,361,363,366],{"class":275,"line":362},10,[273,364,365],{"class":286},"hostname",[273,367,368],{"class":290}," mycontainer\n",[273,370,372,375,378,381,383,386],{"class":275,"line":371},11,[273,373,374],{"class":286},"mount",[273,376,377],{"class":294}," -t",[273,379,380],{"class":290}," proc",[273,382,380],{"class":290},[273,384,385],{"class":290}," \u002Fproc",[273,387,388],{"class":279},"    # remount \u002Fproc to see only this namespace's processes\n",[273,390,392,395,398],{"class":275,"line":391},12,[273,393,394],{"class":286},"ps",[273,396,397],{"class":290}," aux",[273,399,400],{"class":279},"            # only sees this namespace's processes\n",[273,402,404,407,410],{"class":275,"line":403},13,[273,405,406],{"class":286},"ip",[273,408,409],{"class":290}," link",[273,411,412],{"class":279},"           # only loopback (isolated network)\n",[253,414,416,419],{"id":415},"nsenter-enter-a-namespace-debugging",[27,417,418],{},"nsenter"," — Enter a Namespace (Debugging)",[261,421,422],{"language":263},[265,423,425],{"className":267,"code":424,"language":263,"meta":269,"style":269},"# Complex Implementation: debug a distroless container (no shell inside)\n# — enter its namespaces from the host\nPID=$(docker inspect -f '{{.State.Pid}}' mycontainer)\nsudo nsenter -t $PID -m -u -i -n -p bash\n# Now you're \"inside\" the container's namespaces, with a full shell\n# (even if the container has no shell — you brought bash from the host)\n",[27,426,427,432,437,466,492,497],{"__ignoreMap":269},[273,428,429],{"class":275,"line":276},[273,430,431],{"class":279},"# Complex Implementation: debug a distroless container (no shell inside)\n",[273,433,434],{"class":275,"line":283},[273,435,436],{"class":279},"# — enter its namespaces from the host\n",[273,438,439,443,447,450,452,455,457,460,463],{"class":275,"line":313},[273,440,442],{"class":441},"ssxIu","PID",[273,444,446],{"class":445},"svdQ7","=",[273,448,449],{"class":441},"$(",[273,451,33],{"class":286},[273,453,454],{"class":290}," inspect",[273,456,298],{"class":294},[273,458,459],{"class":290}," '{{.State.Pid}}'",[273,461,462],{"class":290}," mycontainer",[273,464,465],{"class":441},")\n",[273,467,468,470,473,475,478,481,483,486,488,490],{"class":275,"line":319},[273,469,287],{"class":286},[273,471,472],{"class":290}," nsenter",[273,474,377],{"class":294},[273,476,477],{"class":441}," $PID ",[273,479,480],{"class":294},"-m",[273,482,304],{"class":294},[273,484,485],{"class":294}," -i",[273,487,307],{"class":294},[273,489,295],{"class":294},[273,491,310],{"class":290},[273,493,494],{"class":275,"line":325},[273,495,496],{"class":279},"# Now you're \"inside\" the container's namespaces, with a full shell\n",[273,498,499],{"class":275,"line":331},[273,500,501],{"class":279},"# (even if the container has no shell — you brought bash from the host)\n",[36,503,505],{"id":504},"cgroups-resource-limits","Cgroups — Resource Limits",[261,507,508],{"language":263},[265,509,511],{"className":267,"code":510,"language":263,"meta":269,"style":269},"# Complex Implementation: manual cgroups v2 (modern, unified hierarchy)\ncat \u002Fsys\u002Ffs\u002Fcgroup\u002Fcgroup.controllers    # available controllers\nstat -fc %T \u002Fsys\u002Ffs\u002Fcgroup\u002F              # cgroup2fs (v2) or tmpfs (v1)\n\n# Create a cgroup with CPU + memory limits\nsudo mkdir \u002Fsys\u002Ffs\u002Fcgroup\u002Fmygroup\necho \"max 50000 100000\" | sudo tee \u002Fsys\u002Ffs\u002Fcgroup\u002Fmygroup\u002Fcpu.max   # 50% CPU\necho \"536870912\" | sudo tee \u002Fsys\u002Ffs\u002Fcgroup\u002Fmygroup\u002Fmemory.max       # 512 MB\necho $$ | sudo tee \u002Fsys\u002Ffs\u002Fcgroup\u002Fmygroup\u002Fcgroup.procs              # add this process\n",[27,512,513,518,529,546,550,555,565,587,606],{"__ignoreMap":269},[273,514,515],{"class":275,"line":276},[273,516,517],{"class":279},"# Complex Implementation: manual cgroups v2 (modern, unified hierarchy)\n",[273,519,520,523,526],{"class":275,"line":283},[273,521,522],{"class":286},"cat",[273,524,525],{"class":290}," \u002Fsys\u002Ffs\u002Fcgroup\u002Fcgroup.controllers",[273,527,528],{"class":279},"    # available controllers\n",[273,530,531,534,537,540,543],{"class":275,"line":313},[273,532,533],{"class":294},"stat",[273,535,536],{"class":294}," -fc",[273,538,539],{"class":290}," %T",[273,541,542],{"class":290}," \u002Fsys\u002Ffs\u002Fcgroup\u002F",[273,544,545],{"class":279},"              # cgroup2fs (v2) or tmpfs (v1)\n",[273,547,548],{"class":275,"line":319},[273,549,347],{"emptyLinePlaceholder":346},[273,551,552],{"class":275,"line":325},[273,553,554],{"class":279},"# Create a cgroup with CPU + memory limits\n",[273,556,557,559,562],{"class":275,"line":331},[273,558,287],{"class":286},[273,560,561],{"class":290}," mkdir",[273,563,564],{"class":290}," \u002Fsys\u002Ffs\u002Fcgroup\u002Fmygroup\n",[273,566,567,569,572,575,578,581,584],{"class":275,"line":337},[273,568,353],{"class":294},[273,570,571],{"class":290}," \"max 50000 100000\"",[273,573,574],{"class":445}," |",[273,576,577],{"class":286}," sudo",[273,579,580],{"class":290}," tee",[273,582,583],{"class":290}," \u002Fsys\u002Ffs\u002Fcgroup\u002Fmygroup\u002Fcpu.max",[273,585,586],{"class":279},"   # 50% CPU\n",[273,588,589,591,594,596,598,600,603],{"class":275,"line":343},[273,590,353],{"class":294},[273,592,593],{"class":290}," \"536870912\"",[273,595,574],{"class":445},[273,597,577],{"class":286},[273,599,580],{"class":290},[273,601,602],{"class":290}," \u002Fsys\u002Ffs\u002Fcgroup\u002Fmygroup\u002Fmemory.max",[273,604,605],{"class":279},"       # 512 MB\n",[273,607,608,610,612,614,616,618,621],{"class":275,"line":350},[273,609,353],{"class":294},[273,611,356],{"class":294},[273,613,574],{"class":445},[273,615,577],{"class":286},[273,617,580],{"class":290},[273,619,620],{"class":290}," \u002Fsys\u002Ffs\u002Fcgroup\u002Fmygroup\u002Fcgroup.procs",[273,622,623],{"class":279},"              # add this process\n",[14,625,626],{},"In practice, use systemd or container runtimes — they manage cgroups for you.",[36,628,630],{"id":629},"container-runtimes","Container Runtimes",[41,632,633,643],{},[44,634,635],{},[47,636,637,640],{},[50,638,639],{},"Runtime",[50,641,642],{},"Role",[60,644,645,655,665,674],{},[47,646,647,652],{},[65,648,649],{},[27,650,651],{},"runc",[65,653,654],{},"Low-level (OCI runtime — actually runs the container)",[47,656,657,662],{},[65,658,659],{},[27,660,661],{},"containerd",[65,663,664],{},"High-level daemon (image management, lifecycle)",[47,666,667,671],{},[65,668,669],{},[27,670,33],{},[65,672,673],{},"Developer tool (uses containerd + runc)",[47,675,676,680],{},[65,677,678],{},[27,679,29],{},[65,681,682],{},"Daemonless, rootless alternative to docker",[253,684,686],{"id":685},"docker-vs-podman","Docker vs Podman",[41,688,689,701],{},[44,690,691],{},[47,692,693,695,698],{},[50,694,52],{},[50,696,697],{},"Docker",[50,699,700],{},"Podman",[60,702,703,718,729],{},[47,704,705,708,715],{},[65,706,707],{},"Daemon",[65,709,710,711,714],{},"Yes (",[27,712,713],{},"dockerd",")",[65,716,717],{},"No (daemonless)",[47,719,720,723,726],{},[65,721,722],{},"Root",[65,724,725],{},"Runs as root",[65,727,728],{},"Can run rootless (as regular user)",[47,730,731,734,738],{},[65,732,733],{},"CLI",[65,735,736],{},[27,737,33],{},[65,739,740,742,743,714],{},[27,741,29],{}," (compatible: ",[27,744,745],{},"alias docker=podman",[14,747,748],{},"Podman is recommended for security (rootless, no daemon). Docker is more established in CI\u002FCD.",[36,750,752],{"id":751},"images-and-registries","Images and Registries",[14,754,755,756,759,760,766],{},"An image is referenced as ",[27,757,758],{},"registry\u002Frepository:tag",". ",[18,761,762,763],{},"Avoid ",[27,764,765],{},":latest"," — it's a moving target.",[261,768,769],{"language":263},[265,770,772],{"className":267,"code":771,"language":263,"meta":269,"style":269},"# Complex Implementation: pin image digest for reproducibility\ndocker pull nginx@sha256:abc123...    # immutable (can't be retagged)\n# vs\ndocker pull nginx:latest              # changes without warning (non-reproducible)\ndocker pull nginx:1.27.2              # pinned tag (reproducible)\n",[27,773,774,779,792,797,809],{"__ignoreMap":269},[273,775,776],{"class":275,"line":276},[273,777,778],{"class":279},"# Complex Implementation: pin image digest for reproducibility\n",[273,780,781,783,786,789],{"class":275,"line":283},[273,782,33],{"class":286},[273,784,785],{"class":290}," pull",[273,787,788],{"class":290}," nginx@sha256:abc123...",[273,790,791],{"class":279},"    # immutable (can't be retagged)\n",[273,793,794],{"class":275,"line":313},[273,795,796],{"class":279},"# vs\n",[273,798,799,801,803,806],{"class":275,"line":319},[273,800,33],{"class":286},[273,802,785],{"class":290},[273,804,805],{"class":290}," nginx:latest",[273,807,808],{"class":279},"              # changes without warning (non-reproducible)\n",[273,810,811,813,815,818],{"class":275,"line":325},[273,812,33],{"class":286},[273,814,785],{"class":290},[273,816,817],{"class":290}," nginx:1.27.2",[273,819,820],{"class":279},"              # pinned tag (reproducible)\n",[36,822,824],{"id":823},"running-containers","Running Containers",[261,826,827],{"language":263},[265,828,830],{"className":267,"code":829,"language":263,"meta":269,"style":269},"# Complex Implementation: production container with security constraints\ndocker run -d --name web \\\n  -p 8080:80 \\\n  --user 1000:1000 \\                   # run as non-root user\n  --cap-drop ALL \\                     # drop all capabilities\n  --cap-add NET_BIND_SERVICE \\        # add only what's needed\n  --security-opt no-new-privileges \\  # prevent privilege escalation\n  --read-only \\                       # read-only root filesystem\n  --tmpfs \u002Ftmp \\                      # writable tmpfs for \u002Ftmp\n  --memory 512m \\                     # memory limit\n  --cpus 1.5 \\                        # CPU limit\n  --restart unless-stopped \\          # restart policy\n  nginx:1.27\n",[27,831,832,837,856,866,880,893,906,919,929,942,955,968,981],{"__ignoreMap":269},[273,833,834],{"class":275,"line":276},[273,835,836],{"class":279},"# Complex Implementation: production container with security constraints\n",[273,838,839,841,844,847,850,853],{"class":275,"line":283},[273,840,33],{"class":286},[273,842,843],{"class":290}," run",[273,845,846],{"class":294}," -d",[273,848,849],{"class":294}," --name",[273,851,852],{"class":290}," web",[273,854,855],{"class":294}," \\\n",[273,857,858,861,864],{"class":275,"line":313},[273,859,860],{"class":294},"  -p",[273,862,863],{"class":290}," 8080:80",[273,865,855],{"class":294},[273,867,868,871,874,877],{"class":275,"line":319},[273,869,870],{"class":294},"  --user",[273,872,873],{"class":290}," 1000:1000",[273,875,876],{"class":294}," \\ ",[273,878,879],{"class":279},"                  # run as non-root user\n",[273,881,882,885,888,890],{"class":275,"line":325},[273,883,884],{"class":286},"  --cap-drop",[273,886,887],{"class":290}," ALL",[273,889,876],{"class":294},[273,891,892],{"class":279},"                    # drop all capabilities\n",[273,894,895,898,901,903],{"class":275,"line":331},[273,896,897],{"class":286},"  --cap-add",[273,899,900],{"class":290}," NET_BIND_SERVICE",[273,902,876],{"class":294},[273,904,905],{"class":279},"       # add only what's needed\n",[273,907,908,911,914,916],{"class":275,"line":337},[273,909,910],{"class":286},"  --security-opt",[273,912,913],{"class":290}," no-new-privileges",[273,915,876],{"class":294},[273,917,918],{"class":279}," # prevent privilege escalation\n",[273,920,921,924,926],{"class":275,"line":343},[273,922,923],{"class":286},"  --read-only",[273,925,876],{"class":294},[273,927,928],{"class":279},"                      # read-only root filesystem\n",[273,930,931,934,937,939],{"class":275,"line":350},[273,932,933],{"class":286},"  --tmpfs",[273,935,936],{"class":290}," \u002Ftmp",[273,938,876],{"class":294},[273,940,941],{"class":279},"                     # writable tmpfs for \u002Ftmp\n",[273,943,944,947,950,952],{"class":275,"line":362},[273,945,946],{"class":286},"  --memory",[273,948,949],{"class":290}," 512m",[273,951,876],{"class":294},[273,953,954],{"class":279},"                    # memory limit\n",[273,956,957,960,963,965],{"class":275,"line":371},[273,958,959],{"class":286},"  --cpus",[273,961,962],{"class":294}," 1.5",[273,964,876],{"class":294},[273,966,967],{"class":279},"                       # CPU limit\n",[273,969,970,973,976,978],{"class":275,"line":391},[273,971,972],{"class":286},"  --restart",[273,974,975],{"class":290}," unless-stopped",[273,977,876],{"class":294},[273,979,980],{"class":279},"         # restart policy\n",[273,982,983],{"class":275,"line":403},[273,984,985],{"class":286},"  nginx:1.27\n",[36,987,989],{"id":988},"building-images","Building Images",[253,991,993],{"id":992},"multi-stage-builds","Multi-Stage Builds",[261,995,996],{"language":263},[265,997,999],{"className":267,"code":998,"language":263,"meta":269,"style":269},"# Complex Implementation: multi-stage build (small final image)\n# Build stage — full SDK\nFROM golang:1.22 AS builder\nWORKDIR \u002Fsrc\nCOPY . .\nRUN CGO_ENABLED=0 go build -o \u002Fapp server.go\n\n# Final stage — minimal runtime (Alpine + binary only, no Go toolchain)\nFROM alpine:3.20\nCOPY --from=builder \u002Fapp \u002Fapp\nCMD [\"\u002Fapp\"]\n# Final image: ~5 MB + binary (not 800 MB Go SDK)\n",[27,1000,1001,1006,1011,1025,1033,1044,1070,1074,1079,1086,1098,1112],{"__ignoreMap":269},[273,1002,1003],{"class":275,"line":276},[273,1004,1005],{"class":279},"# Complex Implementation: multi-stage build (small final image)\n",[273,1007,1008],{"class":275,"line":283},[273,1009,1010],{"class":279},"# Build stage — full SDK\n",[273,1012,1013,1016,1019,1022],{"class":275,"line":313},[273,1014,1015],{"class":286},"FROM",[273,1017,1018],{"class":290}," golang:1.22",[273,1020,1021],{"class":290}," AS",[273,1023,1024],{"class":290}," builder\n",[273,1026,1027,1030],{"class":275,"line":319},[273,1028,1029],{"class":286},"WORKDIR",[273,1031,1032],{"class":290}," \u002Fsrc\n",[273,1034,1035,1038,1041],{"class":275,"line":325},[273,1036,1037],{"class":286},"COPY",[273,1039,1040],{"class":290}," .",[273,1042,1043],{"class":290}," .\n",[273,1045,1046,1049,1052,1055,1058,1061,1064,1067],{"class":275,"line":331},[273,1047,1048],{"class":286},"RUN",[273,1050,1051],{"class":290}," CGO_ENABLED=",[273,1053,1054],{"class":294},"0",[273,1056,1057],{"class":290}," go",[273,1059,1060],{"class":290}," build",[273,1062,1063],{"class":294}," -o",[273,1065,1066],{"class":290}," \u002Fapp",[273,1068,1069],{"class":290}," server.go\n",[273,1071,1072],{"class":275,"line":337},[273,1073,347],{"emptyLinePlaceholder":346},[273,1075,1076],{"class":275,"line":343},[273,1077,1078],{"class":279},"# Final stage — minimal runtime (Alpine + binary only, no Go toolchain)\n",[273,1080,1081,1083],{"class":275,"line":350},[273,1082,1015],{"class":286},[273,1084,1085],{"class":290}," alpine:3.20\n",[273,1087,1088,1090,1093,1095],{"class":275,"line":362},[273,1089,1037],{"class":286},[273,1091,1092],{"class":294}," --from=builder",[273,1094,1066],{"class":290},[273,1096,1097],{"class":290}," \u002Fapp\n",[273,1099,1100,1103,1106,1109],{"class":275,"line":371},[273,1101,1102],{"class":286},"CMD",[273,1104,1105],{"class":441}," [",[273,1107,1108],{"class":290},"\"\u002Fapp\"",[273,1110,1111],{"class":441},"]\n",[273,1113,1114],{"class":275,"line":391},[273,1115,1116],{"class":279},"# Final image: ~5 MB + binary (not 800 MB Go SDK)\n",[253,1118,1120],{"id":1119},"layer-caching-order-matters","Layer Caching — Order Matters",[261,1122,1124],{"language":1123},"dockerfile",[265,1125,1128],{"className":1126,"code":1127,"language":1123,"meta":269,"style":269},"language-dockerfile shiki shiki-themes github-light github-dark","# GOOD: package.json changes rarely → npm ci is cached\nCOPY package*.json .\u002F\nRUN npm ci --only=production\nCOPY . .          # source changes often, but only this layer rebuilds\n\n# BAD: any source change invalidates the npm ci cache\nCOPY . .\nRUN npm ci        # rebuilds every time a source file changes\n",[27,1129,1130,1135,1142,1149,1156,1160,1165,1172],{"__ignoreMap":269},[273,1131,1132],{"class":275,"line":276},[273,1133,1134],{"class":279},"# GOOD: package.json changes rarely → npm ci is cached\n",[273,1136,1137,1139],{"class":275,"line":283},[273,1138,1037],{"class":445},[273,1140,1141],{"class":441}," package*.json .\u002F\n",[273,1143,1144,1146],{"class":275,"line":313},[273,1145,1048],{"class":445},[273,1147,1148],{"class":441}," npm ci --only=production\n",[273,1150,1151,1153],{"class":275,"line":319},[273,1152,1037],{"class":445},[273,1154,1155],{"class":441}," . .          # source changes often, but only this layer rebuilds\n",[273,1157,1158],{"class":275,"line":325},[273,1159,347],{"emptyLinePlaceholder":346},[273,1161,1162],{"class":275,"line":331},[273,1163,1164],{"class":279},"# BAD: any source change invalidates the npm ci cache\n",[273,1166,1167,1169],{"class":275,"line":337},[273,1168,1037],{"class":445},[273,1170,1171],{"class":441}," . .\n",[273,1173,1174,1176],{"class":275,"line":343},[273,1175,1048],{"class":445},[273,1177,1178],{"class":441}," npm ci        # rebuilds every time a source file changes\n",[36,1180,1182],{"id":1181},"volumes-persistent-data","Volumes — Persistent Data",[261,1184,1185],{"language":263},[265,1186,1188],{"className":267,"code":1187,"language":263,"meta":269,"style":269},"# Complex Implementation: persistent data with named volumes\ndocker volume create mydata\ndocker run -d -v mydata:\u002Fdata nginx\n\n# Bind mount (host directory — for development, live reload)\ndocker run -d -v \u002Fhome\u002Falice\u002Fdata:\u002Fdata nginx\n",[27,1189,1190,1195,1208,1225,1229,1234],{"__ignoreMap":269},[273,1191,1192],{"class":275,"line":276},[273,1193,1194],{"class":279},"# Complex Implementation: persistent data with named volumes\n",[273,1196,1197,1199,1202,1205],{"class":275,"line":283},[273,1198,33],{"class":286},[273,1200,1201],{"class":290}," volume",[273,1203,1204],{"class":290}," create",[273,1206,1207],{"class":290}," mydata\n",[273,1209,1210,1212,1214,1216,1219,1222],{"class":275,"line":313},[273,1211,33],{"class":286},[273,1213,843],{"class":290},[273,1215,846],{"class":294},[273,1217,1218],{"class":294}," -v",[273,1220,1221],{"class":290}," mydata:\u002Fdata",[273,1223,1224],{"class":290}," nginx\n",[273,1226,1227],{"class":275,"line":319},[273,1228,347],{"emptyLinePlaceholder":346},[273,1230,1231],{"class":275,"line":325},[273,1232,1233],{"class":279},"# Bind mount (host directory — for development, live reload)\n",[273,1235,1236,1238,1240,1242,1244,1247],{"class":275,"line":331},[273,1237,33],{"class":286},[273,1239,843],{"class":290},[273,1241,846],{"class":294},[273,1243,1218],{"class":294},[273,1245,1246],{"class":290}," \u002Fhome\u002Falice\u002Fdata:\u002Fdata",[273,1248,1224],{"class":290},[253,1250,1252,1253],{"id":1251},"edge-case-data-lost-on-docker-rm","Edge Case: Data Lost on ",[27,1254,1255],{},"docker rm",[261,1257,1258],{"language":263},[265,1259,1261],{"className":267,"code":1260,"language":263,"meta":269,"style":269},"# NAIVE: store database data in the container filesystem\n# docker rm db → ALL DATA LOST (the writable layer is deleted)\n# PRODUCTION: use volumes for anything you need to keep\ndocker run -d --name db -v pgdata:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata postgres:16\n",[27,1262,1263,1268,1273,1278],{"__ignoreMap":269},[273,1264,1265],{"class":275,"line":276},[273,1266,1267],{"class":279},"# NAIVE: store database data in the container filesystem\n",[273,1269,1270],{"class":275,"line":283},[273,1271,1272],{"class":279},"# docker rm db → ALL DATA LOST (the writable layer is deleted)\n",[273,1274,1275],{"class":275,"line":313},[273,1276,1277],{"class":279},"# PRODUCTION: use volumes for anything you need to keep\n",[273,1279,1280,1282,1284,1286,1288,1291,1293,1296],{"class":275,"line":319},[273,1281,33],{"class":286},[273,1283,843],{"class":290},[273,1285,846],{"class":294},[273,1287,849],{"class":294},[273,1289,1290],{"class":290}," db",[273,1292,1218],{"class":294},[273,1294,1295],{"class":290}," pgdata:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata",[273,1297,1298],{"class":290}," postgres:16\n",[36,1300,1302],{"id":1301},"security-considerations","Security Considerations",[1304,1305,1306,1321,1331,1339,1347,1362,1375],"ul",{},[1307,1308,1309,1312,1313,1316,1317,1320],"li",{},[18,1310,1311],{},"Don't run as root in the container",": use ",[27,1314,1315],{},"USER"," in the Dockerfile or ",[27,1318,1319],{},"--user"," at runtime.",[1307,1322,1323,1326,1327,1330],{},[18,1324,1325],{},"Drop capabilities",": ",[27,1328,1329],{},"--cap-drop ALL --cap-add NET_BIND_SERVICE"," — grant only what's needed.",[1307,1332,1333,1338],{},[18,1334,1335],{},[27,1336,1337],{},"--security-opt no-new-privileges",": prevent the process from gaining more privileges.",[1307,1340,1341,1346],{},[18,1342,1343],{},[27,1344,1345],{},"--read-only",": make the root filesystem read-only (use volumes for writable paths).",[1307,1348,1349,1326,1352,30,1355,30,1358,1361],{},[18,1350,1351],{},"Resource limits",[27,1353,1354],{},"--memory",[27,1356,1357],{},"--cpus",[27,1359,1360],{},"--pids-limit"," to prevent a container from starving others.",[1307,1363,1364,1326,1367,1370,1371,1374],{},[18,1365,1366],{},"Pin image digests",[27,1368,1369],{},"nginx@sha256:abc123..."," instead of ",[27,1372,1373],{},"nginx:latest",".",[1307,1376,1377,1326,1380,30,1383,1386],{},[18,1378,1379],{},"Scan images for vulnerabilities",[27,1381,1382],{},"trivy",[27,1384,1385],{},"grype",", or Snyk.",[253,1388,1390],{"id":1389},"edge-case-container-root-is-host-root","Edge Case: Container Root Is Host Root",[261,1392,1393],{"language":263},[265,1394,1396],{"className":267,"code":1395,"language":263,"meta":269,"style":269},"# NAIVE: run as root in container (without user namespaces)\n# USER root in a container = UID 0 on the host\n# If the container escapes (a vulnerability), the attacker has HOST ROOT\ndocker run --user root myapp\n\n# PRODUCTION: use --user or rootless podman (user namespaces map container root to high UID)\ndocker run --user 1000:1000 myapp\n# Or:\npodman run myapp    # rootless by default (container root → high UID on host)\n",[27,1397,1398,1403,1408,1413,1428,1432,1437,1449,1454],{"__ignoreMap":269},[273,1399,1400],{"class":275,"line":276},[273,1401,1402],{"class":279},"# NAIVE: run as root in container (without user namespaces)\n",[273,1404,1405],{"class":275,"line":283},[273,1406,1407],{"class":279},"# USER root in a container = UID 0 on the host\n",[273,1409,1410],{"class":275,"line":313},[273,1411,1412],{"class":279},"# If the container escapes (a vulnerability), the attacker has HOST ROOT\n",[273,1414,1415,1417,1419,1422,1425],{"class":275,"line":319},[273,1416,33],{"class":286},[273,1418,843],{"class":290},[273,1420,1421],{"class":294}," --user",[273,1423,1424],{"class":290}," root",[273,1426,1427],{"class":290}," myapp\n",[273,1429,1430],{"class":275,"line":325},[273,1431,347],{"emptyLinePlaceholder":346},[273,1433,1434],{"class":275,"line":331},[273,1435,1436],{"class":279},"# PRODUCTION: use --user or rootless podman (user namespaces map container root to high UID)\n",[273,1438,1439,1441,1443,1445,1447],{"class":275,"line":337},[273,1440,33],{"class":286},[273,1442,843],{"class":290},[273,1444,1421],{"class":294},[273,1446,873],{"class":290},[273,1448,1427],{"class":290},[273,1450,1451],{"class":275,"line":343},[273,1452,1453],{"class":279},"# Or:\n",[273,1455,1456,1458,1460,1463],{"class":275,"line":350},[273,1457,29],{"class":286},[273,1459,843],{"class":290},[273,1461,1462],{"class":290}," myapp",[273,1464,1465],{"class":279},"    # rootless by default (container root → high UID on host)\n",[36,1467,1469],{"id":1468},"tips-tricks","💡 Tips & Tricks",[1304,1471,1472,1491,1496,1501,1516,1530,1540],{},[1307,1473,1474,1477,1478,1480,1481,1484,1485,759,1488,1490],{},[18,1475,1476],{},"Idiom",": pin image tags (not ",[27,1479,765],{},") — ",[27,1482,1483],{},"nginx:1.27.2"," or ",[27,1486,1487],{},"nginx@sha256:...",[27,1489,765],{}," is a moving target — what you test today isn't what you deploy tomorrow.",[1307,1492,1493,1495],{},[18,1494,1476],{},": use multi-stage builds to shrink images — build in a full SDK image, copy the binary to a minimal runtime image. Reduces image size from GBs to MBs.",[1307,1497,1498,1500],{},[18,1499,1476],{},": order Dockerfile instructions for cache efficiency — copy rarely-changing files (package manifests) first, run expensive commands, then copy frequently-changing source.",[1307,1502,1503,1312,1505,1508,1509,1512,1513,1515],{},[18,1504,1476],{},[27,1506,1507],{},"--rm"," for one-off commands — ",[27,1510,1511],{},"docker run --rm alpine echo hello"," removes the container after it exits. Without ",[27,1514,1507],{},", stopped containers accumulate.",[1307,1517,1518,1312,1520,1523,1524,1370,1527,1529],{},[18,1519,1476],{},[27,1521,1522],{},"docker exec -it"," to get a shell in a running container — use ",[27,1525,1526],{},"sh",[27,1528,263],{}," on Alpine (no bash by default).",[1307,1531,1532,1312,1534,1536,1537,1539],{},[18,1533,1476],{},[27,1535,29],{}," for rootless, daemonless containers — no root daemon (more secure). ",[27,1538,745],{}," for compatibility.",[1307,1541,1542,1312,1545,1547],{},[18,1543,1544],{},"Debug",[27,1546,418],{}," to enter a container's namespaces directly — gives a full shell even if the container has no shell (e.g., distroless).",[36,1549,1551],{"id":1550},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[1304,1553,1554,1566,1579,1591,1609,1639,1658,1671,1677,1685,1693,1706],{},[1307,1555,1556,1561,1562,1565],{},[18,1557,1558,1559],{},"Data in the container filesystem is lost on ",[27,1560,1255],{},": use volumes (",[27,1563,1564],{},"-v",") for anything you need to keep.",[1307,1567,1568,1578],{},[18,1569,1570,1573,1574,1577],{},[27,1571,1572],{},"docker run -v \u002Fdata:\u002Fdata"," creates ",[27,1575,1576],{},"\u002Fdata"," on the host if missing",": and it's owned by root. If the container runs as a non-root user, it can't write there.",[1307,1580,1581,1326,1584,1587,1588,1374],{},[18,1582,1583],{},"Port conflicts",[27,1585,1586],{},"-p 8080:80"," fails if host port 8080 is already in use. Check with ",[27,1589,1590],{},"ss -tlnp | grep 8080",[1307,1592,1593,1326,1601,1603,1604,1606,1607,1374],{},[18,1594,1595,1598,1599],{},[27,1596,1597],{},"ADD"," vs ",[27,1600,1037],{},[27,1602,1597],{}," auto-extracts tarballs and supports URLs (surprising behavior); ",[27,1605,1037],{}," is simple and explicit. Prefer ",[27,1608,1037],{},[1307,1610,1611,1326,1618,1620,1621,1623,1624,1626,1627,1630,1631,1634,1635,1638],{},[18,1612,1613,1598,1615],{},[27,1614,1102],{},[27,1616,1617],{},"ENTRYPOINT",[27,1619,1102],{}," is the default command (overridable). ",[27,1622,1617],{}," is the fixed command (",[27,1625,1102],{}," becomes args). ",[27,1628,1629],{},"docker run image echo hi"," with ",[27,1632,1633],{},"ENTRYPOINT [\"server\"]"," runs ",[27,1636,1637],{},"server echo hi"," (probably wrong).",[1307,1640,1641,1312,1647,1650,1651,30,1654,1657],{},[18,1642,1643,1646],{},[27,1644,1645],{},"docker build"," sends the entire context to the daemon",[27,1648,1649],{},".dockerignore"," to exclude ",[27,1652,1653],{},"node_modules",[27,1655,1656],{},".git",", etc.",[1307,1659,1660,1663,1664,1484,1667,1670],{},[18,1661,1662],{},"Alpine uses musl, not glibc",": binaries compiled against glibc won't run on Alpine. Use ",[27,1665,1666],{},"debian-slim",[27,1668,1669],{},"ubuntu"," base if unsure.",[1307,1672,1673,1676],{},[18,1674,1675],{},"Containers share the host kernel",": a kernel exploit affects all containers (unlike VMs). Don't run untrusted containers on a host with sensitive data.",[1307,1678,1679,1684],{},[18,1680,1681,1683],{},[27,1682,765],{}," can change without warning",": breaks reproducibility. Pin to a specific tag or digest.",[1307,1686,1687,1312,1690,1692],{},[18,1688,1689],{},"Root in a container is root on the host (without user namespaces)",[27,1691,1319],{}," or rootless podman.",[1307,1694,1695,1326,1698,1701,1702,1705],{},[18,1696,1697],{},"Layer cache can hide security updates",[27,1699,1700],{},"RUN apt install nginx"," is cached. Use ",[27,1703,1704],{},"--no-cache"," for production builds, or pin versions.",[1307,1707,1708,1711,1712,1715],{},[18,1709,1710],{},"Container networking defaults to bridge",": the default bridge network doesn't do DNS between containers. Create a user-defined network (",[27,1713,1714],{},"docker network create",") for name-based resolution.",[36,1717,1719],{"id":1718},"spot-the-bug","🧠 Spot the Bug",[14,1721,1722],{},"A developer builds a Node.js app image with this Dockerfile:",[261,1724,1725],{"language":1123},[265,1726,1728],{"className":1126,"code":1727,"language":1123,"meta":269,"style":269},"FROM node:20\nCOPY . \u002Fapp\nWORKDIR \u002Fapp\nRUN npm install\nEXPOSE 3000\nCMD [\"node\", \"server.js\"]\n",[27,1729,1730,1737,1744,1750,1757,1765],{"__ignoreMap":269},[273,1731,1732,1734],{"class":275,"line":276},[273,1733,1015],{"class":445},[273,1735,1736],{"class":441}," node:20\n",[273,1738,1739,1741],{"class":275,"line":283},[273,1740,1037],{"class":445},[273,1742,1743],{"class":441}," . \u002Fapp\n",[273,1745,1746,1748],{"class":275,"line":313},[273,1747,1029],{"class":445},[273,1749,1097],{"class":441},[273,1751,1752,1754],{"class":275,"line":319},[273,1753,1048],{"class":445},[273,1755,1756],{"class":441}," npm install\n",[273,1758,1759,1762],{"class":275,"line":325},[273,1760,1761],{"class":445},"EXPOSE",[273,1763,1764],{"class":441}," 3000\n",[273,1766,1767,1769,1771,1774,1776,1779],{"class":275,"line":331},[273,1768,1102],{"class":445},[273,1770,1105],{"class":441},[273,1772,1773],{"class":290},"\"node\"",[273,1775,30],{"class":441},[273,1777,1778],{"class":290},"\"server.js\"",[273,1780,1111],{"class":441},[14,1782,1783],{},"Every time they change a single source file, the build takes 5 minutes (npm install runs every time). What's wrong, and how do they fix it?",[1785,1786,1787,1791,1816,1821,1881,1884,1911],"details",{},[1788,1789,1790],"summary",{},"Answer",[14,1792,1793,1803,1804,1806,1807,1811,1812,1815],{},[18,1794,1795,1796,1799,1800,1374],{},"Layer caching is invalidated by the ",[27,1797,1798],{},"COPY . \u002Fapp"," before ",[27,1801,1802],{},"npm install"," Docker caches layers — if a layer's input hasn't changed, the cache is reused. ",[27,1805,1798],{}," copies ",[1808,1809,1810],"em",{},"all"," files, including source code. When any source file changes, this layer is invalidated, and all subsequent layers (including ",[27,1813,1814],{},"RUN npm install",") rebuild.",[14,1817,1818],{},[18,1819,1820],{},"Fix — copy package manifests first, install deps, then copy source:",[261,1822,1823],{"language":1123},[265,1824,1826],{"className":1126,"code":1825,"language":1123,"meta":269,"style":269},"FROM node:20\nWORKDIR \u002Fapp\nCOPY package*.json .\u002F          # package.json changes rarely → cache survives\nRUN npm ci                     # only rebuilds when package.json changes\nCOPY . .                       # source changes often, but only this layer rebuilds\nEXPOSE 3000\nCMD [\"node\", \"server.js\"]\n",[27,1827,1828,1834,1840,1847,1854,1861,1867],{"__ignoreMap":269},[273,1829,1830,1832],{"class":275,"line":276},[273,1831,1015],{"class":445},[273,1833,1736],{"class":441},[273,1835,1836,1838],{"class":275,"line":283},[273,1837,1029],{"class":445},[273,1839,1097],{"class":441},[273,1841,1842,1844],{"class":275,"line":313},[273,1843,1037],{"class":445},[273,1845,1846],{"class":441}," package*.json .\u002F          # package.json changes rarely → cache survives\n",[273,1848,1849,1851],{"class":275,"line":319},[273,1850,1048],{"class":445},[273,1852,1853],{"class":441}," npm ci                     # only rebuilds when package.json changes\n",[273,1855,1856,1858],{"class":275,"line":325},[273,1857,1037],{"class":445},[273,1859,1860],{"class":441}," . .                       # source changes often, but only this layer rebuilds\n",[273,1862,1863,1865],{"class":275,"line":331},[273,1864,1761],{"class":445},[273,1866,1764],{"class":441},[273,1868,1869,1871,1873,1875,1877,1879],{"class":275,"line":337},[273,1870,1102],{"class":445},[273,1872,1105],{"class":441},[273,1874,1773],{"class":290},[273,1876,30],{"class":441},[273,1878,1778],{"class":290},[273,1880,1111],{"class":441},[14,1882,1883],{},"Now:",[1304,1885,1886,1896,1903],{},[1307,1887,1888,1891,1892,1895],{},[27,1889,1890],{},"package.json"," changes rarely → ",[27,1893,1894],{},"npm ci"," layer is cached → fast rebuilds.",[1307,1897,1898,1899,1902],{},"Source code changes → only the ",[27,1900,1901],{},"COPY . ."," layer rebuilds (seconds, not minutes).",[1307,1904,1905,1907,1908,1910],{},[27,1906,1894],{}," (not ",[27,1909,1802],{},") is preferred for reproducible builds (respects lockfile, faster in CI).",[14,1912,1913,1914,1917],{},"The principle: ",[18,1915,1916],{},"order Dockerfile instructions from least-frequently-changing to most-frequently-changing"," to maximize cache hits.",[1919,1920,1921],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}",{"title":269,"searchDepth":283,"depth":283,"links":1923},[1924,1925,1931,1932,1935,1936,1937,1941,1945,1948,1949,1950],{"id":38,"depth":283,"text":39},{"id":112,"depth":283,"text":113,"children":1926},[1927,1929],{"id":255,"depth":313,"text":1928},"unshare — Create a Namespace",{"id":415,"depth":313,"text":1930},"nsenter — Enter a Namespace (Debugging)",{"id":504,"depth":283,"text":505},{"id":629,"depth":283,"text":630,"children":1933},[1934],{"id":685,"depth":313,"text":686},{"id":751,"depth":283,"text":752},{"id":823,"depth":283,"text":824},{"id":988,"depth":283,"text":989,"children":1938},[1939,1940],{"id":992,"depth":313,"text":993},{"id":1119,"depth":313,"text":1120},{"id":1181,"depth":283,"text":1182,"children":1942},[1943],{"id":1251,"depth":313,"text":1944},"Edge Case: Data Lost on docker rm",{"id":1301,"depth":283,"text":1302,"children":1946},[1947],{"id":1389,"depth":313,"text":1390},{"id":1468,"depth":283,"text":1469},{"id":1550,"depth":283,"text":1551},{"id":1718,"depth":283,"text":1719},"Containers are a Linux-native technology for isolating processes. They use namespaces (isolation) and cgroups (resource limits) — both kernel features — to run processes that appear to have their own system, without the overhead of a full virtual machine. This chapter covers the underlying primitives, tools (podman, docker), images, and security.","md",{},"\u002Flinux\u002F17-containers-and-namespaces",{"title":5,"description":1951},"linux\u002F17-containers-and-namespaces","gAQk10ndUcHZniT3MHJoU8lQsqTLrkiGzdlFYEFtFv4",1789924650142]