[{"data":1,"prerenderedAt":538},["ShallowReactive",2],{"mdc-p57gs6-key":3,"mdc--fygls4-key":43,"mdc--z4jcvj-key":129,"mdc--iaw11r-key":141,"mdc--ekwqoi-key":194,"mdc-svyv64-key":225,"mdc-v04jwb-key":247,"mdc-6pbn0o-key":290,"mdc--1j8blb-key":327,"mdc--6yvpyr-key":348,"mdc-9vnftw-key":358,"mdc-cp7s2m-key":401,"mdc--qmnjch-key":521},{"data":4,"body":5},{},{"type":6,"children":7},"root",[8,16],{"type":9,"tag":10,"props":11,"children":12},"element","p",{},[13],{"type":14,"value":15},"text","The container needs to talk. Right now it has no network at all — or rather, it has the host's, which is worse: it can bind the host's ports and see the host's interfaces.",{"type":9,"tag":10,"props":17,"children":18},{},[19,21,27,29,34,36,41],{"type":14,"value":20},"Fixing that takes three pieces. A ",{"type":9,"tag":22,"props":23,"children":24},"strong",{},[25],{"type":14,"value":26},"network namespace",{"type":14,"value":28}," to hold an isolated stack. A ",{"type":9,"tag":22,"props":30,"children":31},{},[32],{"type":14,"value":33},"veth pair",{"type":14,"value":35}," — a virtual cable with a plug at each end. And a ",{"type":9,"tag":22,"props":37,"children":38},{},[39],{"type":14,"value":40},"bridge",{"type":14,"value":42},", which is a software switch on the host that the container plugs into.",{"data":44,"body":45},{},{"type":6,"children":46},[47,54,59,72,81,102],{"type":9,"tag":48,"props":49,"children":51},"h2",{"id":50},"a-network-namespace-is-a-whole-second-stack",[52],{"type":14,"value":53},"A network namespace is a whole second stack",{"type":9,"tag":10,"props":55,"children":56},{},[57],{"type":14,"value":58},"Not a filter, not a virtual interface: a complete, independent set of interfaces, routing tables, ARP caches, iptables rules, and port space.",{"type":9,"tag":60,"props":61,"children":65},"pre",{"className":62,"code":64,"language":14},[63],"language-text","sudo ip netns add netns_my-container\nsudo ip netns exec netns_my-container ip addr\n",[66],{"type":9,"tag":67,"props":68,"children":70},"code",{"__ignoreMap":69},"",[71],{"type":14,"value":64},{"type":9,"tag":60,"props":73,"children":76},{"className":74,"code":75,"language":14},[63],"1: lo: \u003CLOOPBACK> mtu 65536 qdisc noop state DOWN\n    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\n",[77],{"type":9,"tag":67,"props":78,"children":79},{"__ignoreMap":69},[80],{"type":14,"value":75},{"type":9,"tag":10,"props":82,"children":83},{},[84,86,92,94,100],{"type":14,"value":85},"One interface, ",{"type":9,"tag":67,"props":87,"children":89},{"className":88},[],[90],{"type":14,"value":91},"lo",{"type":14,"value":93},", and it is down. Nothing else — no ethernet, no routes, no way out. ",{"type":9,"tag":67,"props":95,"children":97},{"className":96},[],[98],{"type":14,"value":99},"ip netns exec NAME COMMAND",{"type":14,"value":101}," is how you run anything inside it.",{"type":9,"tag":10,"props":103,"children":104},{},[105,107,112,114,119,121,127],{"type":14,"value":106},"Note that ",{"type":9,"tag":67,"props":108,"children":110},{"className":109},[],[111],{"type":14,"value":91},{"type":14,"value":113}," starts ",{"type":9,"tag":22,"props":115,"children":116},{},[117],{"type":14,"value":118},"down",{"type":14,"value":120},". A container that cannot reach its own ",{"type":9,"tag":67,"props":122,"children":124},{"className":123},[],[125],{"type":14,"value":126},"127.0.0.1",{"type":14,"value":128}," has usually hit exactly this, and the fix is one line further down.",{"data":130,"body":131},{},{"type":6,"children":132},[133],{"type":9,"tag":134,"props":135,"children":140},"quiz",{":answer":136,":options":137,"explanation":138,"question":139},"0","[\"Both succeed — each namespace has its own independent port space\",\"The second fails with \\\"address already in use\\\"\",\"The kernel assigns the second one a different port\"]","Ports belong to a network namespace, not to the machine. This is why every container in a cluster can serve on 8080 and why publishing a port is a separate, explicit act — a mapping from a host port into the namespace, which is what `-p 8080:8080` sets up.","Two containers are in separate network namespaces. Both bind port 8080. What happens?",[],{"data":142,"body":143},{},{"type":6,"children":144},[145,151,156,165],{"type":9,"tag":48,"props":146,"children":148},{"id":147},"a-bridge-to-plug-things-into",[149],{"type":14,"value":150},"A bridge, to plug things into",{"type":9,"tag":10,"props":152,"children":153},{},[154],{"type":14,"value":155},"A bridge is a virtual layer-2 switch living in the host's kernel. Give it an address and it also becomes the containers' gateway:",{"type":9,"tag":60,"props":157,"children":160},{"className":158,"code":159,"language":14},[63],"sudo ip link add name bridge0 type bridge\nsudo ip addr add 10.0.0.1/24 dev bridge0\nsudo ip link set bridge0 up\n",[161],{"type":9,"tag":67,"props":162,"children":163},{"__ignoreMap":69},[164],{"type":14,"value":159},{"type":9,"tag":10,"props":166,"children":167},{},[168,170,176,178,184,186,192],{"type":14,"value":169},"That is ",{"type":9,"tag":67,"props":171,"children":173},{"className":172},[],[174],{"type":14,"value":175},"docker0",{"type":14,"value":177}," on any machine running Docker, under a different name. Run ",{"type":9,"tag":67,"props":179,"children":181},{"className":180},[],[182],{"type":14,"value":183},"ip addr show docker0",{"type":14,"value":185}," and you will see the same shape — a bridge holding ",{"type":9,"tag":67,"props":187,"children":189},{"className":188},[],[190],{"type":14,"value":191},"172.17.0.1/16",{"type":14,"value":193},", acting as the default gateway for every container on the default network.",{"data":195,"body":196},{},{"type":6,"children":197},[198,204,216],{"type":9,"tag":48,"props":199,"children":201},{"id":200},"the-veth-pair-a-cable-with-two-ends",[202],{"type":14,"value":203},"The veth pair: a cable with two ends",{"type":9,"tag":10,"props":205,"children":206},{},[207,209,214],{"type":14,"value":208},"A ",{"type":9,"tag":22,"props":210,"children":211},{},[212],{"type":14,"value":213},"veth",{"type":14,"value":215}," is created as a pair, and it behaves exactly like a physical cable: whatever goes in one end comes out the other. One end stays on the host and plugs into the bridge; the other is handed to the container.",{"type":9,"tag":60,"props":217,"children":220},{"className":218,"code":219,"language":14},[63],"# The virtual cable: veth_host \u003C-> veth_cont\nsudo ip link add dev veth_host type veth peer name veth_cont\n\n# Plug the host end into the bridge and switch it on\nsudo ip link set veth_host master bridge0\nsudo ip link set veth_host up\n",[221],{"type":9,"tag":67,"props":222,"children":223},{"__ignoreMap":69},[224],{"type":14,"value":219},{"data":226,"body":227},{},{"type":6,"children":228},[229,234],{"type":9,"tag":230,"props":231,"children":233},"terminal-teaser",{":lines":232},"[{\"cmd\":\"ip link show veth_host\",\"out\":\"4: veth_host@veth_cont: \u003CBROADCAST,MULTICAST> master bridge0 state DOWN\"},{\"cmd\":\"sudo ip link set veth_host up\",\"out\":\"\"},{\"cmd\":\"ip link show master bridge0\",\"out\":\"4: veth_host@veth_cont: \u003CBROADCAST,MULTICAST,UP,LOWER_UP> master bridge0 state UP\"}]",[],{"type":9,"tag":10,"props":235,"children":236},{},[237,239,245],{"type":14,"value":238},"The ",{"type":9,"tag":67,"props":240,"children":242},{"className":241},[],[243],{"type":14,"value":244},"@veth_cont",{"type":14,"value":246}," in the name is the kernel telling you which interface is the other end of this cable.",{"data":248,"body":249},{},{"type":6,"children":250},[251,257,262,271],{"type":9,"tag":48,"props":252,"children":254},{"id":253},"move-one-end-into-the-namespace",[255],{"type":14,"value":256},"Move one end into the namespace",{"type":9,"tag":10,"props":258,"children":259},{},[260],{"type":14,"value":261},"This is the step where the isolation actually happens:",{"type":9,"tag":60,"props":263,"children":266},{"className":264,"code":265,"language":14},[63],"sudo ip link set veth_cont netns netns_my-container\n",[267],{"type":9,"tag":67,"props":268,"children":269},{"__ignoreMap":69},[270],{"type":14,"value":265},{"type":9,"tag":10,"props":272,"children":273},{},[274,280,282,288],{"type":9,"tag":67,"props":275,"children":277},{"className":276},[],[278],{"type":14,"value":279},"veth_cont",{"type":14,"value":281}," now disappears from the host's ",{"type":9,"tag":67,"props":283,"children":285},{"className":284},[],[286],{"type":14,"value":287},"ip link",{"type":14,"value":289}," output entirely. An interface belongs to exactly one network namespace, and it has just moved. The cable still runs between the two — that is the point — but the host can no longer configure or see that end.",{"data":291,"body":292},{},{"type":6,"children":293},[294,300,313,322],{"type":9,"tag":48,"props":295,"children":297},{"id":296},"configure-it-from-the-inside",[298],{"type":14,"value":299},"Configure it from the inside",{"type":9,"tag":10,"props":301,"children":302},{},[303,305,311],{"type":14,"value":304},"Every remaining command runs inside the namespace, via ",{"type":9,"tag":67,"props":306,"children":308},{"className":307},[],[309],{"type":14,"value":310},"ip netns exec",{"type":14,"value":312},":",{"type":9,"tag":60,"props":314,"children":317},{"className":315,"code":316,"language":14},[63],"sudo ip netns exec netns_my-container ip link set dev lo up\nsudo ip netns exec netns_my-container ip addr add 10.0.0.2/24 dev veth_cont\nsudo ip netns exec netns_my-container ip link set dev veth_cont up\nsudo ip netns exec netns_my-container ip route add default via 10.0.0.1\n",[318],{"type":9,"tag":67,"props":319,"children":320},{"__ignoreMap":69},[321],{"type":14,"value":316},{"type":9,"tag":10,"props":323,"children":324},{},[325],{"type":14,"value":326},"In order: bring up loopback, give the container end an address on the bridge's subnet, bring the interface up, and add a default route pointing at the bridge.",{"data":328,"body":329},{},{"type":6,"children":330},[331,335],{"type":9,"tag":230,"props":332,"children":334},{":lines":333},"[{\"cmd\":\"sudo ip netns exec netns_my-container ip addr show veth_cont\",\"out\":\"3: veth_cont@if4: \u003CBROADCAST,MULTICAST,UP,LOWER_UP> state UP\\n    inet 10.0.0.2/24 scope global veth_cont\"},{\"cmd\":\"sudo ip netns exec netns_my-container ping -c1 10.0.0.1\",\"out\":\"64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.061 ms\"},{\"cmd\":\"sudo ip netns exec netns_my-container ping -c1 8.8.8.8\",\"out\":\"connect: Network is unreachable\"}]",[],{"type":9,"tag":10,"props":336,"children":337},{},[338,340,346],{"type":14,"value":339},"The container can reach the host. It cannot reach the internet — ",{"type":9,"tag":67,"props":341,"children":343},{"className":342},[],[344],{"type":14,"value":345},"10.0.0.0/24",{"type":14,"value":347}," is a private range that no router on the way out will carry a reply back to. Fixing that is the next lesson.",{"data":349,"body":350},{},{"type":6,"children":351},[352],{"type":9,"tag":134,"props":353,"children":357},{":answer":136,":options":354,"explanation":355,"question":356},"[\"Its source address is in a private range, so replies from the internet have nowhere to return to\",\"The default route is wrong and should point at the container's own address\",\"ICMP is blocked by default in a new network namespace\"]","Routing out is fine — the packet leaves. Nothing on the public internet knows how to route a reply to 10.0.0.2, and the host is not yet rewriting the source address on its way through. That rewrite is NAT.","Why does the container reach 10.0.0.1 but not 8.8.8.8, even with a default route configured?",[],{"data":359,"body":360},{},{"type":6,"children":361},[362,368,387,396],{"type":9,"tag":48,"props":363,"children":365},{"id":364},"dns-is-a-file-not-a-protocol-setting",[366],{"type":14,"value":367},"DNS is a file, not a protocol setting",{"type":9,"tag":10,"props":369,"children":370},{},[371,373,379,381,386],{"type":14,"value":372},"Name resolution has nothing to do with the network namespace. It is ",{"type":9,"tag":67,"props":374,"children":376},{"className":375},[],[377],{"type":14,"value":378},"/etc/resolv.conf",{"type":14,"value":380}," inside the container's ",{"type":9,"tag":22,"props":382,"children":383},{},[384],{"type":14,"value":385},"filesystem",{"type":14,"value":312},{"type":9,"tag":60,"props":388,"children":391},{"className":389,"code":390,"language":14},[63],"sudo sh -c \"echo 'nameserver 8.8.8.8' > ./btrfs-mount/my-container/etc/resolv.conf\"\n",[392],{"type":9,"tag":67,"props":393,"children":394},{"__ignoreMap":69},[395],{"type":14,"value":390},{"type":9,"tag":10,"props":397,"children":398},{},[399],{"type":14,"value":400},"Two isolation mechanisms, one problem — which is why \"the container has network but can't resolve anything\" is such a common failure. The network namespace is set up correctly; the file in the root filesystem is empty or missing.",{"data":402,"body":403},{},{"type":6,"children":404},[405],{"type":9,"tag":406,"props":407,"children":409},"deep-dive",{"title":408},"What Docker adds on top of exactly this",[410,423,433,482,516],{"type":9,"tag":10,"props":411,"children":412},{},[413,415,421],{"type":14,"value":414},"Everything above is what ",{"type":9,"tag":67,"props":416,"children":418},{"className":417},[],[419],{"type":14,"value":420},"docker network create",{"type":14,"value":422}," does, with three additions.",{"type":9,"tag":10,"props":424,"children":425},{},[426,431],{"type":9,"tag":22,"props":427,"children":428},{},[429],{"type":14,"value":430},"Automation and lifecycle.",{"type":14,"value":432}," A veth pair per container, named from the container ID, created on start and destroyed on stop. IP addresses handed out from the bridge's subnet by an internal IPAM allocator instead of typed by hand.",{"type":9,"tag":10,"props":434,"children":435},{},[436,441,443,449,451,456,458,464,466,472,474,480],{"type":9,"tag":22,"props":437,"children":438},{},[439],{"type":14,"value":440},"An embedded DNS server.",{"type":14,"value":442}," On a user-defined network, Docker runs a resolver at ",{"type":9,"tag":67,"props":444,"children":446},{"className":445},[],[447],{"type":14,"value":448},"127.0.0.11",{"type":14,"value":450}," inside each container and writes that address into ",{"type":9,"tag":67,"props":452,"children":454},{"className":453},[],[455],{"type":14,"value":378},{"type":14,"value":457},". It resolves container names to their current addresses, which is what makes ",{"type":9,"tag":67,"props":459,"children":461},{"className":460},[],[462],{"type":14,"value":463},"postgres:5432",{"type":14,"value":465}," work in a compose file. This is also why containers on the ",{"type":9,"tag":467,"props":468,"children":469},"em",{},[470],{"type":14,"value":471},"default",{"type":14,"value":473}," bridge cannot resolve each other by name — the embedded DNS only serves user-defined networks, and that one difference accounts for a large share of \"it works in compose but not with plain ",{"type":9,"tag":67,"props":475,"children":477},{"className":476},[],[478],{"type":14,"value":479},"docker run",{"type":14,"value":481},"\".",{"type":9,"tag":10,"props":483,"children":484},{},[485,490,492,498,500,506,508,514],{"type":9,"tag":22,"props":486,"children":487},{},[488],{"type":14,"value":489},"Port publishing.",{"type":14,"value":491}," ",{"type":9,"tag":67,"props":493,"children":495},{"className":494},[],[496],{"type":14,"value":497},"-p 8080:80",{"type":14,"value":499}," is a DNAT rule in the host's ",{"type":9,"tag":67,"props":501,"children":503},{"className":502},[],[504],{"type":14,"value":505},"nat",{"type":14,"value":507}," table rewriting the destination of inbound packets to the container's address, plus a userland proxy process as a fallback. ",{"type":9,"tag":67,"props":509,"children":511},{"className":510},[],[512],{"type":14,"value":513},"sudo iptables -t nat -L DOCKER",{"type":14,"value":515}," shows the rules for every published port on the machine.",{"type":9,"tag":10,"props":517,"children":518},{},[519],{"type":14,"value":520},"None of it is a different mechanism. It is bookkeeping on top of veth pairs, a bridge, and iptables.",{"data":522,"body":523},{},{"type":6,"children":524},[525,533],{"type":9,"tag":526,"props":527,"children":532},"fill-blank",{":answer":528,"hint":529,"placeholder":530,"prompt":531},"[\"sudo ip netns exec netns_web ip addr\",\"ip netns exec netns_web ip addr\",\"sudo ip netns exec netns_web ip a\"]","The pattern is `ip netns exec NAMESPACE COMMAND`.","sudo ip netns ...","Run `ip addr` inside the network namespace `netns_web`.",[],{"type":9,"tag":10,"props":534,"children":535},{},[536],{"type":14,"value":537},"Next up: NAT — the three iptables rules that let a private address reach the public internet.",1787908868374]