{"id":"l-dk-2-4","courseId":"docker-kubernetes","lang":"en","kind":"lesson","summary":"Scan notes-api images for known vulnerabilities with Docker Scout, triage what you find, and use BuildKit secret mounts so tokens needed at build time never land in an image layer.","html":"<p>The worst incident on my record was not a crash. A build step needed a token for a private npm registry, so someone passed it as a build argument. The image went to a registry with broad read access, and months later a routine audit found the token sitting in the image metadata, valid the whole time. This lesson is about what’s inside your images, the parts you didn’t write and the parts you didn’t mean to include.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Scan before you ship</h2>\n<p>Every package in your image, from Debian’s <code dir=\"ltr\">libssl</code> to npm’s <code dir=\"ltr\">express</code>, has a public history of vulnerabilities (CVEs). A scanner reads the image’s package inventory and matches it against those databases. Docker Scout is built into the Docker CLI:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> scout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> quickview</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api:1.4.0</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> scout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> cves</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --only-severity</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> critical,high</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api:1.4.0</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> scout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> recommendations</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api:1.4.0</span></span></code></pre></div>\n<p><code dir=\"ltr\">quickview</code> gives a one-screen summary: vulnerability counts for your image, for its base image, and for a newer version of that base if one exists. <code dir=\"ltr\">cves</code> lists each finding with the package, the installed version and the version that fixes it. <code dir=\"ltr\">recommendations</code> suggests base image tags with fewer known vulnerabilities. Trivy and Grype are popular open-source scanners that do the same job; pick one and use it everywhere.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Triage in a sensible order</h2>\n<p>A long list of findings is not a to-do list. Work through it like this:</p>\n<ol>\n<li><strong>Base image findings first.</strong> If most findings come from OS packages, update to a freshly patched base or move to a smaller one. The switch to distroless in lesson 2.2 removes most of these in one step.</li>\n<li><strong>Fixable app dependencies next.</strong> If a fixed version exists, bump it in <code dir=\"ltr\">package.json</code>, run <code dir=\"ltr\">npm ci</code>, rebuild.</li>\n<li><strong>Unfixable findings get a decision, not silence.</strong> Write down why the code path isn’t reachable, or what mitigates it, and set a date to look again.</li>\n</ol>\n<p>Then make it a gate so regressions can’t sneak in:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> scout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> cves</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --only-severity</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> critical,high</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --exit-code</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api:1.4.0</span></span></code></pre></div>\n<p>With <code dir=\"ltr\">--exit-code</code>, the command fails when it finds vulnerabilities at those severities, so your CI job fails before the push. While you’re in CI, ask the builder for supply-chain metadata too: <code dir=\"ltr\">docker buildx build --sbom=true --provenance=mode=max …</code> attaches a software bill of materials and build provenance to the image, which makes the next audit a lookup instead of an investigation.</p>\n<aside class=\"callout callout-why\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">Why this matters</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Why scan on a schedule, not only on build</span></p>\n<p>New CVEs are published every day against packages that haven’t changed. An image that scanned clean at release can have a critical finding next month. Rescan what’s deployed, weekly at least, and rebuild on a fresh base when it matters.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">How secrets leak into images</h2>\n<p>Images remember more than you’d think. Three common leaks:</p>\n<ul>\n<li><strong><code dir=\"ltr\">ENV API_KEY=…</code></strong> is stored in the image config and set in every container. <code dir=\"ltr\">docker image inspect</code> shows it.</li>\n<li><strong><code dir=\"ltr\">ARG NPM_TOKEN</code></strong> used in a <code dir=\"ltr\">RUN</code> step is recorded in the image’s build history. <code dir=\"ltr\">docker history --no-trunc</code> can reveal it.</li>\n<li><strong>A file written and later deleted</strong> (<code dir=\"ltr\">COPY .npmrc</code> then <code dir=\"ltr\">RUN rm .npmrc</code>) still exists in the earlier layer. Anyone with the image can extract that layer.</li>\n</ul>\n<p>Docker’s build checks warn about the first two (<code dir=\"ltr\">SecretsUsedInArgOrEnv</code>) when the variable name looks like a secret. Take the warning seriously.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">BuildKit secret mounts</h2>\n<p>The right tool is a <strong>secret mount</strong>: the secret is made available to a single <code dir=\"ltr\">RUN</code> step as a file (or an environment variable) and never written to a layer. notes-api’s build needs a token for a private package, so:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"dockerfile\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">Dockerfile</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> node:24-slim </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> build</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WORKDIR</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /app</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">COPY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> package.json package-lock.json ./</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">RUN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> --mount=type=secret,id=npmrc,target=/root/.npmrc \\</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    --mount=type=cache,target=/root/.npm \\</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    npm ci</span></span></code></pre></div>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> build</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --secret</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> id=npmrc,src=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">$HOME</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/.npmrc</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -t</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-api:1.4.0</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> .</span></span></code></pre></div>\n<p>During that <code dir=\"ltr\">RUN</code>, npm finds its config at <code dir=\"ltr\">/root/.npmrc</code>. Before and after, the file doesn’t exist, and nothing about it is stored in the image or the build cache. Without a <code dir=\"ltr\">target</code>, secrets are mounted at <code dir=\"ltr\">/run/secrets/&lt;id&gt;</code>. If a tool reads a token from an environment variable instead, mount it as one with <code dir=\"ltr\">RUN --mount=type=secret,id=npm_token,env=NPM_TOKEN npm ci</code> and pass <code dir=\"ltr\">--secret id=npm_token,env=NPM_TOKEN</code> on the command line, which reads the value from your shell’s own <code dir=\"ltr\">NPM_TOKEN</code>.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>“It’s only the build stage, it doesn’t ship”</span></p>\n<p>Multi-stage builds do keep the build stage out of the final image. But build stages get pushed too: as cache exports with <code dir=\"ltr\">mode=max</code>, as test images from <code dir=\"ltr\">--target build</code>, as debugging tags. A token in any layer of any stage is a token in your registry. Use secret mounts in every stage.</p>\n</aside>\n<h2 id=\"sec-5\" tabindex=\"-1\">Runtime secrets are not the image’s job</h2>\n<p>The database password notes-api uses in production has no business in the image at all. It arrives when the container starts: from an environment variable in Compose, from a Kubernetes Secret, or from your cloud’s secret manager. You’ll wire up both of the first two in the next sections. The image stays identical across environments; only the injected configuration changes.</p>\n<p>Section 2 is done: your image is lean, unprivileged, traceable and clean. Next you’ll give it a database to talk to, with Docker Compose.</p>\n","toc":[{"id":"sec-1","text":"Scan before you ship","level":2},{"id":"sec-2","text":"Triage in a sensible order","level":2},{"id":"sec-3","text":"How secrets leak into images","level":2},{"id":"sec-4","text":"BuildKit secret mounts","level":2},{"id":"sec-5","text":"Runtime secrets are not the image’s job","level":2}],"takeaways":["<code dir=\"ltr\">docker scout cves</code> lists known CVEs per package; <code dir=\"ltr\">--only-severity critical,high --exit-code</code> turns it into a CI gate.","Fix base-image findings first by updating or switching the base; <code dir=\"ltr\">docker scout recommendations</code> suggests candidates.","Anything passed with <code dir=\"ltr\">ARG</code> or <code dir=\"ltr\">ENV</code>, or written to a file in a <code dir=\"ltr\">RUN</code> step, is recoverable from the image, even if a later step deletes it.","<code dir=\"ltr\">RUN --mount=type=secret</code> exposes a secret to one build step without writing it into any layer.","Runtime secrets such as database passwords are injected when the container starts, never built into the image."],"further":[{"title":"Docker Scout quickstart","url":"https://docs.docker.com/scout/quickstart/"},{"title":"Build secrets","url":"https://docs.docker.com/build/building/secrets/"},{"title":"docker scout cves","url":"https://docs.docker.com/reference/cli/docker/scout/cves/"}],"quiz":[{"q":"<p>A scan of <code dir=\"ltr\">notes-api:1.4.0</code> reports 30 CVEs, 27 of them in Debian packages from the base image. What’s the most effective first step?</p>","options":[{"text":"Suppress the base-image findings, since you didn’t write that code.","why":"You ship that code, so you own its risk. Suppression without analysis hides real exposure."},{"text":"Rebuild on an updated or smaller base image, then rescan.","why":"Correct. Most base findings disappear with a patched base or a slimmer one like distroless. One change fixes dozens of findings."},{"text":"Patch each Debian package with <code dir=\"ltr\">apt-get upgrade</code> in the runtime stage.","why":"It makes builds unpredictable and still leaves you maintaining the OS by hand. Fix the base image itself."},{"text":"Ignore it until a customer asks.","why":"Known, fixable CVEs in shipped images are exactly what attackers scan for."}],"answer":1},{"q":"<p>This build step needs an npm token. What’s wrong with it?</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"dockerfile\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Dockerfile</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ARG</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> NPM_TOKEN</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">RUN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> echo </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"//npm.pkg.github.com/:_authToken=${NPM_TOKEN}\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> > .npmrc \\</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> &#x26;&#x26; npm ci &#x26;&#x26; rm .npmrc</span></span></code></pre></div>\n","options":[{"text":"Nothing; the <code dir=\"ltr\">.npmrc</code> file is deleted in the same step, so the token never persists.","why":"The file is gone, but build arguments are recorded in the image’s build history, so the token is still recoverable."},{"text":"<code dir=\"ltr\">ARG</code> values can’t be used inside <code dir=\"ltr\">RUN</code>.","why":"They can; that’s what makes this pattern tempting."},{"text":"The token must be passed with <code dir=\"ltr\">ENV</code> instead of <code dir=\"ltr\">ARG</code>.","why":"<code dir=\"ltr\">ENV</code> is worse: it’s stored in the image config and set in every running container."},{"text":"The token is recorded with the build arguments and can be read from the image’s history; use a secret mount instead.","why":"Correct. Docker’s own docs warn that <code dir=\"ltr\">ARG</code> and <code dir=\"ltr\">ENV</code> are not for secrets. A secret mount exists only during that one step."}],"answer":3},{"q":"<p>What does this line give the <code dir=\"ltr\">npm ci</code> step? <code dir=\"ltr\">RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci</code></p>","options":[{"text":"A temporary file at <code dir=\"ltr\">/root/.npmrc</code> containing the secret, visible only during this step and absent from the layer.","why":"Correct. The mount appears for the command and disappears afterwards; nothing about its content is stored in the image."},{"text":"An environment variable named <code dir=\"ltr\">npmrc</code> set for every later step.","why":"This form mounts a file at <code dir=\"ltr\">target</code>. Secret mounts never carry over to later steps."},{"text":"A copy of <code dir=\"ltr\">.npmrc</code> baked into the image under <code dir=\"ltr\">/root</code>.","why":"That’s what <code dir=\"ltr\">COPY</code> would do. A secret mount is never written into a layer."},{"text":"Nothing, unless the image also runs as root.","why":"The mount works for whichever user runs the step; the target path here just matches root’s home directory."}],"answer":0},{"q":"<p>Where should notes-api’s production database password come from?</p>","options":[{"text":"A <code dir=\"ltr\">.env</code> file copied into the image during the build.","why":"That bakes the password into every copy of the image, in every registry and cache it reaches."},{"text":"A build argument, so each environment’s image gets its own password.","why":"Build arguments are recorded in image history, and per-environment images break “test what you ship”."},{"text":"Injected at run time, for example from a Kubernetes Secret or Compose secret, never stored in the image.","why":"Correct. The same image runs everywhere; each environment supplies its own credentials when the container starts."},{"text":"Hard-coded in <code dir=\"ltr\">dist/config.js</code> and protected by running as non-root.","why":"A non-root user can still read its own code, and so can anyone who pulls the image."}],"answer":2}],"exercise":{"kind":"spot-bug","language":"dockerfile","code":"# syntax=docker/dockerfile:1\nFROM node:24-slim\nARG NPM_TOKEN\nWORKDIR /app\nCOPY package.json package-lock.json ./\nRUN echo \"//npm.pkg.github.com/:_authToken=${NPM_TOKEN}\" > .npmrc\nRUN npm ci --omit=dev\nRUN rm .npmrc\nCOPY dist ./dist\nUSER node\nCMD [\"node\", \"dist/server.js\"]","bugLines":[3,6],"title":"Where the token leaks","prompt":"<p>This image for an internal tool installs packages from a private registry. The author believes deleting <code dir=\"ltr\">.npmrc</code> on line 8 keeps the token safe. Select the <strong>two</strong> lines that put the token into the image where anyone who pulls it can recover it.</p>\n","hints":["How does the token get into the build, and is that mechanism recorded anywhere?","A file written in one layer still exists in that layer even if a later layer deletes it."],"explanation":"<p>Line 3 passes the token as a build argument, which is recorded in the image’s build history. Line 6 writes it into <code dir=\"ltr\">.npmrc</code> in its own layer; line 8’s <code dir=\"ltr\">rm</code> only hides the file in a later layer, so extracting the line-6 layer reveals it. Use a secret mount, which exists only for the one step that needs it:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"dockerfile\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Dockerfile</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">RUN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> --mount=type=secret,id=npmrc,target=/root/.npmrc \\</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    npm ci --omit=dev</span></span></code></pre></div>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> build</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --secret</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> id=npmrc,src=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">$HOME</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/.npmrc</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -t</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> tool:1.0.0</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> .</span></span></code></pre></div>\n<p>Then rotate the token, because every copy of the old image still contains it.</p>\n","checkLabels":{}},"words":788,"readMinutes":4}