[{"data":1,"prerenderedAt":587},["ShallowReactive",2],{"mdc--7ht1zt-key":3,"mdc-2otk9i-key":16,"mdc--231isp-key":86,"mdc--i4f0wb-key":95,"mdc-9f2yji-key":177,"mdc-dqiqx4-key":253,"mdc--5sm9z1-key":265,"mdc--km8jv5-key":300,"mdc--mvuwo2-key":384,"mdc-wk1jpn-key":394,"mdc--bcj9u2-key":406},{"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","BuildKit has been Docker's default builder for years, and most Dockerfiles still do not use anything it added. This lesson is the features that make a real difference, and the one line that turns them on.",{"data":17,"body":18},{},{"type":6,"children":19},[20,27,41,54,59],{"type":9,"tag":21,"props":22,"children":24},"h2",{"id":23},"the-syntax-directive",[25],{"type":14,"value":26},"The syntax directive",{"type":9,"tag":28,"props":29,"children":35},"pre",{"className":30,"code":32,"language":33,"meta":34},[31],"language-dockerfile","# syntax=docker/dockerfile:1\n","dockerfile","",[36],{"type":9,"tag":37,"props":38,"children":39},"code",{"__ignoreMap":34},[40],{"type":14,"value":32},{"type":9,"tag":10,"props":42,"children":43},{},[44,46,52],{"type":14,"value":45},"First line of the file, before any comment you actually meant. It tells BuildKit which ",{"type":9,"tag":47,"props":48,"children":49},"strong",{},[50],{"type":14,"value":51},"frontend",{"type":14,"value":53}," to use — the parser that interprets the Dockerfile — and it downloads it.",{"type":9,"tag":10,"props":55,"children":56},{},[57],{"type":14,"value":58},"That indirection is the point. Your Dockerfile syntax is no longer tied to your Docker version. A CI runner on an older engine still gets current syntax, and new features arrive by pulling a new frontend rather than upgrading the daemon.",{"type":9,"tag":10,"props":60,"children":61},{},[62,68,70,76,78,84],{"type":9,"tag":37,"props":63,"children":65},{"className":64},[],[66],{"type":14,"value":67},"docker/dockerfile:1",{"type":14,"value":69}," tracks the latest stable 1.x. Pin harder if you need reproducibility (",{"type":9,"tag":37,"props":71,"children":73},{"className":72},[],[74],{"type":14,"value":75},"docker/dockerfile:1.26",{"type":14,"value":77},"), and use the ",{"type":9,"tag":37,"props":79,"children":81},{"className":80},[],[82],{"type":14,"value":83},"-labs",{"type":14,"value":85}," variants for experimental features.",{"data":87,"body":88},{},{"type":6,"children":89},[90],{"type":9,"tag":91,"props":92,"children":94},"terminal-teaser",{":lines":93},"[{\"cmd\":\"docker build -t app .\",\"out\":\"=> resolve image config for docker.io/docker/dockerfile:1\\n=> docker-image://docker.io/docker/dockerfile:1.26.0\\n=> [internal] load build definition from Dockerfile\"}]",[],{"data":96,"body":97},{},{"type":6,"children":98},[99,105,126,135,155,168],{"type":9,"tag":21,"props":100,"children":102},{"id":101},"heredocs",[103],{"type":14,"value":104},"Heredocs",{"type":9,"tag":10,"props":106,"children":107},{},[108,110,116,118,124],{"type":14,"value":109},"Long ",{"type":9,"tag":37,"props":111,"children":113},{"className":112},[],[114],{"type":14,"value":115},"RUN",{"type":14,"value":117}," chains held together by ",{"type":9,"tag":37,"props":119,"children":121},{"className":120},[],[122],{"type":14,"value":123},"&&",{"type":14,"value":125}," and backslashes are hard to read and harder to diff. Heredocs work:",{"type":9,"tag":28,"props":127,"children":130},{"className":128,"code":129,"language":33,"meta":34},[31],"RUN \u003C\u003CEOF\nset -eux\napt-get update\napt-get install -y --no-install-recommends curl ca-certificates\nrm -rf /var/lib/apt/lists/*\nEOF\n",[131],{"type":9,"tag":37,"props":132,"children":133},{"__ignoreMap":34},[134],{"type":14,"value":129},{"type":9,"tag":10,"props":136,"children":137},{},[138,140,146,148,153],{"type":14,"value":139},"Still one layer, still one shell, and ",{"type":9,"tag":37,"props":141,"children":143},{"className":142},[],[144],{"type":14,"value":145},"set -eux",{"type":14,"value":147}," at the top gives you what ",{"type":9,"tag":37,"props":149,"children":151},{"className":150},[],[152],{"type":14,"value":123},{"type":14,"value":154}," was really for — stopping on the first failure. Without it, each line runs independently and the layer succeeds even if the install failed.",{"type":9,"tag":10,"props":156,"children":157},{},[158,160,166],{"type":14,"value":159},"They also write files without a chain of ",{"type":9,"tag":37,"props":161,"children":163},{"className":162},[],[164],{"type":14,"value":165},"echo",{"type":14,"value":167},"s:",{"type":9,"tag":28,"props":169,"children":172},{"className":170,"code":171,"language":33,"meta":34},[31],"COPY \u003C\u003CEOF /etc/nginx/conf.d/app.conf\nserver {\n  listen 80;\n  location / { proxy_pass http://api:3000; }\n}\nEOF\n",[173],{"type":9,"tag":37,"props":174,"children":175},{"__ignoreMap":34},[176],{"type":14,"value":171},{"data":178,"body":179},{},{"type":6,"children":180},[181,187,214,223,228,233,242],{"type":9,"tag":21,"props":182,"children":184},{"id":183},"cache-mounts",[185],{"type":14,"value":186},"Cache mounts",{"type":9,"tag":10,"props":188,"children":189},{},[190,192,198,200,205,207,212],{"type":14,"value":191},"The one with the biggest day-to-day effect. ",{"type":9,"tag":37,"props":193,"children":195},{"className":194},[],[196],{"type":14,"value":197},"RUN --mount=type=cache",{"type":14,"value":199}," gives a directory that ",{"type":9,"tag":47,"props":201,"children":202},{},[203],{"type":14,"value":204},"persists across builds",{"type":14,"value":206}," but is ",{"type":9,"tag":47,"props":208,"children":209},{},[210],{"type":14,"value":211},"not part of the image",{"type":14,"value":213},":",{"type":9,"tag":28,"props":215,"children":218},{"className":216,"code":217,"language":33,"meta":34},[31],"RUN --mount=type=cache,target=/root/.npm \\\n    npm ci --omit=dev\n",[219],{"type":9,"tag":37,"props":220,"children":221},{"__ignoreMap":34},[222],{"type":14,"value":217},{"type":9,"tag":10,"props":224,"children":225},{},[226],{"type":14,"value":227},"The npm cache survives, so a rebuild after a dependency change re-downloads only what changed instead of the whole tree. And because the mount is not a layer, none of it ships.",{"type":9,"tag":10,"props":229,"children":230},{},[231],{"type":14,"value":232},"Per ecosystem:",{"type":9,"tag":28,"props":234,"children":237},{"className":235,"code":236,"language":33,"meta":34},[31],"# apt — note the extra flag to stop apt deleting its own cache\nRUN --mount=type=cache,target=/var/cache/apt,sharing=locked \\\n    --mount=type=cache,target=/var/lib/apt,sharing=locked \\\n    rm -f /etc/apt/apt.conf.d/docker-clean && \\\n    apt-get update && apt-get install -y --no-install-recommends curl\n\n# pip\nRUN --mount=type=cache,target=/root/.cache/pip \\\n    pip install -r requirements.txt\n\n# Go\nRUN --mount=type=cache,target=/go/pkg/mod \\\n    --mount=type=cache,target=/root/.cache/go-build \\\n    go build -o /out/server ./cmd/server\n\n# Cargo\nRUN --mount=type=cache,target=/usr/local/cargo/registry \\\n    --mount=type=cache,target=/app/target \\\n    cargo build --release\n",[238],{"type":9,"tag":37,"props":239,"children":240},{"__ignoreMap":34},[241],{"type":14,"value":236},{"type":9,"tag":10,"props":243,"children":244},{},[245,251],{"type":9,"tag":37,"props":246,"children":248},{"className":247},[],[249],{"type":14,"value":250},"sharing=locked",{"type":14,"value":252}," serialises concurrent builds that want the same cache, which is what apt needs — its lock files do not survive two builders at once.",{"data":254,"body":255},{},{"type":6,"children":256},[257],{"type":9,"tag":258,"props":259,"children":264},"quiz",{":answer":260,":options":261,"explanation":262,"question":263},"0","[\"The cache persists across builds but never becomes part of the image\",\"It is faster to write but otherwise identical\",\"It caches the layer itself rather than its contents\"]","A cache mount lives in the builder, not the image, so it survives cache invalidation of the layer above it and adds nothing to what you ship. A cache baked into a layer is the opposite on both counts — invalidated with the layer, and shipped forever.","How does `RUN --mount=type=cache` differ from just letting a layer hold the cache directory?",[],{"data":266,"body":267},{},{"type":6,"children":268},[269,275,286,295],{"type":9,"tag":21,"props":270,"children":272},{"id":271},"bind-mounts-at-build-time",[273],{"type":14,"value":274},"Bind mounts at build time",{"type":9,"tag":10,"props":276,"children":277},{},[278,284],{"type":9,"tag":37,"props":279,"children":281},{"className":280},[],[282],{"type":14,"value":283},"RUN --mount=type=bind",{"type":14,"value":285}," exposes files from the context or another stage for one instruction, without copying them in:",{"type":9,"tag":28,"props":287,"children":290},{"className":288,"code":289,"language":33,"meta":34},[31],"RUN --mount=type=bind,source=package.json,target=package.json \\\n    --mount=type=bind,source=package-lock.json,target=package-lock.json \\\n    --mount=type=cache,target=/root/.npm \\\n    npm ci --omit=dev\n",[291],{"type":9,"tag":37,"props":292,"children":293},{"__ignoreMap":34},[294],{"type":14,"value":289},{"type":9,"tag":10,"props":296,"children":297},{},[298],{"type":14,"value":299},"The manifests are readable during the install and are not in the resulting layer. Combined with a cache mount, this is the fastest install step you can write.",{"data":301,"body":302},{},{"type":6,"children":303},[304,310,353,362,372],{"type":9,"tag":21,"props":305,"children":307},{"id":306},"secrets-that-never-touch-a-layer",[308],{"type":14,"value":309},"Secrets that never touch a layer",{"type":9,"tag":10,"props":311,"children":312},{},[313,315,321,323,329,331,336,338,344,346,351],{"type":14,"value":314},"The important one. ",{"type":9,"tag":37,"props":316,"children":318},{"className":317},[],[319],{"type":14,"value":320},"ARG",{"type":14,"value":322}," and ",{"type":9,"tag":37,"props":324,"children":326},{"className":325},[],[327],{"type":14,"value":328},"ENV",{"type":14,"value":330}," both leak — ",{"type":9,"tag":37,"props":332,"children":334},{"className":333},[],[335],{"type":14,"value":320},{"type":14,"value":337}," shows up in ",{"type":9,"tag":37,"props":339,"children":341},{"className":340},[],[342],{"type":14,"value":343},"docker history",{"type":14,"value":345}," in plain text, and ",{"type":9,"tag":37,"props":347,"children":349},{"className":348},[],[350],{"type":14,"value":328},{"type":14,"value":352}," is baked into the image and visible to anyone who pulls it.",{"type":9,"tag":28,"props":354,"children":357},{"className":355,"code":356,"language":33,"meta":34},[31],"RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \\\n    npm ci --omit=dev\n",[358],{"type":9,"tag":37,"props":359,"children":360},{"__ignoreMap":34},[361],{"type":14,"value":356},{"type":9,"tag":28,"props":363,"children":367},{"className":364,"code":366,"language":14},[365],"language-text","docker build --secret id=npmrc,src=$HOME/.npmrc -t app .\n",[368],{"type":9,"tag":37,"props":369,"children":370},{"__ignoreMap":34},[371],{"type":14,"value":366},{"type":9,"tag":10,"props":373,"children":374},{},[375,377,382],{"type":14,"value":376},"The file is mounted as a tmpfs for the duration of that one instruction. It is not a layer, not in ",{"type":9,"tag":37,"props":378,"children":380},{"className":379},[],[381],{"type":14,"value":343},{"type":14,"value":383},", and not in the image. The next lesson is entirely about this.",{"data":385,"body":386},{},{"type":6,"children":387},[388],{"type":9,"tag":258,"props":389,"children":393},{":answer":260,":options":390,"explanation":391,"question":392},"[\"Build arguments are recorded in the image metadata and visible in `docker history`\",\"`git clone` writes the token into `.git/config`\",\"It is safe as long as the build is not pushed\"]","Both, actually — `git` does write the remote URL into `.git/config` — but the metadata leak is the one that catches people out, because it survives even if you delete the checkout. `docker history --no-trunc` on the published image shows the value. Use `--mount=type=secret`.","Why is `ARG GITHUB_TOKEN` plus `RUN git clone https://$GITHUB_TOKEN@…` unsafe even if the token is never written to a file?",[],{"data":395,"body":396},{},{"type":6,"children":397},[398],{"type":9,"tag":399,"props":400,"children":405},"fill-blank",{":answer":401,"hint":402,"placeholder":403,"prompt":404},"[\"--mount=type=cache,target=/root/.cache/pip\",\"--mount=type=cache,target=/root/.cache/pip,sharing=locked\"]","Two settings, comma-separated — what kind of mount, and where it appears.","--mount=...","Add a build-time cache mount for pip's cache directory `/root/.cache/pip` to a RUN instruction. Write just the mount flag.",[],{"data":407,"body":408},{},{"type":6,"children":409},[410,582],{"type":9,"tag":411,"props":412,"children":414},"deep-dive",{"title":413},"Build checks, SSH mounts, and `--no-cache-filter`",[415,447,456,477,486,496,505,514,534,552,561],{"type":9,"tag":10,"props":416,"children":417},{},[418,423,425,430,432,437,439,445],{"type":9,"tag":47,"props":419,"children":420},{},[421],{"type":14,"value":422},"Build checks.",{"type":14,"value":424}," BuildKit lints your Dockerfile as it builds and reports problems it can see — a stage name that collides, ",{"type":9,"tag":37,"props":426,"children":428},{"className":427},[],[429],{"type":14,"value":328},{"type":14,"value":431}," used where ",{"type":9,"tag":37,"props":433,"children":435},{"className":434},[],[436],{"type":14,"value":320},{"type":14,"value":438}," was meant, a ",{"type":9,"tag":37,"props":440,"children":442},{"className":441},[],[443],{"type":14,"value":444},"FROM",{"type":14,"value":446}," without a tag, casing inconsistencies:",{"type":9,"tag":28,"props":448,"children":451},{"className":449,"code":450,"language":14},[365],"docker build --check .\n",[452],{"type":9,"tag":37,"props":453,"children":454},{"__ignoreMap":34},[455],{"type":14,"value":450},{"type":9,"tag":10,"props":457,"children":458},{},[459,461,467,469,475],{"type":14,"value":460},"That runs the checks ",{"type":9,"tag":462,"props":463,"children":464},"em",{},[465],{"type":14,"value":466},"only",{"type":14,"value":468},", without building, which makes it a cheap CI step. The ",{"type":9,"tag":37,"props":470,"children":472},{"className":471},[],[473],{"type":14,"value":474},"check",{"type":14,"value":476}," parser directive (frontend 1.8) can promote warnings to errors so a bad Dockerfile fails the pipeline:",{"type":9,"tag":28,"props":478,"children":481},{"className":479,"code":480,"language":33,"meta":34},[31],"# syntax=docker/dockerfile:1\n# check=error=true\n",[482],{"type":9,"tag":37,"props":483,"children":484},{"__ignoreMap":34},[485],{"type":14,"value":480},{"type":9,"tag":10,"props":487,"children":488},{},[489,494],{"type":9,"tag":47,"props":490,"children":491},{},[492],{"type":14,"value":493},"SSH mounts",{"type":14,"value":495},", for private Git dependencies:",{"type":9,"tag":28,"props":497,"children":500},{"className":498,"code":499,"language":33,"meta":34},[31],"RUN --mount=type=ssh \\\n    go mod download\n",[501],{"type":9,"tag":37,"props":502,"children":503},{"__ignoreMap":34},[504],{"type":14,"value":499},{"type":9,"tag":28,"props":506,"children":509},{"className":507,"code":508,"language":14},[365],"docker build --ssh default .\n",[510],{"type":9,"tag":37,"props":511,"children":512},{"__ignoreMap":34},[513],{"type":14,"value":508},{"type":9,"tag":10,"props":515,"children":516},{},[517,519,524,526,532],{"type":14,"value":518},"The builder gets access to your local SSH ",{"type":9,"tag":47,"props":520,"children":521},{},[522],{"type":14,"value":523},"agent",{"type":14,"value":525},", not your key. The key never enters the build, so it cannot end up in a layer. This is how a private Go module or a ",{"type":9,"tag":37,"props":527,"children":529},{"className":528},[],[530],{"type":14,"value":531},"git+ssh",{"type":14,"value":533}," npm dependency should be fetched — and it is what the two Nuxt apps in this workspace do for the rendering engine.",{"type":9,"tag":10,"props":535,"children":536},{},[537,542,544,550],{"type":9,"tag":47,"props":538,"children":539},{},[540],{"type":14,"value":541},"Targeted cache busting.",{"type":14,"value":543}," ",{"type":9,"tag":37,"props":545,"children":547},{"className":546},[],[548],{"type":14,"value":549},"--no-cache",{"type":14,"value":551}," rebuilds everything, which is usually more than you wanted:",{"type":9,"tag":28,"props":553,"children":556},{"className":554,"code":555,"language":14},[365],"docker build --no-cache-filter deps -t app .\n",[557],{"type":9,"tag":37,"props":558,"children":559},{"__ignoreMap":34},[560],{"type":14,"value":555},{"type":9,"tag":10,"props":562,"children":563},{},[564,566,572,574,580],{"type":14,"value":565},"That invalidates the ",{"type":9,"tag":37,"props":567,"children":569},{"className":568},[],[570],{"type":14,"value":571},"deps",{"type":14,"value":573}," stage and keeps everything else. It is the right tool when a cache mount has gone stale or an ",{"type":9,"tag":37,"props":575,"children":577},{"className":576},[],[578],{"type":14,"value":579},"apt-get update",{"type":14,"value":581}," is serving package lists that no longer exist on the mirror.",{"type":9,"tag":10,"props":583,"children":584},{},[585],{"type":14,"value":586},"Next up: build secrets in full — the several ways credentials end up in an image, and the one way they do not.",1787908867679]