[{"data":1,"prerenderedAt":505},["ShallowReactive",2],{"mdc-90hoj0-key":3,"mdc-jinmlz-key":16,"mdc--20t273-key":123,"mdc--tu3i8h-key":142,"mdc--s5s8sf-key":154,"mdc--w94eio-key":218,"mdc-rtjyky-key":228,"mdc-mk1xsm-key":271,"mdc--pytbro-key":327,"mdc--ekkxq6-key":339},{"data":4,"body":5},{},{"type":6,"children":7},"root",[8],{"type":9,"tag":10,"props":11,"children":12},"element","p",{},[13],{"type":14,"value":15},"text","Every container gets its own network stack — its own interfaces, routing table, and port space. That is why two containers can both listen on port 80 without arguing, and why neither of them is reachable from your browser until you say so.",{"data":17,"body":18},{},{"type":6,"children":19},[20,27,49,61,74,79,88],{"type":9,"tag":21,"props":22,"children":24},"h2",{"id":23},"the-default-bridge-and-why-not-to-use-it",[25],{"type":14,"value":26},"The default bridge, and why not to use it",{"type":9,"tag":10,"props":28,"children":29},{},[30,32,39,41,47],{"type":14,"value":31},"Start a container with no network options and it lands on a bridge called ",{"type":9,"tag":33,"props":34,"children":36},"code",{"className":35},[],[37],{"type":14,"value":38},"bridge",{"type":14,"value":40},", which appears on the host as ",{"type":9,"tag":33,"props":42,"children":44},{"className":43},[],[45],{"type":14,"value":46},"docker0",{"type":14,"value":48},".",{"type":9,"tag":50,"props":51,"children":55},"pre",{"className":52,"code":54,"language":14},[53],"language-text","docker network ls\nNETWORK ID     NAME      DRIVER    SCOPE\n8f2c1a9b7d34   bridge    bridge    local\n1e4d8c2f9a01   host      host      local\nc73b5e1a8d92   none      null      local\n",[56],{"type":9,"tag":33,"props":57,"children":59},{"__ignoreMap":58},"",[60],{"type":14,"value":54},{"type":9,"tag":10,"props":62,"children":63},{},[64,66,72],{"type":14,"value":65},"Containers on the default bridge can reach each other ",{"type":9,"tag":67,"props":68,"children":69},"strong",{},[70],{"type":14,"value":71},"by IP address only",{"type":14,"value":73},". There is no name resolution between them. Which is close to useless, because container IPs are assigned at start and change.",{"type":9,"tag":10,"props":75,"children":76},{},[77],{"type":14,"value":78},"The fix is one command:",{"type":9,"tag":50,"props":80,"children":83},{"className":81,"code":82,"language":14},[53],"docker network create appnet\ndocker run -d --name db     --network appnet postgres:17\ndocker run -d --name api    --network appnet myapi\n",[84],{"type":9,"tag":33,"props":85,"children":86},{"__ignoreMap":58},[87],{"type":14,"value":82},{"type":9,"tag":10,"props":89,"children":90},{},[91,93,98,100,105,107,113,115,121],{"type":14,"value":92},"On a ",{"type":9,"tag":67,"props":94,"children":95},{},[96],{"type":14,"value":97},"user-defined",{"type":14,"value":99}," network, Docker runs an embedded DNS resolver and containers resolve each other ",{"type":9,"tag":67,"props":101,"children":102},{},[103],{"type":14,"value":104},"by container name",{"type":14,"value":106},". The API connects to ",{"type":9,"tag":33,"props":108,"children":110},{"className":109},[],[111],{"type":14,"value":112},"postgres://db:5432",{"type":14,"value":114}," and it works, permanently, regardless of what address ",{"type":9,"tag":33,"props":116,"children":118},{"className":117},[],[119],{"type":14,"value":120},"db",{"type":14,"value":122}," gets today.",{"data":124,"body":125},{},{"type":6,"children":126},[127,132],{"type":9,"tag":128,"props":129,"children":131},"terminal-teaser",{":lines":130},"[{\"cmd\":\"docker network create appnet\",\"out\":\"c9a17f3e2b48\"},{\"cmd\":\"docker run -d --name db --network appnet -e POSTGRES_PASSWORD=x postgres:17\",\"out\":\"4f81c93ae207\"},{\"cmd\":\"docker run --rm --network appnet alpine ping -c1 db\",\"out\":\"PING db (172.19.0.2): 56 data bytes\\n64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.089 ms\"}]",[],{"type":9,"tag":10,"props":133,"children":134},{},[135,140],{"type":9,"tag":67,"props":136,"children":137},{},[138],{"type":14,"value":139},"This is the single most valuable networking fact in Docker",{"type":14,"value":141},", and the reason so many tutorials mysteriously fail: the embedded DNS server serves user-defined networks only, never the default bridge. If two containers cannot find each other by name, they are almost always on the default bridge. Compose creates a user-defined network for you automatically, which is why the problem seems to disappear the moment people move to Compose.",{"data":143,"body":144},{},{"type":6,"children":145},[146],{"type":9,"tag":147,"props":148,"children":153},"quiz",{":answer":149,":options":150,"explanation":151,"question":152},"0","[\"Put them both on a user-defined network — the default bridge has no DNS resolution\",\"Publish their ports with `-p`\",\"Add entries to each container's `/etc/hosts`\"]","Name resolution between containers is a feature of user-defined networks. Publishing exposes ports to the *host*, which is a different problem. `docker network create` and `--network` is the whole fix.","Two containers started with plain `docker run` cannot reach each other by name. What is the fix?",[],{"data":155,"body":156},{},{"type":6,"children":157},[158,164,173,185,213],{"type":9,"tag":21,"props":159,"children":161},{"id":160},"publishing-and-the-direction-of-travel",[162],{"type":14,"value":163},"Publishing, and the direction of travel",{"type":9,"tag":50,"props":165,"children":168},{"className":166,"code":167,"language":14},[53],"docker run -p 8080:80 nginx\n",[169],{"type":9,"tag":33,"props":170,"children":171},{"__ignoreMap":58},[172],{"type":14,"value":167},{"type":9,"tag":10,"props":174,"children":175},{},[176,178,183],{"type":14,"value":177},"Publishing connects ",{"type":9,"tag":67,"props":179,"children":180},{},[181],{"type":14,"value":182},"the host",{"type":14,"value":184}," to a container port. It has nothing to do with container-to-container traffic.",{"type":9,"tag":10,"props":186,"children":187},{},[188,190,196,198,203,205,211],{"type":14,"value":189},"That distinction resolves a lot of confusion in multi-service setups. Your API talks to Postgres on ",{"type":9,"tag":33,"props":191,"children":193},{"className":192},[],[194],{"type":14,"value":195},"db:5432",{"type":14,"value":197}," over the shared network — Postgres does ",{"type":9,"tag":67,"props":199,"children":200},{},[201],{"type":14,"value":202},"not",{"type":14,"value":204}," need ",{"type":9,"tag":33,"props":206,"children":208},{"className":207},[],[209],{"type":14,"value":210},"-p 5432:5432",{"type":14,"value":212}," for that. Publishing the database port only makes it reachable from your laptop, and on a server, from anywhere that can route to the host.",{"type":9,"tag":10,"props":214,"children":215},{},[216],{"type":14,"value":217},"So: publish what humans and the outside world need. Leave everything internal unpublished.",{"data":219,"body":220},{},{"type":6,"children":221},[222],{"type":9,"tag":147,"props":223,"children":227},{":answer":149,":options":224,"explanation":225,"question":226},"[\"No — publishing is for host access; container-to-container traffic uses the shared network directly\",\"Yes, otherwise the port is closed\",\"Only if they are on different hosts\"]","Inside the network the port was never closed. Publishing it adds host-side exposure you probably do not want, and on a public server means the internet unless you bind to `127.0.0.1`.","An API container and a Postgres container are on the same user-defined network. Does Postgres need `-p 5432:5432` for the API to reach it?",[],{"data":229,"body":230},{},{"type":6,"children":231},[232,238,243,252,257,266],{"type":9,"tag":21,"props":233,"children":235},{"id":234},"reaching-the-host-from-a-container",[236],{"type":14,"value":237},"Reaching the host from a container",{"type":9,"tag":10,"props":239,"children":240},{},[241],{"type":14,"value":242},"Containers see a private address, not yours. To connect back to something running on the host, use the special name Docker provides:",{"type":9,"tag":50,"props":244,"children":247},{"className":245,"code":246,"language":14},[53],"docker run --rm alpine ping -c1 host.docker.internal\n",[248],{"type":9,"tag":33,"props":249,"children":250},{"__ignoreMap":58},[251],{"type":14,"value":246},{"type":9,"tag":10,"props":253,"children":254},{},[255],{"type":14,"value":256},"This resolves on Docker Desktop out of the box. On Linux you have to ask for it:",{"type":9,"tag":50,"props":258,"children":261},{"className":259,"code":260,"language":14},[53],"docker run --add-host=host.docker.internal:host-gateway myapp\n",[262],{"type":9,"tag":33,"props":263,"children":264},{"__ignoreMap":58},[265],{"type":14,"value":260},{"type":9,"tag":10,"props":267,"children":268},{},[269],{"type":14,"value":270},"Reach for it in development — a container talking to a database running natively on your laptop — and design it out of anything you ship, because in production there is rarely a \"host\" to talk to.",{"data":272,"body":273},{},{"type":6,"children":274},[275,281,290,316],{"type":9,"tag":21,"props":276,"children":278},{"id":277},"the-other-network-modes",[279],{"type":14,"value":280},"The other network modes",{"type":9,"tag":50,"props":282,"children":285},{"className":283,"code":284,"language":14},[53],"docker run --network host nginx     # no isolation: uses the host's stack directly\ndocker run --network none alpine    # loopback only, no external access\n",[286],{"type":9,"tag":33,"props":287,"children":288},{"__ignoreMap":58},[289],{"type":14,"value":284},{"type":9,"tag":10,"props":291,"children":292},{},[293,299,301,307,309,314],{"type":9,"tag":33,"props":294,"children":296},{"className":295},[],[297],{"type":14,"value":298},"host",{"type":14,"value":300}," mode removes the network namespace entirely. The container binds host ports directly, so ",{"type":9,"tag":33,"props":302,"children":304},{"className":303},[],[305],{"type":14,"value":306},"-p",{"type":14,"value":308}," is meaningless and port conflicts are real again. It is worth it for a small class of things — high-throughput proxies where the extra hop matters, or tools that need to see the host's real interfaces — and it is not worth it as a way to make a networking problem go away. It is also ",{"type":9,"tag":67,"props":310,"children":311},{},[312],{"type":14,"value":313},"Linux-only",{"type":14,"value":315},"; on Docker Desktop the \"host\" is the Linux VM, not your machine.",{"type":9,"tag":10,"props":317,"children":318},{},[319,325],{"type":9,"tag":33,"props":320,"children":322},{"className":321},[],[323],{"type":14,"value":324},"none",{"type":14,"value":326}," is for jobs that should have no network at all. An untrusted build step, a batch process that only touches mounted files.",{"data":328,"body":329},{},{"type":6,"children":330},[331],{"type":9,"tag":332,"props":333,"children":338},"fill-blank",{":answer":334,"hint":335,"placeholder":336,"prompt":337},"[\"docker network create appnet\",\"docker network create --driver bridge appnet\",\"docker network create -d bridge appnet\"]","Three words. Bridge is the default driver, so you don't have to name it.","docker network ...","Create a user-defined bridge network called `appnet`.",[],{"data":340,"body":341},{},{"type":6,"children":342},[343,500],{"type":9,"tag":344,"props":345,"children":347},"deep-dive",{"title":346},"What Engine 29 changed underneath",[348,361,387,405,415,448],{"type":9,"tag":10,"props":349,"children":350},{},[351,353,359],{"type":14,"value":352},"Docker's networking is bridges, veth pairs, and iptables rules on the host — the same primitives ",{"type":9,"tag":354,"props":355,"children":356},"em",{},[357],{"type":14,"value":358},"Containers From Scratch",{"type":14,"value":360}," builds by hand. Engine 29 reworked several of them, which matters if you have host firewall rules or memorised chain names.",{"type":9,"tag":10,"props":362,"children":363},{},[364,369,371,377,379,385],{"type":9,"tag":67,"props":365,"children":366},{},[367],{"type":14,"value":368},"The isolation chains are gone.",{"type":14,"value":370}," ",{"type":9,"tag":33,"props":372,"children":374},{"className":373},[],[375],{"type":14,"value":376},"DOCKER-ISOLATION-STAGE-1",{"type":14,"value":378}," and ",{"type":9,"tag":33,"props":380,"children":382},{"className":381},[],[383],{"type":14,"value":384},"DOCKER-ISOLATION-STAGE-2",{"type":14,"value":386}," were removed and the bridge rules restructured. One visible consequence: containers can now reach published ports across networks when the userland proxy is not running, which previously did not work.",{"type":9,"tag":10,"props":388,"children":389},{},[390,395,397,403],{"type":9,"tag":67,"props":391,"children":392},{},[393],{"type":14,"value":394},"There is an nftables backend.",{"type":14,"value":396}," The ",{"type":9,"tag":33,"props":398,"children":400},{"className":399},[],[401],{"type":14,"value":402},"firewall-backend",{"type":14,"value":404}," daemon option can select nftables instead of iptables — experimental, but the direction of travel, since most distributions have moved on from iptables already.",{"type":9,"tag":10,"props":406,"children":407},{},[408,413],{"type":9,"tag":67,"props":409,"children":410},{},[411],{"type":14,"value":412},"macvlan and ipvlan no longer install a default gateway",{"type":14,"value":414}," unless you configure one explicitly. Existing setups that relied on the old behaviour need the gateway spelled out.",{"type":9,"tag":10,"props":416,"children":417},{},[418,423,425,431,433,439,441,446],{"type":9,"tag":67,"props":419,"children":420},{},[421],{"type":14,"value":422},"Legacy container links are deprecated.",{"type":14,"value":424}," The environment variables ",{"type":9,"tag":33,"props":426,"children":428},{"className":427},[],[429],{"type":14,"value":430},"--link",{"type":14,"value":432}," used to inject are on their way out, with ",{"type":9,"tag":33,"props":434,"children":436},{"className":435},[],[437],{"type":14,"value":438},"DOCKER_KEEP_DEPRECATED_LEGACY_LINKS_ENV_VARS=1",{"type":14,"value":440}," as a temporary escape hatch. ",{"type":9,"tag":33,"props":442,"children":444},{"className":443},[],[445],{"type":14,"value":430},{"type":14,"value":447}," was superseded by user-defined networks years ago; if you still have it, this is the nudge.",{"type":9,"tag":10,"props":449,"children":450},{},[451,453,458,460,466,468,474,476,482,484,490,492,498],{"type":14,"value":452},"The rule that has not changed, and the one worth carrying: ",{"type":9,"tag":67,"props":454,"children":455},{},[456],{"type":14,"value":457},"Docker's rules are inserted ahead of a naive host firewall.",{"type":14,"value":459}," Publishing a port with ",{"type":9,"tag":33,"props":461,"children":463},{"className":462},[],[464],{"type":14,"value":465},"-p 8080:80",{"type":14,"value":467}," on a public server makes it reachable even if ",{"type":9,"tag":33,"props":469,"children":471},{"className":470},[],[472],{"type":14,"value":473},"ufw",{"type":14,"value":475}," says otherwise, because forwarded traffic never passes through the ",{"type":9,"tag":33,"props":477,"children":479},{"className":478},[],[480],{"type":14,"value":481},"INPUT",{"type":14,"value":483}," chain. Bind to ",{"type":9,"tag":33,"props":485,"children":487},{"className":486},[],[488],{"type":14,"value":489},"127.0.0.1:8080:80",{"type":14,"value":491}," when you mean local only, and put deliberate rules in the ",{"type":9,"tag":33,"props":493,"children":495},{"className":494},[],[496],{"type":14,"value":497},"DOCKER-USER",{"type":14,"value":499}," chain, which is the one place Docker will not overwrite them.",{"type":9,"tag":10,"props":501,"children":502},{},[503],{"type":14,"value":504},"Next up: Compose — declaring all of this in a file instead of remembering it.",1787908867645]