[{"data":1,"prerenderedAt":720},["ShallowReactive",2],{"mdc-t1omq-key":3,"mdc--e8ojqk-key":16,"mdc-4aypk-key":51,"mdc-igy0au-key":105,"mdc--bplesm-key":273,"mdc--p724zu-key":285,"mdc--4fenvj-key":452,"mdc--sp28nv-key":515,"mdc-jglr0v-key":525,"mdc--odqxn0-key":613,"mdc-n5rwwh-key":625},{"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","A Dockerfile is a recipe for an image. It is read top to bottom, each instruction produces a layer, and the result is something anyone can run without knowing anything about your language, your runtime, or your machine.",{"data":17,"body":18},{},{"type":6,"children":19},[20,27,32,46],{"type":9,"tag":21,"props":22,"children":24},"h2",{"id":23},"the-smallest-useful-one",[25],{"type":14,"value":26},"The smallest useful one",{"type":9,"tag":10,"props":28,"children":29},{},[30],{"type":14,"value":31},"For a Node service:",{"type":9,"tag":33,"props":34,"children":40},"pre",{"className":35,"code":37,"language":38,"meta":39},[36],"language-dockerfile","# syntax=docker/dockerfile:1\nFROM node:22-alpine\n\nWORKDIR /app\n\nCOPY package*.json ./\nRUN npm ci --omit=dev\n\nCOPY . .\n\nEXPOSE 3000\nCMD [\"node\", \"server.js\"]\n","dockerfile","",[41],{"type":9,"tag":42,"props":43,"children":44},"code",{"__ignoreMap":39},[45],{"type":14,"value":37},{"type":9,"tag":10,"props":47,"children":48},{},[49],{"type":14,"value":50},"Build and run it:",{"data":52,"body":53},{},{"type":6,"children":54},[55,60],{"type":9,"tag":56,"props":57,"children":59},"terminal-teaser",{":lines":58},"[{\"cmd\":\"docker build -t myapp:1.0 .\",\"out\":\"[+] Building 12.4s (10/10) FINISHED\\n => [1/5] FROM docker.io/library/node:22-alpine\\n => [4/5] RUN npm ci --omit=dev\\n => exporting to image\\n => => naming to docker.io/library/myapp:1.0\"},{\"cmd\":\"docker run -d -p 3000:3000 myapp:1.0\",\"out\":\"b71f0ac93e18\"}]",[],{"type":9,"tag":10,"props":61,"children":62},{},[63,65,71,73,79,81,87,89,95,97,103],{"type":14,"value":64},"The trailing ",{"type":9,"tag":42,"props":66,"children":68},{"className":67},[],[69],{"type":14,"value":70},".",{"type":14,"value":72}," in ",{"type":9,"tag":42,"props":74,"children":76},{"className":75},[],[77],{"type":14,"value":78},"docker build -t myapp:1.0 .",{"type":14,"value":80}," is the ",{"type":9,"tag":82,"props":83,"children":84},"strong",{},[85],{"type":14,"value":86},"build context",{"type":14,"value":88}," — the directory sent to the builder. It is not the Dockerfile's location (that is ",{"type":9,"tag":42,"props":90,"children":92},{"className":91},[],[93],{"type":14,"value":94},"-f",{"type":14,"value":96},"). Passing ",{"type":9,"tag":42,"props":98,"children":100},{"className":99},[],[101],{"type":14,"value":102},"/",{"type":14,"value":104}," or your home directory as a context is a classic way to make a build take ten minutes and produce a two-gigabyte image.",{"data":106,"body":107},{},{"type":6,"children":108},[109,115,136,150,180,202,215,252],{"type":9,"tag":21,"props":110,"children":112},{"id":111},"what-each-instruction-does",[113],{"type":14,"value":114},"What each instruction does",{"type":9,"tag":10,"props":116,"children":117},{},[118,127,129,134],{"type":9,"tag":82,"props":119,"children":120},{},[121],{"type":9,"tag":42,"props":122,"children":124},{"className":123},[],[125],{"type":14,"value":126},"# syntax=docker/dockerfile:1",{"type":14,"value":128}," — the first line, and the one most tutorials omit. It pins the ",{"type":9,"tag":82,"props":130,"children":131},{},[132],{"type":14,"value":133},"Dockerfile frontend",{"type":14,"value":135},": the parser BuildKit downloads to interpret this file. With it, you get current syntax regardless of how old the installed Docker is, and new features arrive without upgrading the engine. Without it, you are stuck on whatever the engine shipped with. Always include it.",{"type":9,"tag":10,"props":137,"children":138},{},[139,148],{"type":9,"tag":82,"props":140,"children":141},{},[142],{"type":9,"tag":42,"props":143,"children":145},{"className":144},[],[146],{"type":14,"value":147},"FROM",{"type":14,"value":149}," — the base image, and the start of a stage. Every Dockerfile has at least one.",{"type":9,"tag":10,"props":151,"children":152},{},[153,162,164,170,172,178],{"type":9,"tag":82,"props":154,"children":155},{},[156],{"type":9,"tag":42,"props":157,"children":159},{"className":158},[],[160],{"type":14,"value":161},"WORKDIR",{"type":14,"value":163}," — sets the directory for everything after it, creating it if needed. Use it rather than ",{"type":9,"tag":42,"props":165,"children":167},{"className":166},[],[168],{"type":14,"value":169},"RUN cd /app",{"type":14,"value":171},", which does nothing useful because each ",{"type":9,"tag":42,"props":173,"children":175},{"className":174},[],[176],{"type":14,"value":177},"RUN",{"type":14,"value":179}," is a separate shell.",{"type":9,"tag":10,"props":181,"children":182},{},[183,192,194,200],{"type":9,"tag":82,"props":184,"children":185},{},[186],{"type":9,"tag":42,"props":187,"children":189},{"className":188},[],[190],{"type":14,"value":191},"COPY",{"type":14,"value":193}," — copies from the build context into the image. ",{"type":9,"tag":42,"props":195,"children":197},{"className":196},[],[198],{"type":14,"value":199},"COPY . .",{"type":14,"value":201}," means \"context root into the current WORKDIR\".",{"type":9,"tag":10,"props":203,"children":204},{},[205,213],{"type":9,"tag":82,"props":206,"children":207},{},[208],{"type":9,"tag":42,"props":209,"children":211},{"className":210},[],[212],{"type":14,"value":177},{"type":14,"value":214}," — executes a command at build time and commits the result as a layer.",{"type":9,"tag":10,"props":216,"children":217},{},[218,227,229,234,236,242,244,250],{"type":9,"tag":82,"props":219,"children":220},{},[221],{"type":9,"tag":42,"props":222,"children":224},{"className":223},[],[225],{"type":14,"value":226},"EXPOSE",{"type":14,"value":228}," — documentation. It does ",{"type":9,"tag":82,"props":230,"children":231},{},[232],{"type":14,"value":233},"not",{"type":14,"value":235}," publish anything; it records which port the image expects to serve on, which ",{"type":9,"tag":42,"props":237,"children":239},{"className":238},[],[240],{"type":14,"value":241},"-P",{"type":14,"value":243}," and some tooling read. Publishing is ",{"type":9,"tag":42,"props":245,"children":247},{"className":246},[],[248],{"type":14,"value":249},"-p",{"type":14,"value":251}," at run time.",{"type":9,"tag":10,"props":253,"children":254},{},[255,264,266,272],{"type":9,"tag":82,"props":256,"children":257},{},[258],{"type":9,"tag":42,"props":259,"children":261},{"className":260},[],[262],{"type":14,"value":263},"CMD",{"type":14,"value":265}," — the default command. Overridden by anything you type after the image name in ",{"type":9,"tag":42,"props":267,"children":269},{"className":268},[],[270],{"type":14,"value":271},"docker run",{"type":14,"value":70},{"data":274,"body":275},{},{"type":6,"children":276},[277],{"type":9,"tag":278,"props":279,"children":284},"quiz",{":answer":280,":options":281,"explanation":282,"question":283},"0","[\"`EXPOSE` is only metadata — you still need `-p 3000:3000` to publish the port\",\"The app is bound to the wrong interface\",\"`EXPOSE` requires a matching `--expose` flag at run time\"]","`EXPOSE` documents intent and nothing more. It makes `-P` work and tells a reader what the image wants, but it opens nothing. (Binding to `127.0.0.1` *inside* the container is the other classic cause of the same symptom — inside a container, listen on `0.0.0.0`.)","Your Dockerfile has `EXPOSE 3000`. You run `docker run -d myapp` and cannot reach port 3000 from the host. Why?",[],{"data":286,"body":287},{},{"type":6,"children":288},[289,306,311,320,361,370,415],{"type":9,"tag":21,"props":290,"children":292},{"id":291},"cmd-versus-entrypoint",[293,298,300],{"type":9,"tag":42,"props":294,"children":296},{"className":295},[],[297],{"type":14,"value":263},{"type":14,"value":299}," versus ",{"type":9,"tag":42,"props":301,"children":303},{"className":302},[],[304],{"type":14,"value":305},"ENTRYPOINT",{"type":9,"tag":10,"props":307,"children":308},{},[309],{"type":14,"value":310},"Both say what runs. The difference is what happens when someone passes arguments.",{"type":9,"tag":33,"props":312,"children":315},{"className":313,"code":314,"language":38,"meta":39},[36],"CMD [\"node\", \"server.js\"]\n",[316],{"type":9,"tag":42,"props":317,"children":318},{"__ignoreMap":39},[319],{"type":14,"value":314},{"type":9,"tag":10,"props":321,"children":322},{},[323,329,331,337,339,345,346,352,354,359],{"type":9,"tag":42,"props":324,"children":326},{"className":325},[],[327],{"type":14,"value":328},"docker run myapp",{"type":14,"value":330}," runs ",{"type":9,"tag":42,"props":332,"children":334},{"className":333},[],[335],{"type":14,"value":336},"node server.js",{"type":14,"value":338},". ",{"type":9,"tag":42,"props":340,"children":342},{"className":341},[],[343],{"type":14,"value":344},"docker run myapp sh",{"type":14,"value":330},{"type":9,"tag":42,"props":347,"children":349},{"className":348},[],[350],{"type":14,"value":351},"sh",{"type":14,"value":353}," instead — ",{"type":9,"tag":42,"props":355,"children":357},{"className":356},[],[358],{"type":14,"value":263},{"type":14,"value":360}," is entirely replaced.",{"type":9,"tag":33,"props":362,"children":365},{"className":363,"code":364,"language":38,"meta":39},[36],"ENTRYPOINT [\"node\"]\nCMD [\"server.js\"]\n",[366],{"type":9,"tag":42,"props":367,"children":368},{"__ignoreMap":39},[369],{"type":14,"value":364},{"type":9,"tag":10,"props":371,"children":372},{},[373,378,380,385,387,393,394,400,402,407,409,414],{"type":9,"tag":42,"props":374,"children":376},{"className":375},[],[377],{"type":14,"value":328},{"type":14,"value":379}," still runs ",{"type":9,"tag":42,"props":381,"children":383},{"className":382},[],[384],{"type":14,"value":336},{"type":14,"value":386},". But ",{"type":9,"tag":42,"props":388,"children":390},{"className":389},[],[391],{"type":14,"value":392},"docker run myapp worker.js",{"type":14,"value":330},{"type":9,"tag":42,"props":395,"children":397},{"className":396},[],[398],{"type":14,"value":399},"node worker.js",{"type":14,"value":401}," — the argument replaces ",{"type":9,"tag":42,"props":403,"children":405},{"className":404},[],[406],{"type":14,"value":263},{"type":14,"value":408}," and is appended to ",{"type":9,"tag":42,"props":410,"children":412},{"className":411},[],[413],{"type":14,"value":305},{"type":14,"value":70},{"type":9,"tag":10,"props":416,"children":417},{},[418,420,438,440,450],{"type":14,"value":419},"The rule of thumb: ",{"type":9,"tag":82,"props":421,"children":422},{},[423,428,430,436],{"type":9,"tag":42,"props":424,"children":426},{"className":425},[],[427],{"type":14,"value":305},{"type":14,"value":429}," when the image ",{"type":9,"tag":431,"props":432,"children":433},"em",{},[434],{"type":14,"value":435},"is",{"type":14,"value":437}," one tool",{"type":14,"value":439}," and the arguments are its arguments; ",{"type":9,"tag":82,"props":441,"children":442},{},[443,448],{"type":9,"tag":42,"props":444,"children":446},{"className":445},[],[447],{"type":14,"value":263},{"type":14,"value":449}," alone when the image is an environment",{"type":14,"value":451}," someone might want to poke at with a shell.",{"data":453,"body":454},{},{"type":6,"children":455},[456,462,471],{"type":9,"tag":21,"props":457,"children":459},{"id":458},"exec-form-not-shell-form",[460],{"type":14,"value":461},"Exec form, not shell form",{"type":9,"tag":33,"props":463,"children":466},{"className":464,"code":465,"language":38,"meta":39},[36],"CMD [\"node\", \"server.js\"]      # exec form — a JSON array\nCMD node server.js             # shell form — wrapped in /bin/sh -c\n",[467],{"type":9,"tag":42,"props":468,"children":469},{"__ignoreMap":39},[470],{"type":14,"value":465},{"type":9,"tag":10,"props":472,"children":473},{},[474,476,482,484,489,491,497,499,505,507,513],{"type":14,"value":475},"Use the array. Shell form runs your process as a child of ",{"type":9,"tag":42,"props":477,"children":479},{"className":478},[],[480],{"type":14,"value":481},"/bin/sh",{"type":14,"value":483},", so ",{"type":9,"tag":82,"props":485,"children":486},{},[487],{"type":14,"value":488},"the shell is PID 1 and your process is not",{"type":14,"value":490}," — and most shells do not forward signals to children. ",{"type":9,"tag":42,"props":492,"children":494},{"className":493},[],[495],{"type":14,"value":496},"docker stop",{"type":14,"value":498}," sends ",{"type":9,"tag":42,"props":500,"children":502},{"className":501},[],[503],{"type":14,"value":504},"SIGTERM",{"type":14,"value":506}," to PID 1, the shell ignores it, and ten seconds later everything is ",{"type":9,"tag":42,"props":508,"children":510},{"className":509},[],[511],{"type":14,"value":512},"SIGKILL",{"type":14,"value":514},"ed. That is the usual explanation for a container that always takes exactly ten seconds to stop.",{"data":516,"body":517},{},{"type":6,"children":518},[519],{"type":9,"tag":278,"props":520,"children":524},{":answer":280,":options":521,"explanation":522,"question":523},"[\"Exec form makes Python PID 1 directly, so `docker stop`'s SIGTERM reaches it instead of a shell that ignores it\",\"Exec form starts faster\",\"Shell form cannot pass arguments\"]","Shell form becomes `/bin/sh -c \"python app.py\"`, so the shell is PID 1. It does not forward SIGTERM, so your app never gets the chance to finish requests, flush, and exit — it is killed after the grace period.","Why does `CMD [\"python\", \"app.py\"]` shut down more cleanly than `CMD python app.py`?",[],{"data":526,"body":527},{},{"type":6,"children":528},[529,539,575,585],{"type":9,"tag":21,"props":530,"children":532},{"id":531},"dockerignore",[533],{"type":9,"tag":42,"props":534,"children":536},{"className":535},[],[537],{"type":14,"value":538},".dockerignore",{"type":9,"tag":10,"props":540,"children":541},{},[542,544,549,551,557,559,565,567,573],{"type":14,"value":543},"The build context is uploaded to the builder before anything runs. Without a ",{"type":9,"tag":42,"props":545,"children":547},{"className":546},[],[548],{"type":14,"value":538},{"type":14,"value":550},", that includes ",{"type":9,"tag":42,"props":552,"children":554},{"className":553},[],[555],{"type":14,"value":556},"node_modules",{"type":14,"value":558},", ",{"type":9,"tag":42,"props":560,"children":562},{"className":561},[],[563],{"type":14,"value":564},".git",{"type":14,"value":566},", build output, local ",{"type":9,"tag":42,"props":568,"children":570},{"className":569},[],[571],{"type":14,"value":572},".env",{"type":14,"value":574}," files, and everything else in the directory.",{"type":9,"tag":33,"props":576,"children":580},{"className":577,"code":579,"language":14},[578],"language-text",".git\nnode_modules\ndist\n*.log\n.env\n.env.*\n",[581],{"type":9,"tag":42,"props":582,"children":583},{"__ignoreMap":39},[584],{"type":14,"value":579},{"type":9,"tag":10,"props":586,"children":587},{},[588,590,595,597,603,605,611],{"type":14,"value":589},"Two reasons this matters, and the second is the serious one. It makes builds faster and images smaller. And it stops ",{"type":9,"tag":42,"props":591,"children":593},{"className":592},[],[594],{"type":14,"value":199},{"type":14,"value":596}," from baking your local secrets into a layer that anyone who pulls the image can read — ",{"type":9,"tag":42,"props":598,"children":600},{"className":599},[],[601],{"type":14,"value":602},"docker history",{"type":14,"value":604}," and a ",{"type":9,"tag":42,"props":606,"children":608},{"className":607},[],[609],{"type":14,"value":610},"tar",{"type":14,"value":612}," extraction will find them, and rebuilding without the file does not remove it from a published image.",{"data":614,"body":615},{},{"type":6,"children":616},[617],{"type":9,"tag":618,"props":619,"children":624},"fill-blank",{":answer":620,"hint":621,"placeholder":622,"prompt":623},"[\"docker build -t api:2.1 .\",\"docker build --tag api:2.1 .\",\"docker build -t api:2.1 ./\"]","Tag flag, then the build context — which is a single character.","docker build ...","Build the Dockerfile in the current directory, tagging the result `api:2.1`.",[],{"data":626,"body":627},{},{"type":6,"children":628},[629,715],{"type":9,"tag":630,"props":631,"children":633},"deep-dive",{"title":632},"`docker init` writes this for you",[634,639,648,693,698,710],{"type":9,"tag":10,"props":635,"children":636},{},[637],{"type":14,"value":638},"Docker ships a scaffolding command that generates a sensible starting point rather than making you remember the shape:",{"type":9,"tag":33,"props":640,"children":643},{"className":641,"code":642,"language":14},[578],"docker init\n",[644],{"type":9,"tag":42,"props":645,"children":646},{"__ignoreMap":39},[647],{"type":14,"value":642},{"type":9,"tag":10,"props":649,"children":650},{},[651,653,662,663,672,673,681,683,692],{"type":14,"value":652},"It asks what your project is, then writes four files: ",{"type":9,"tag":82,"props":654,"children":655},{},[656],{"type":9,"tag":42,"props":657,"children":659},{"className":658},[],[660],{"type":14,"value":661},"Dockerfile",{"type":14,"value":558},{"type":9,"tag":82,"props":664,"children":665},{},[666],{"type":9,"tag":42,"props":667,"children":669},{"className":668},[],[670],{"type":14,"value":671},"compose.yaml",{"type":14,"value":558},{"type":9,"tag":82,"props":674,"children":675},{},[676],{"type":9,"tag":42,"props":677,"children":679},{"className":678},[],[680],{"type":14,"value":538},{"type":14,"value":682},", and ",{"type":9,"tag":82,"props":684,"children":685},{},[686],{"type":9,"tag":42,"props":687,"children":689},{"className":688},[],[690],{"type":14,"value":691},"README.Docker.md",{"type":14,"value":70},{"type":9,"tag":10,"props":694,"children":695},{},[696],{"type":14,"value":697},"Templates exist for ASP.NET Core, Go, Java (Maven, uber-jar), Node, PHP with Apache, Python, Rust, and a general-purpose \"Other\".",{"type":9,"tag":10,"props":699,"children":700},{},[701,703,708],{"type":14,"value":702},"What it produces is genuinely good — multi-stage where the language benefits, a non-root user, a pinned base version, a real ",{"type":9,"tag":42,"props":704,"children":706},{"className":705},[],[707],{"type":14,"value":538},{"type":14,"value":709},". It is a better starting point than most tutorials, this lesson's deliberately minimal example included.",{"type":9,"tag":10,"props":711,"children":712},{},[713],{"type":14,"value":714},"Two caveats. It overwrites existing files after prompting, so run it in a clean directory or read the diff. And treat the output as a draft: it cannot know that your build needs a native toolchain, or which of your directories holds state. Generating it and then understanding every line is the useful workflow — which is what the rest of this course is for.",{"type":9,"tag":10,"props":716,"children":717},{},[718],{"type":14,"value":719},"Next up: layers and the build cache — why the order of those instructions is not arbitrary.",1787908867629]