<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Untitled Publication]]></title><description><![CDATA[I love building great software products that helps people. I have 10+ years of experience in software engineering and 5+ years building on cloud-native platform]]></description><link>https://blog.bijan.cloud</link><generator>RSS for Node</generator><lastBuildDate>Tue, 09 Jun 2026 18:36:28 GMT</lastBuildDate><atom:link href="https://blog.bijan.cloud/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to secure your Docker images with Docker BuildKit and GitHub Actions]]></title><description><![CDATA[The issue with leaking secrets
It has been known for a long time that docker images can be prone to leaking secrets. That means in case an attacker gets access to your Docker images, they can find your secrets by inspecting the docker history to look...]]></description><link>https://blog.bijan.cloud/how-to-secure-your-docker-images-with-docker-buildkit-and-github-actions</link><guid isPermaLink="true">https://blog.bijan.cloud/how-to-secure-your-docker-images-with-docker-buildkit-and-github-actions</guid><category><![CDATA[Docker]]></category><category><![CDATA[docker images]]></category><category><![CDATA[Dockerfile]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[GitHub]]></category><dc:creator><![CDATA[Bijan Chokoufe Nejad]]></dc:creator><pubDate>Wed, 22 Mar 2023 10:09:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1679400379915/d3473b2b-673b-4044-be08-93b85880c14b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-issue-with-leaking-secrets">The issue with leaking secrets</h2>
<p>It has been known <a target="_blank" href="https://www.alexandraulsh.com/2018/06/25/docker-npmrc-security/">for a long time</a> that docker images can be prone to leaking secrets. That means in case an attacker gets access to your Docker images, they can find your secrets by inspecting the <code>docker history</code> to look for build arguments or look through intermediate layers to find any secret files that might be <a target="_blank" href="https://pythonspeed.com/articles/docker-build-secrets/">present in previous layers</a>.</p>
<p>So, if you can't use <code>ENV</code>, <code>ARG</code> or <code>COPY</code>, how do you get the secret into your image to e.g. access a private repository via <code>.npmrc</code>? (I'll be using the example of <a target="_blank" href="https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow">accessing a private NPM repository</a> with an access token <code>NPM_TOKEN</code> which is put in a <code>.npmrc</code> throughout this article but the solution applies to any secret.) The recommended solutions that avoid this problem are <strong>multi-stage builds</strong> and <strong>Docker Buildkit</strong>. The multi-stage build separates the Dockerfile into a builder and a runner stage where the runner stage only gets the minimal data needed to run in production and doesn't inherit any intermediate layers from the build process. You can find an example <a target="_blank" href="https://github.com/alulsh/docker-npmrc-security/blob/main/Dockerfile-secure-multistage">here</a>. While this is a good solution, there are some downsides to it: the <code>Dockerfile</code> is more complex and the build can take a bit longer. Additionally, SonarCloud has <a target="_blank" href="https://rules.sonarsource.com/docker/RSPEC-6472">started to look for the usage</a> of <code>ENV</code> or <code>ARG</code> to handle secrets. It's not smart enough to identify a multi-stage build, so I explored the other option: Docker Buildkit.</p>
<h2 id="heading-using-docker-buildkit-on-desktop">Using Docker Buildkit on Desktop</h2>
<h3 id="heading-the-mount-file-option">The mount file option</h3>
<p>On my machine, it was very easy to set it up as BuildKit is already <a target="_blank" href="https://docs.docker.com/build/buildkit/#getting-started">enabled by default on Docker Desktop</a>. Assuming you have the file with the secret present on the host machine like <code>./.npmrc</code>, you pass it to the <code>docker buildx build</code> command instead of <code>docker build</code> with the <code>--secret</code> option and give it a name via the <code>id</code> notation:</p>
<pre><code class="lang-plaintext">&gt; docker buildx build --secret id=npm,src=./.npmrc .
</code></pre>
<p>Then you can use it inside the <code>Dockerfile</code> with the <code>--mount</code> <a target="_blank" href="https://docs.docker.com/engine/reference/builder/#run---mounttypesecret">option</a> by referring to the <code>id=npm</code> secret:</p>
<pre><code class="lang-plaintext">RUN --mount=type=secret,id=npm,target=./.npmrc \
    npm ci
</code></pre>
<h3 id="heading-using-the-environment-option">Using the environment option</h3>
<p>While it is <a target="_blank" href="https://docs.docker.com/engine/reference/commandline/buildx_build/#secret">documented</a> in the Docker CLI reference documentation, it wasn't immediately obvious to me that you can use the <code>--secret</code> option also for environment variables and not just for files. For this, you can use <code>--secret id=NPM_TOKEN</code> when running <code>docker build</code> with BuildKit, where the <code>id</code> should refer to an actual environment variable that contains a secret. You can then use it with</p>
<pre><code class="lang-plaintext">RUN --mount=type=secret,id=NPM_TOKEN \
    NPM_TOKEN=$(cat /run/secrets/NPM_TOKEN) \
    npm ci
</code></pre>
<p>Note that the <code>--mount</code> command only puts the content of <code>NPM_TOKEN</code> in a file under <code>/run/secrets</code>. To use it as an environment file again, you must load it with <code>cat</code>. This way is a bit cleaner as you don't have to generate a <code>.npmrc</code> with the token inside but instead can keep it clean and checked into git with just</p>
<pre><code class="lang-plaintext">//registry.npmjs.org/:_authToken=${NPM_TOKEN}
</code></pre>
<p>and whatever other settings you want to have in there.</p>
<h2 id="heading-making-it-work-with-github-actions">Making it work with GitHub Actions</h2>
<p>As hinted at before, the setup is only that easy on Docker. Depending on your CI/CD provider, you may have to change the docker settings to enable it. With GitHub Actions (GHA), it's luckily quite easy as there is an <a target="_blank" href="https://github.com/docker/setup-buildx-action#usage">action from Docker</a>:</p>
<pre><code class="lang-yaml">      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">Docker</span> <span class="hljs-string">Buildx</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">docker/setup-buildx-action@v2</span>
</code></pre>
<p>However, when you add that step in front of your <code>docker build</code> and <code>docker push</code> step, you will stumble over the fact that <code>docker push</code> cannot find the docker image you build previously. The reason is that for <code>docker buildx</code> we have to <a target="_blank" href="https://docs.docker.com/engine/reference/commandline/buildx_build/#output">specify the export action for the build result</a> with <code>--output</code>. So our build command becomes</p>
<pre><code class="lang-bash">&gt; docker buildx build \
    --output=<span class="hljs-string">'type=docker'</span> \
    --secret <span class="hljs-string">'id=NPM_TOKEN'</span> \
    -t $(IMAGE):$(TAG) .

&gt; docker push $(IMAGE):$(TAG)
</code></pre>
<p>And behold: We have successfully pushed an image using BuildKits secure mounts with our CI/CD pipeline 🎉.</p>
<h1 id="heading-bonus-docker-buildkit-secret-mount-in-docker-compose">Bonus: Docker BuildKit Secret Mount in Docker Compose</h1>
<p>You might also be using <code>docker-compose</code> to simplify your local developer experience. The approach above is compatible with Docker Compose. Since <a target="_blank" href="https://github.com/docker/compose/pull/9615/files">last summer</a>, it supports files, externally created docker secrets or environment variables but many other resources don't mention the environment variable option yet. First, you need to add a <a target="_blank" href="https://docs.docker.com/compose/compose-file/#secrets-top-level-element">top-level <code>secrets</code> object</a> to your <code>docker-compose.yml</code> containing a key describing the secret you want to use. In our case, we will add the <code>NPM_TOKEN</code> secret, which uses the <code>NPM_TOKEN</code> environment variable. Then, we use it in the description of our service <code>my-service</code> in the <code>build.secrets</code> array (make sure you add the <code>secrets</code> <strong>under</strong> <code>build</code> and not under <code>my-service</code>. The latter will result in a run-time secret that is injected when the container is run but we need a build-time secret that is injected when we build the image). So overall, we have something like this</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.8"</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">my-service:</span>
    <span class="hljs-attr">build:</span>
      <span class="hljs-attr">context:</span> <span class="hljs-string">.</span>
      <span class="hljs-attr">target:</span> <span class="hljs-string">builder</span>
      <span class="hljs-attr">secrets:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">NPM_TOKEN</span>
<span class="hljs-attr">secrets:</span>
  <span class="hljs-attr">NPM_TOKEN:</span>
    <span class="hljs-attr">environment:</span> <span class="hljs-string">NPM_TOKEN</span>
</code></pre>
<p>And there you have it. Now you can benefit from Docker BuildKit in most common scenarios and never worry about leaking your secrets with Docker images again.</p>
]]></content:encoded></item></channel></rss>