[{"data":1,"prerenderedAt":425},["ShallowReactive",2],{"mdc--ys6gku-key":3,"mdc-prqc9g-key":27,"mdc--sxv8sz-key":84,"mdc-y23qr4-key":105,"mdc-vp13ib-key":117,"mdc-3z09dv-key":168,"mdc--kn3iag-key":221,"mdc-vrwfp9-key":233,"mdc--j73l96-key":278},{"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","By the end of the last lesson a single build command had six flags. A real project has several images, each with its own context, arguments, tags, platforms, and cache settings — and that turns into a shell script nobody wants to maintain.",{"type":9,"tag":10,"props":17,"children":18},{},[19,25],{"type":9,"tag":20,"props":21,"children":22},"strong",{},[23],{"type":14,"value":24},"Bake",{"type":14,"value":26}," is that configuration as a file. And since Compose v5 it is no longer optional knowledge: Compose removed its internal builder and delegates every build to Bake.",{"data":28,"body":29},{},{"type":6,"children":30},[31,38,50,63,73],{"type":9,"tag":32,"props":33,"children":35},"h2",{"id":34},"a-bake-file",[36],{"type":14,"value":37},"A bake file",{"type":9,"tag":10,"props":39,"children":40},{},[41,48],{"type":9,"tag":42,"props":43,"children":45},"code",{"className":44},[],[46],{"type":14,"value":47},"docker-bake.hcl",{"type":14,"value":49}," in the project root:",{"type":9,"tag":51,"props":52,"children":58},"pre",{"className":53,"code":55,"language":56,"meta":57},[54],"language-hcl","group \"default\" {\n  targets = [\"api\", \"web\"]\n}\n\ntarget \"api\" {\n  context    = \"./api\"\n  dockerfile = \"Dockerfile\"\n  tags       = [\"ghcr.io/acme/api:dev\"]\n  platforms  = [\"linux/amd64\", \"linux/arm64\"]\n}\n\ntarget \"web\" {\n  context = \"./web\"\n  tags    = [\"ghcr.io/acme/web:dev\"]\n  args    = { NODE_VERSION = \"22\" }\n}\n","hcl","",[59],{"type":9,"tag":42,"props":60,"children":61},{"__ignoreMap":57},[62],{"type":14,"value":55},{"type":9,"tag":51,"props":64,"children":68},{"className":65,"code":67,"language":14},[66],"language-text","docker buildx bake                # builds the default group, in parallel\ndocker buildx bake api            # just one target\ndocker buildx bake --push         # override output for every target\ndocker buildx bake --print        # render the resolved JSON without building\n",[69],{"type":9,"tag":42,"props":70,"children":71},{"__ignoreMap":57},[72],{"type":14,"value":67},{"type":9,"tag":10,"props":74,"children":75},{},[76,82],{"type":9,"tag":42,"props":77,"children":79},{"className":78},[],[80],{"type":14,"value":81},"--print",{"type":14,"value":83}," is the one to reach for first when something is not doing what you expect. It shows exactly what each target resolved to after inheritance and variables.",{"data":85,"body":86},{},{"type":6,"children":87},[88,93],{"type":9,"tag":89,"props":90,"children":92},"terminal-teaser",{":lines":91},"[{\"cmd\":\"docker buildx bake --print api\",\"out\":\"{\\n  \\\"target\\\": {\\n    \\\"api\\\": {\\n      \\\"context\\\": \\\"./api\\\",\\n      \\\"dockerfile\\\": \\\"Dockerfile\\\",\\n      \\\"tags\\\": [\\\"ghcr.io/acme/api:dev\\\"],\\n      \\\"platforms\\\": [\\\"linux/amd64\\\", \\\"linux/arm64\\\"]\\n    }\\n  }\\n}\"},{\"cmd\":\"docker buildx bake\",\"out\":\"[+] Building 31.7s (24/24) FINISHED\\n => [api] exporting to image\\n => [web] exporting to image\"}]",[],{"type":9,"tag":10,"props":94,"children":95},{},[96,98,103],{"type":14,"value":97},"Targets in a group build ",{"type":9,"tag":20,"props":99,"children":100},{},[101],{"type":14,"value":102},"concurrently",{"type":14,"value":104},", sharing one BuildKit instance and one cache. That is most of the speed win on a repo with several images.",{"data":106,"body":107},{},{"type":6,"children":108},[109],{"type":9,"tag":110,"props":111,"children":116},"quiz",{":answer":112,":options":113,"explanation":114,"question":115},"0","[\"Renders the fully resolved build configuration as JSON without building anything\",\"Prints the build log of the last run\",\"Prints the Dockerfile each target would use\"]","It resolves inheritance, variables and functions and shows you the result. When a target is picking up the wrong tag or platform, this answers why in one command instead of by bisecting the file.","What does `docker buildx bake --print` do?",[],{"data":118,"body":119},{},{"type":6,"children":120},[121,127,132,141,146,155],{"type":9,"tag":32,"props":122,"children":124},{"id":123},"inheritance-and-variables",[125],{"type":14,"value":126},"Inheritance and variables",{"type":9,"tag":10,"props":128,"children":129},{},[130],{"type":14,"value":131},"Targets inherit, so the shared parts are written once:",{"type":9,"tag":51,"props":133,"children":136},{"className":134,"code":135,"language":56,"meta":57},[54],"variable \"TAG\" { default = \"dev\" }\nvariable \"REGISTRY\" { default = \"ghcr.io/acme\" }\n\ntarget \"_common\" {\n  platforms = [\"linux/amd64\", \"linux/arm64\"]\n  args = { BUILDKIT_INLINE_CACHE = \"1\" }\n}\n\ntarget \"api\" {\n  inherits = [\"_common\"]\n  context  = \"./api\"\n  tags     = [\"${REGISTRY}/api:${TAG}\"]\n}\n\ntarget \"web\" {\n  inherits = [\"_common\"]\n  context  = \"./web\"\n  tags     = [\"${REGISTRY}/web:${TAG}\"]\n}\n",[137],{"type":9,"tag":42,"props":138,"children":139},{"__ignoreMap":57},[140],{"type":14,"value":135},{"type":9,"tag":10,"props":142,"children":143},{},[144],{"type":14,"value":145},"Variables come from the environment, so CI sets them without editing the file:",{"type":9,"tag":51,"props":147,"children":150},{"className":148,"code":149,"language":14},[66],"TAG=$(git rev-parse --short HEAD) docker buildx bake --push\n",[151],{"type":9,"tag":42,"props":152,"children":153},{"__ignoreMap":57},[154],{"type":14,"value":149},{"type":9,"tag":10,"props":156,"children":157},{},[158,160,166],{"type":14,"value":159},"A leading underscore is a convention, not syntax — ",{"type":9,"tag":42,"props":161,"children":163},{"className":162},[],[164],{"type":14,"value":165},"_common",{"type":14,"value":167}," is still a real target, it is simply not in any group, so nothing builds it directly.",{"data":169,"body":170},{},{"type":6,"children":171},[172,178,183,192],{"type":9,"tag":32,"props":173,"children":175},{"id":174},"matrix-builds",[176],{"type":14,"value":177},"Matrix builds",{"type":9,"tag":10,"props":179,"children":180},{},[181],{"type":14,"value":182},"One target definition, several combinations:",{"type":9,"tag":51,"props":184,"children":187},{"className":185,"code":186,"language":56,"meta":57},[54],"target \"api\" {\n  name = \"api-${tgt}\"\n  matrix = {\n    tgt = [\"debug\", \"release\"]\n  }\n  target = tgt\n  tags   = [\"ghcr.io/acme/api:${tgt}\"]\n}\n",[188],{"type":9,"tag":42,"props":189,"children":190},{"__ignoreMap":57},[191],{"type":14,"value":186},{"type":9,"tag":10,"props":193,"children":194},{},[195,197,203,205,211,213,219],{"type":14,"value":196},"That expands to ",{"type":9,"tag":42,"props":198,"children":200},{"className":199},[],[201],{"type":14,"value":202},"api-debug",{"type":14,"value":204}," and ",{"type":9,"tag":42,"props":206,"children":208},{"className":207},[],[209],{"type":14,"value":210},"api-release",{"type":14,"value":212},", each stopping at a different Dockerfile stage. The ",{"type":9,"tag":42,"props":214,"children":216},{"className":215},[],[217],{"type":14,"value":218},"name",{"type":14,"value":220}," field is required when a matrix is present, because the targets need distinct names.",{"data":222,"body":223},{},{"type":6,"children":224},[225],{"type":9,"tag":226,"props":227,"children":232},"fill-blank",{":answer":228,"hint":229,"placeholder":230,"prompt":231},"[\"docker buildx bake --push\",\"docker buildx bake --push default\",\"docker buildx bake default --push\"]","Three words plus an output flag; the default group needs no naming.","docker buildx bake ...","Build every target in the default group and push the results.",[],{"data":234,"body":235},{},{"type":6,"children":236},[237,243,264,273],{"type":9,"tag":32,"props":238,"children":240},{"id":239},"bake-reads-your-compose-file",[241],{"type":14,"value":242},"Bake reads your Compose file",{"type":9,"tag":10,"props":244,"children":245},{},[246,248,254,256,262],{"type":14,"value":247},"You do not have to write HCL. Bake accepts a ",{"type":9,"tag":42,"props":249,"children":251},{"className":250},[],[252],{"type":14,"value":253},"compose.yaml",{"type":14,"value":255}," and treats each service with a ",{"type":9,"tag":42,"props":257,"children":259},{"className":258},[],[260],{"type":14,"value":261},"build:",{"type":14,"value":263}," section as a target:",{"type":9,"tag":51,"props":265,"children":268},{"className":266,"code":267,"language":14},[66],"docker buildx bake -f compose.yaml\ndocker buildx bake -f compose.yaml -f docker-bake.hcl api\n",[269],{"type":9,"tag":42,"props":270,"children":271},{"__ignoreMap":57},[272],{"type":14,"value":267},{"type":9,"tag":10,"props":274,"children":275},{},[276],{"type":14,"value":277},"Multiple files merge, so a common pattern is to keep the service definitions in Compose and add the things Compose has no key for — platforms, cache backends, output types — in a small HCL file alongside.",{"data":279,"body":280},{},{"type":6,"children":281},[282,420],{"type":9,"tag":283,"props":284,"children":286},"deep-dive",{"title":285},"Why Compose v5 removed its own builder",[287,307,335,340,399],{"type":9,"tag":10,"props":288,"children":289},{},[290,292,297,299,305],{"type":14,"value":291},"Compose v5.0.0 (\"Mont Blanc\", December 2025) ",{"type":9,"tag":20,"props":293,"children":294},{},[295],{"type":14,"value":296},"dropped its internal BuildKit builder",{"type":14,"value":298}," and delegates builds to Bake, the same path ",{"type":9,"tag":42,"props":300,"children":302},{"className":301},[],[303],{"type":14,"value":304},"docker build",{"type":14,"value":306}," takes.",{"type":9,"tag":10,"props":308,"children":309},{},[310,312,317,319,325,327,333],{"type":14,"value":311},"Before that, three code paths could build a container image — ",{"type":9,"tag":42,"props":313,"children":315},{"className":314},[],[316],{"type":14,"value":304},{"type":14,"value":318},", ",{"type":9,"tag":42,"props":320,"children":322},{"className":321},[],[323],{"type":14,"value":324},"docker buildx build",{"type":14,"value":326},", and ",{"type":9,"tag":42,"props":328,"children":330},{"className":329},[],[331],{"type":14,"value":332},"docker compose build",{"type":14,"value":334}," — and they differed in small, undocumented ways: which cache they used, whether a multi-platform build worked, which flags were honoured. A build that worked one way and not another was a genuinely confusing thing to debug.",{"type":9,"tag":10,"props":336,"children":337},{},[338],{"type":14,"value":339},"Now there is one. What follows in practice:",{"type":9,"tag":341,"props":342,"children":343},"ul",{},[344,367,384],{"type":9,"tag":345,"props":346,"children":347},"li",{},[348,365],{"type":9,"tag":20,"props":349,"children":350},{},[351,356,357,363],{"type":9,"tag":42,"props":352,"children":354},{"className":353},[],[355],{"type":14,"value":332},{"type":14,"value":204},{"type":9,"tag":42,"props":358,"children":360},{"className":359},[],[361],{"type":14,"value":362},"docker buildx bake",{"type":14,"value":364}," produce the same result",{"type":14,"value":366},", because they are the same code.",{"type":9,"tag":345,"props":368,"children":369},{},[370,375,377,382],{"type":9,"tag":20,"props":371,"children":372},{},[373],{"type":14,"value":374},"Bake features reach Compose projects",{"type":14,"value":376},", including cache backends and output types that ",{"type":9,"tag":42,"props":378,"children":380},{"className":379},[],[381],{"type":14,"value":253},{"type":14,"value":383}," has no key for — which is exactly what the two-file pattern above is for.",{"type":9,"tag":345,"props":385,"children":386},{},[387,397],{"type":9,"tag":20,"props":388,"children":389},{},[390,395],{"type":9,"tag":42,"props":391,"children":393},{"className":392},[],[394],{"type":14,"value":332},{"type":14,"value":396}," needs a buildx-capable setup.",{"type":14,"value":398}," On a stripped-down engine without the buildx plugin, this is a real change rather than a cosmetic one.",{"type":9,"tag":10,"props":400,"children":401},{},[402,404,410,412,418],{"type":14,"value":403},"The wider point, which is worth carrying beyond Docker: when a tool's release notes say a subsystem was ",{"type":9,"tag":405,"props":406,"children":407},"em",{},[408],{"type":14,"value":409},"removed and delegated",{"type":14,"value":411},", the behaviour you had memorised for it is the thing most likely to have moved. Compose v5's other headline changes are all reconciliation — how it decides what to recreate on ",{"type":9,"tag":42,"props":413,"children":415},{"className":414},[],[416],{"type":14,"value":417},"up",{"type":14,"value":419}," — rewritten in v5.2, extended to volumes and networks in v5.4, and tightened in v5.5 to stop recreating containers over image-digest churn.",{"type":9,"tag":10,"props":421,"children":422},{},[423],{"type":14,"value":424},"Next up: Compose for real applications — profiles, overrides, includes, and the lifecycle hooks that replace entrypoint shell scripts.",1787908867692]