<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alexis M.</title>
    <description>The latest articles on DEV Community by Alexis M. (@alexismtr).</description>
    <link>https://dev.to/alexismtr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1246952%2Fe8c33ef5-5cc8-4863-beee-4a0b0cca203e.png</url>
      <title>DEV Community: Alexis M.</title>
      <link>https://dev.to/alexismtr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexismtr"/>
    <language>en</language>
    <item>
      <title>Docker : Change the build behavior with multi-contexts builds</title>
      <dc:creator>Alexis M.</dc:creator>
      <pubDate>Thu, 16 May 2024 06:17:09 +0000</pubDate>
      <link>https://dev.to/alexismtr/docker-change-the-build-behavior-with-multi-contexts-builds-bob</link>
      <guid>https://dev.to/alexismtr/docker-change-the-build-behavior-with-multi-contexts-builds-bob</guid>
      <description>&lt;h2&gt;
  
  
  Why do I need to change my build behavior ?
&lt;/h2&gt;

&lt;p&gt;In corporate settings, security tools often intercept and encrypt each request with their own certificates, falling under the category of MitM tools. While these tools can be intrusive during local development, there are solutions to address the issues. However, in Docker, the build environment differs from your local machine, rendering these solutions ineffective, leading to build failures. An elegant solution is required to ensure compatibility between these two tools.&lt;/p&gt;

&lt;p&gt;You may have different scenario, but I will use this one to explain how we build our images with this kind of tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do these tools block our Docker builds ?
&lt;/h3&gt;

&lt;p&gt;These tools function as Man-in-the-Middle (MitM) tools. Every request we send is intercepted and encrypted with the tool's certificate. By default, this certificate is not trusted, and it is added to the machine's certificate store during installation.&lt;/p&gt;

&lt;p&gt;Docker is unaware of these tools, and it performs requests as it would in a standard environment when building our image. We must modify the behavior for building within a restrictive environment.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡for NPM, we must define &lt;code&gt;NODE_EXTRA_CA_CERTS&lt;/code&gt; environment variable to point to the tool's certificate&lt;/p&gt;

&lt;p&gt;💡for NuGet, we just need to add the certificate to the &lt;code&gt;/etc/ssl/certs/&lt;/code&gt; folder&lt;/p&gt;

&lt;p&gt;⚠️ all runtimes may have a different way to handle certificate&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For this post, I will use NPM as package manager for simplicity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's wrong ?
&lt;/h2&gt;

&lt;p&gt;I believe a Dockerfile ought to encompass all the essential steps for constructing our application, rather than just copying a pre-built artifact from our machine. Additionally, it should be environment-agnostic, ensuring that the Dockerfile remains consistent whether the build occurs locally or on a CI system.&lt;/p&gt;

&lt;h3&gt;
  
  
  So, how should we address this?
&lt;/h3&gt;

&lt;p&gt;Upon the tool's installation, the IT team offered three potential solutions; regrettably, none met our prerequisite (no changes in the Dockerfile).&lt;/p&gt;

&lt;p&gt;This is our default Dockerfile for the post&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# file: Dockerfile&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm build &lt;span class="c"&gt;# output to /app/dist&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:alpine&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist /usr/share/nginx/html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Solution 1 : One Dockerfile for local build, another one for CI build
&lt;/h4&gt;

&lt;p&gt;I won't waste time detailing why it's ill-advised. Duplicating code with the intention of achieving identical results only leads to self-inflicted issues.&lt;/p&gt;

&lt;h4&gt;
  
  
  Solution 2 : Copy certificates and use ARG/IF to condition the steps
&lt;/h4&gt;

&lt;p&gt;If duplication is not advisable, could we share code ?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# file: Dockerfile&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; BUILD_ENV=remote&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; rootcacert.pem /etc/ssl/certs/securitycert.pem&lt;/span&gt;
&lt;span class="c"&gt;# ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/securitycert.pem&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOC&lt;/span&gt;
if [ "$BUILD_ENV" = "local" ]; then
  &lt;span class="c"&gt;# for NPM, we just need to set an env&lt;/span&gt;
  &lt;span class="c"&gt;# it's here for the purposes of this post, but it belongs above&lt;/span&gt;
  export ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/securitycert.pem
  &lt;span class="c"&gt;# run specifics command here&lt;/span&gt;
  &lt;span class="c"&gt;# update the store certificate or other commands required by your tools&lt;/span&gt;
fi
EOC
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm build &lt;span class="c"&gt;# output to ./dist&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:alpine&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist /usr/share/nginx/html&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Utilizing ARG and bash conditions to determine the necessity of a certificate increases the complexity of our Dockerfile.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Why is this approach problematic?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It complicates the Dockerfile.&lt;/li&gt;
&lt;li&gt;The certificate needs to be included in the git repository.&lt;/li&gt;
&lt;li&gt;Adding the certificate to the &lt;code&gt;.gitignore&lt;/code&gt; file will cause the CI build to fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Locally, we must specify that we want to build on a restricted environment&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; repo/image:1.0.0 &lt;span class="nt"&gt;--build-arg&lt;/span&gt; &lt;span class="nv"&gt;BUILD_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Solution 3 : Use a &lt;code&gt;compose.yaml&lt;/code&gt; definition
&lt;/h4&gt;

&lt;p&gt;The solution should be divided into two distinct parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build using tools that only require the file to be correctly positioned or accompanied by the appropriate environment variables.&lt;/li&gt;
&lt;li&gt;Build using tools that necessitate additional commands for managing certificates.&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;
  
  
  3.1 : build with tools that require a file or an ENV
&lt;/h5&gt;

&lt;p&gt;With this solution, in the given context, there was no need to update our Dockerfile, which is the good news. We only need to add a &lt;code&gt;compose.yaml&lt;/code&gt; file to specify certain configurations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# file: compose.yaml&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.7'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;application&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;repo/image:${TAG}&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NODE_EXTRA_CA_CERTS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/ssl/certs/securitycert.pem&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/path/to/your/certificate:/etc/ssl/certs/securitycert.pem&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  3.2 : build with tools that require specific command
&lt;/h5&gt;

&lt;p&gt;In this case, we need to update our Dockerfile to handle certificate&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# file: Dockerfile&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; BUILD_ENV=remote&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; CERT_PATH=/etc/ssl/certs/securitycert.pem&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="c"&gt;# ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/securitycert.pem&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOC&lt;/span&gt;
if [ "$BUILD_ENV" = "local" ]; then
  &lt;span class="c"&gt;# for NPM, we just need to set an env&lt;/span&gt;
  &lt;span class="c"&gt;# it's here for the purposes of this post, but it belongs above&lt;/span&gt;
  export ENV NODE_EXTRA_CA_CERTS=$CERT_PATH
  &lt;span class="c"&gt;# run specifics command here&lt;/span&gt;
  &lt;span class="c"&gt;# update the store certificate or other commands required by your tools&lt;/span&gt;
fi
EOC
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm build &lt;span class="c"&gt;# output to /app/dist&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:alpine&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist /usr/share/nginx/html&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we need to define a &lt;code&gt;compose.yaml&lt;/code&gt; file to specify some configurations&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# file: compose.yaml&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.7'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;application&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;repo/image:${TAG}&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;BUILD_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
      &lt;span class="na"&gt;CERT_PATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/ssl/certs/securitycertpem&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/path/to/your/certificate:/etc/ssl/certs/securitycert.pem&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In both cases, to build locally, we need to run the build with compose&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.0.0 docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; compose.yaml build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On our CI system, we don't change how we build&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; repo/image:1.0.0 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As mentioned, this solution is suitable for a simple use case; however, for more complex setups, we will need to introduce additional complexity into our Dockerfile.&lt;/p&gt;

&lt;h2&gt;
  
  
  How multi-contexts build can help ?
&lt;/h2&gt;

&lt;p&gt;We have observed that the three (or four) solutions mentioned are not satisfactory. They either add complexity or fail to work in all scenarios.&lt;/p&gt;

&lt;p&gt;If duplication or code sharing don't serve as convincing solutions, perhaps we could isolate the problematic component?&lt;/p&gt;

&lt;p&gt;In object-oriented programming, the strategy pattern enables us to alter our code's behavior without modifying the code itself. Why not apply it here?&lt;/p&gt;

&lt;p&gt;In a Dockerfile, multiple contexts exist. The most common is the default build context (the '.' at the end of the 'docker build' command), but there are others, and we utilize them each time we write or build an image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build-stage&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . ./&lt;/span&gt;
...

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build-stage . ./&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FROM node:20 as build-stage&lt;/code&gt; define a context based on &lt;code&gt;node:20&lt;/code&gt; context and named &lt;code&gt;build-stage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COPY . ./&lt;/code&gt; use the default build context to copy files in the current one&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COPY --from=build-stage . ./&lt;/code&gt; copy file from a context named &lt;code&gt;build-stage&lt;/code&gt; into the current context&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How can it help ?
&lt;/h3&gt;

&lt;p&gt;If &lt;code&gt;node:20&lt;/code&gt; serves as a context, it is possible to update this context at build time using the &lt;a href="https://www.docker.com/blog/dockerfiles-now-support-multiple-build-contexts/?utm_campaign=2022-05-03-brnd-dockerfilemultiplebuildcontexts&amp;amp;utm_medium=social&amp;amp;utm_source=twitter" rel="noopener noreferrer"&gt;multi-contexts build feature&lt;/a&gt; introduced in Docker. If there is a context that contains all the necessary requirements to build our image in a restricted environment, we should be able to replace the &lt;code&gt;node:20&lt;/code&gt; context with it.&lt;/p&gt;

&lt;p&gt;To carry out the build in a restrictive context, we must construct a restricted context. We will create a new Dockerfile where we will specify all the requirements and proceed with the build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# file: Dockerfile.securitycert&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; rootcacert.pem /etc/ssl/certs/securitycert.pem&lt;/span&gt;
&lt;span class="c"&gt;# ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/securitycert.pem&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOC&lt;/span&gt;
&lt;span class="c"&gt;# for NPM, we just need to set an env&lt;/span&gt;
&lt;span class="c"&gt;# it's here for the purposes of this post, but it belongs above&lt;/span&gt;
export ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/securitycert.pem
&lt;span class="c"&gt;# run specifics command here&lt;/span&gt;
&lt;span class="c"&gt;# update the store certificate or other commands required by your tools&lt;/span&gt;
EOC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; node:20-securitycert &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile.securitycert /path/to/your/certificates/folder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we have a restricted context with all requirements built on a new docker image &lt;code&gt;node:20-securitycert&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Without any modifications on our base Dockerfile, we can now build our application container using this image as base.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; repo/image:1.0.0 &lt;span class="nt"&gt;--build-context&lt;/span&gt; node:20&lt;span class="o"&gt;=&lt;/span&gt;docker-image://node:20-securitycert &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker will update the &lt;code&gt;node:20&lt;/code&gt; base we use with our newly created image at build time.&lt;/p&gt;

&lt;p&gt;That sounds good, but it adds an extra step to build the solution, doesn't it?&lt;/p&gt;

&lt;p&gt;Yes, we added an extra command to build an intermediate image, but our base Dockerfile remains unchanged. In our CI system, we continue to run the same command to build our image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; repo/image:1.0.0 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Indeed, the developer experience has been affected. To address this, we could utilize BAKE.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improve developer experience with Bake
&lt;/h3&gt;

&lt;p&gt;Docker buildx &lt;a href="https://docs.docker.com/build/bake/reference/" rel="noopener noreferrer"&gt;bake&lt;/a&gt; offers a novel approach to constructing our images, leveraging parallelization and orchestration of builds. &lt;/p&gt;

&lt;p&gt;It necessitates the addition of a configuration file called &lt;code&gt;docker-bake.hcl&lt;/code&gt; and a modification in our image-building method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file: docker-bake.hcl&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"_BASE_IMAGE"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"node:20"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"TAG"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"latest"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="s2"&gt;"_securitycert"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/path/to/your/certificates/folder"&lt;/span&gt;
  &lt;span class="nx"&gt;dockerfile-inline&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
  FROM ${_BASE_IMAGE}
  COPY rootcacert.pem /etc/ssl/certs/securitycert.pem
  ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/securitycert.pem
  EOF
}

target "default" {
  context = "."
  tags = [
    "repo/image:${TAG}"
  ]
}

target "securitycert" {
  inherits = [ "default" ]
  contexts = {
    "${_BASE_IMAGE}" = "target:_securitycert"
  }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;_BASE_IMAGE&lt;/code&gt; variable is used internally by our targets to share the value &lt;em&gt;(it can be change by the user, but I use &lt;code&gt;_&lt;/code&gt; as a convention for internal usage)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TAG&lt;/code&gt; variable can be change by user to change the tag used at build time&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_securitycert&lt;/code&gt; target define how we build our restricted context image with an inline-docker definition &lt;em&gt;(we also can define it on a separate file)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;default&lt;/code&gt; target define how we build our image on a standard environment &lt;em&gt;(not restricted one)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;securitycert&lt;/code&gt; target defined how we build our image in a restricted environment. It inherits properties from &lt;code&gt;default&lt;/code&gt; target, so every change on the default target is replicated on the securitycert one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the &lt;code&gt;securitycert&lt;/code&gt; target, we replace the context &lt;code&gt;_BASE_IMAGE&lt;/code&gt; with the &lt;code&gt;_securitycert&lt;/code&gt; target. Docker will build the &lt;code&gt;_securitycert&lt;/code&gt; target prior to executing the final build due to its dependency on it.&lt;/p&gt;

&lt;p&gt;At build time, if we do not specify which target to build, bake will use the &lt;code&gt;default&lt;/code&gt; target.&lt;/p&gt;

&lt;p&gt;To build locally, we need to run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.0.0 docker buildx bake securitycert
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On CI, or non-restrictive environment&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.0.0 docker buildx bake

&lt;span class="c"&gt;# you can also run the old one if you want&lt;/span&gt;
&lt;span class="c"&gt;# docker build -t repo/image:1.0.0 .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Docker and Dockerfile are potent tools for building container images with ease, yet they must remain straightforward, even in complex contexts. Adding complexity to Dockerfiles may deter developers from maintaining them, which is understandable.&lt;/p&gt;

&lt;p&gt;Multi-context builds are effective in addressing numerous issues related to build contexts and ought to be utilized more frequently.&lt;/p&gt;

&lt;p&gt;To enhance the developer experience and documentation, 'buildx bake' appears to be a beneficial tool, providing numerous advantages, especially when used in conjunction with multi-context builds.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>showdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Docker : Build complex Solution with multi-contexts and Bake</title>
      <dc:creator>Alexis M.</dc:creator>
      <pubDate>Thu, 16 May 2024 06:14:17 +0000</pubDate>
      <link>https://dev.to/alexismtr/docker-build-complex-solution-with-multi-contexts-and-bake-eni</link>
      <guid>https://dev.to/alexismtr/docker-build-complex-solution-with-multi-contexts-and-bake-eni</guid>
      <description>&lt;p&gt;In the .NET world, Solutions serve as a structural mechanism to organize multiple projects, such as applications and libraries, under a single umbrella. Typically, a Solution includes all the projects that make up a full application, streamlining the process of sharing DLLs. Instead of publishing them to a NuGet feed, they are directly referenced in the .csproj files within the Solution.&lt;/p&gt;

&lt;p&gt;Enabling Docker support for a project prompts VisualStudio to generate a Dockerfile in that project. However, the image must be built from the Solution directory, not the individual project directory. This approach ensures that all necessary code, including that which resides outside the project directory within the Solution, is accessible during the build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; repo/image:1.0.0 &lt;span class="nt"&gt;-f&lt;/span&gt; Project/Dockerfile &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="c"&gt;# or, build from the project folder&lt;/span&gt;
&lt;span class="c"&gt;# docker build -t repo/image:1.0.0 ..&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's fine if you have only 2 projects on the solution (The domain, and the API for example).&lt;/p&gt;

&lt;p&gt;What if you have a big solution and you want to build one project that depends only on one of these projects ?&lt;/p&gt;

&lt;p&gt;Imagine building an IoT application. You need to develop two modules to retrieve data from different data sources, with one module dedicated to each source. These modules will convert the data into a format that the rest of your application can understand and then transmit it to a queue system. On the receiving end of the queue, there are two modules: one for aggregating and storing the data, and another for generating alerts and dispatching them to a notification channel. Additionally, there is a fifth module that exposes the aggregated data through an API.&lt;/p&gt;

&lt;p&gt;For the data ingestion modules, you establish an external library that encompasses the domain of each data source. To facilitate data exchange between your ingestion and processing modules, you create another library that defines the object structures. Lastly, you develop a separate library for your domain, which is utilized by your API.&lt;/p&gt;

&lt;p&gt;We have 5 applications, and 4 libraries&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8to1utarsfqn5qvm2krg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8to1utarsfqn5qvm2krg.png" alt="describe the relations between each projects of the solution" width="734" height="141"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TB
  IngestionA(IngestionA)
  IngestionA.Domain{{IngestionA.Domain}}
  IngestionB(IngestionB)
  IngestionB.Domain{{IngestionB.Domain}}
  Exchange{{Exchange}}
  Data(Data)
  Alerts(Alerts)
  API(API)
  Domain{{Domain}}

  IngestionA --&amp;gt; IngestionA.Domain
  IngestionA --&amp;gt; Exchange
  IngestionB --&amp;gt; IngestionB.Domain
  IngestionB --&amp;gt; Exchange
  Data --&amp;gt; Exchange
  Alerts --&amp;gt; Exchange
  API --&amp;gt; Domain

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's build our 5 applications&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; repo/ingestionA:1.0.0 &lt;span class="nt"&gt;-f&lt;/span&gt; IngestionA/Dockerfile &lt;span class="nb"&gt;.&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; repo/ingestionB:1.0.0 &lt;span class="nt"&gt;-f&lt;/span&gt; IngestionB/Dockerfile &lt;span class="nb"&gt;.&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; repo/data:1.0.0 &lt;span class="nt"&gt;-f&lt;/span&gt; Data/Dockerfile &lt;span class="nb"&gt;.&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; repo/alerts:1.0.0 &lt;span class="nt"&gt;-f&lt;/span&gt; Alerts/Dockerfile &lt;span class="nb"&gt;.&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; repo/api:1.0.0 &lt;span class="nt"&gt;-f&lt;/span&gt; API/Dockerfile &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Docker build context
&lt;/h2&gt;

&lt;p&gt;When executing the &lt;code&gt;docker build&lt;/code&gt; command, Docker uses the specified folder as the build context and transfers it to the Docker daemon. It omits files and folders specified in the &lt;code&gt;.dockerignore&lt;/code&gt; file but includes all other files (the complete codebase).&lt;/p&gt;

&lt;p&gt;For building our API, Docker transfers the entire solution to the build context, as denoted by the &lt;code&gt;.&lt;/code&gt; in our command, despite the fact that we might only require the API and the domain code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✏️ &lt;em&gt;Docker, with BuildKit is smart enough to lazy send files to context with only required files. The purpose of this still apply, but not for context optimizations. It's apply for codebase that is not in the same folder (context) as your default one&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Docker build mutli-contexts
&lt;/h2&gt;

&lt;p&gt;To avoid sending the entire codebase, we can limit the context to only the essential parts of the code. However, it's crucial to define the context for the dependent code. &lt;/p&gt;

&lt;p&gt;This can be achieved with the &lt;a href="https://www.docker.com/blog/dockerfiles-now-support-multiple-build-contexts/?utm_campaign=2022-05-03-brnd-dockerfilemultiplebuildcontexts&amp;amp;utm_medium=social&amp;amp;utm_source=twitter" rel="noopener noreferrer"&gt;multi-contexts&lt;/a&gt; build feature, which necessitates an update to the Dockerfile to accommodate this new approach.&lt;/p&gt;

&lt;p&gt;This is our full Dockerfile base for the IngestionA module&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# file: Dockerfile&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/runtime:8.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; app&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:8.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; BUILD_CONFIGURATION=Release&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; IngestionA.Domain/IngestionA.Domain.csproj ./IngestionA.Domain/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Exchange/Exchange.csproj ./Exchange/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; IngestionA/IngestionA.csproj ./IngestionA/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore ./IngestionA/IngestionA.csproj
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src/IngestionA&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet build ./IngestionA.csproj &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;$BUILD_CONFIGURATION&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /app/build &lt;span class="nt"&gt;--no-restore&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;publish&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; BUILD_CONFIGURATION=Release&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet publish ./IngestionA.csproj &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;$BUILD_CONFIGURATION&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /app/publish /p:UseAppHost&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="nt"&gt;--no-restore&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;final&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 5000&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=publish /app/publish .&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dotnet", "IngestionA.dll"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's focus on the &lt;code&gt;build&lt;/code&gt; stage to use multi-contexts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# file: Dockerfile&lt;/span&gt;
...

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:8.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; BUILD_CONFIGURATION=Release&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=domain IngestionA.Domain.csproj ./IngestionA.Domain/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=exchange Exchange.csproj ./Exchange/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; IngestionA.csproj ./IngestionA/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore ./IngestionA/IngestionA.csproj
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=domain . ./IngestionA.Domain/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=exchange . ./Exchange/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . ./IngestionA/&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src/IngestionA&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet build ./IngestionA.csproj &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;$BUILD_CONFIGURATION&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /app/build &lt;span class="nt"&gt;--no-restore&lt;/span&gt;

...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To build our image, we need to define each context&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# from the IngestionA project folder&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; repo/ingestionA:1.0.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-context&lt;/span&gt; &lt;span class="nv"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;../IngestionA.Domain/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-context&lt;/span&gt; &lt;span class="nv"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;../Exchange/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ Each context must define it's own .dockerignore&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, to build all 5 applications, we must write quite long commands&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; repo/api:1.0.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-context&lt;/span&gt; &lt;span class="nv"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./Domain
  API/
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; repo/ingestionA:1.0.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-context&lt;/span&gt; &lt;span class="nv"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./IngestionA.Domain/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-context&lt;/span&gt; &lt;span class="nv"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./Exchange/ &lt;span class="se"&gt;\&lt;/span&gt;
  IngestionA/
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; repo/ingestionB:1.0.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-context&lt;/span&gt; &lt;span class="nv"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./IngestionB.Domain/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-context&lt;/span&gt; &lt;span class="nv"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./Exchange/ &lt;span class="se"&gt;\&lt;/span&gt;
  IngestionB/
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; repo/data:1.0.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-context&lt;/span&gt; &lt;span class="nv"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./Exchange/ &lt;span class="se"&gt;\&lt;/span&gt;
  Data/
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; repo/alerts:1.0.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-context&lt;/span&gt; &lt;span class="nv"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./Exchange/ &lt;span class="se"&gt;\&lt;/span&gt;
  Alerts/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Improve developer experience with Bake
&lt;/h2&gt;

&lt;p&gt;Docker buildx &lt;a href="https://docs.docker.com/build/bake/reference/" rel="noopener noreferrer"&gt;bake&lt;/a&gt; is a new way to build our images while drawing parallelization and build's orchestration.&lt;/p&gt;

&lt;p&gt;We need to add one configuration file named &lt;code&gt;docker-bake.hcl&lt;/code&gt; and to change the way we build images.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file: docker-bake.hcl&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"TAG"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"latest"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="s2"&gt;"api"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;./&lt;/span&gt;&lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;
  &lt;span class="nx"&gt;contexts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;./&lt;/span&gt;&lt;span class="nx"&gt;Domain&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"repo/api:${TAG}"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="s2"&gt;"ingestionA"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;./&lt;/span&gt;&lt;span class="nx"&gt;IngestionA&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;
  &lt;span class="nx"&gt;contexts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;./&lt;/span&gt;&lt;span class="nx"&gt;IngestionA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Domain&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;
    &lt;span class="nx"&gt;exchange&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;./&lt;/span&gt;&lt;span class="nx"&gt;Exchange&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"repo/ingestionA:${TAG}"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="s2"&gt;"ingestionB"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;./&lt;/span&gt;&lt;span class="nx"&gt;IngestionB&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;
  &lt;span class="nx"&gt;contexts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;./&lt;/span&gt;&lt;span class="nx"&gt;IngestionB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Domain&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;
    &lt;span class="nx"&gt;exchange&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;./&lt;/span&gt;&lt;span class="nx"&gt;Exchange&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"repo/ingestionB:${TAG}"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="s2"&gt;"data"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;./&lt;/span&gt;&lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;
  &lt;span class="nx"&gt;contexts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;exchange&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;./&lt;/span&gt;&lt;span class="nx"&gt;Exchange&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"repo/data:${TAG}"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="s2"&gt;"alerts"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;./&lt;/span&gt;&lt;span class="nx"&gt;Alerts&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;
  &lt;span class="nx"&gt;contexts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;exchange&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;./&lt;/span&gt;&lt;span class="nx"&gt;Exchange&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"repo/alerts:${TAG}"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="s2"&gt;"default"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;targets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"alerts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"ingestionA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"ingestionB"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="s2"&gt;"ingestions"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;targets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"ingestionA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"ingestionB"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="s2"&gt;"data-pipeline"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;targets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"ingestionA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"ingestionB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"alerts"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To build our 5 applications, we have to run a single command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker buildx bake
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, Bake will search for a group &lt;code&gt;default&lt;/code&gt; (or target if group isn't defined). In this case, the group "default" contains all our targets&lt;/p&gt;

&lt;p&gt;If we want to build only the ingestion and processing's modules, we must target &lt;code&gt;data-pipeline&lt;/code&gt; group&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker buildx bake data-pipeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To build only the API, we must call bake with the target &lt;code&gt;api&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker buildx bake api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the images we build will be tagged with &lt;code&gt;latest&lt;/code&gt;, we can change this behavior in different ways.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Override the TAG value using ENV
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.0.0 docker buildx bake api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Override the tag's image using &lt;code&gt;--set&lt;/code&gt; argument
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker buildx bake api &lt;span class="nt"&gt;--set&lt;/span&gt; api.tags&lt;span class="o"&gt;=&lt;/span&gt;api:1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Define a variable for each target and override them in a parameter file
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file: docker-bake.hcl&lt;/span&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"API_TAG"&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"latest"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="s2"&gt;"api"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
 &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;./&lt;/span&gt;&lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt; 
 &lt;span class="nx"&gt;contexts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;./&lt;/span&gt;&lt;span class="nx"&gt;Domain&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; 
   &lt;span class="s2"&gt;"repo/api:${API_TAG}"&lt;/span&gt; 
 &lt;span class="p"&gt;]&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file: bake-parameters.hcl&lt;/span&gt;
&lt;span class="nx"&gt;API_TAG&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker buildx bake api &lt;span class="nt"&gt;-f&lt;/span&gt; docker-bake.hcl &lt;span class="nt"&gt;-f&lt;/span&gt; bake-parameters.hcl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Docker's multi-context builds are particularly beneficial for large solutions with numerous projects, as they help to reduce build context's size. &lt;br&gt;
While they introduce complexity by requiring the definition of each context, this is mitigated when combined with Bake. &lt;/p&gt;

&lt;p&gt;Bake streamlines the building process of the entire solution, allowing for the simultaneous construction of all images with a single command.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>dotnet</category>
      <category>tooling</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
