<?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: Pradumna Saraf</title>
    <description>The latest articles on DEV Community by Pradumna Saraf (@pradumnasaraf).</description>
    <link>https://dev.to/pradumnasaraf</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%2F682769%2F6d44570c-3d95-4656-ba55-c8a4b0e95f9e.jpg</url>
      <title>DEV Community: Pradumna Saraf</title>
      <link>https://dev.to/pradumnasaraf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pradumnasaraf"/>
    <language>en</language>
    <item>
      <title>Using Profiles with Docker Compose</title>
      <dc:creator>Pradumna Saraf</dc:creator>
      <pubDate>Thu, 29 Jan 2026 04:19:39 +0000</pubDate>
      <link>https://dev.to/pradumnasaraf/using-profiles-with-docker-compose-238l</link>
      <guid>https://dev.to/pradumnasaraf/using-profiles-with-docker-compose-238l</guid>
      <description>&lt;p&gt;Most applications don’t need all Docker Compose services running all the time with the core application, such as development tools, like monitoring and debugging. For example, in a full-stack application, we want the backend, database, and maybe a frontend running by default, and keep monitoring and debugging tools turned off until we need them.  &lt;/p&gt;

&lt;p&gt;Docker Compose profiles make this easy. It saves us from creating multiple compose files with different configurations and managing them. With this approach, we can achievea single source file as a single source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring with Compose profiles
&lt;/h2&gt;

&lt;p&gt;Let's look at an example to understand how we can use profiles with Docker Compose with a real-world example. Let's say we have a full-stack application with a backend, database, and a frontend. We want to run the backend, database, and frontend by default, and keep monitoring and debugging tools turned off until we need them.&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backend&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;node:20-alpine&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run start&lt;/span&gt;

  &lt;span class="na"&gt;frontend&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;node:20-alpine&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run dev&lt;/span&gt;

  &lt;span class="na"&gt;db&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;mysql:8&lt;/span&gt;

  &lt;span class="na"&gt;prometheus&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;prom/prometheus&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;monitoring&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;grafana&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;grafana/grafana&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;monitoring&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;phpmyadmin&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;phpmyadmin&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;debug&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this setup, the backend, frontend, and database form the core of the application and are started by default because we didn't assign any profiles to them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prometheus&lt;/strong&gt; and &lt;strong&gt;Grafana&lt;/strong&gt; are grouped under the &lt;code&gt;monitoring&lt;/code&gt; profile, as we only need them if we want to look at the metrics or performance. About &lt;strong&gt;phpMyAdmin&lt;/strong&gt;, it is grouped under the &lt;code&gt;debug&lt;/code&gt; profile, and it’s only necessary when we need to debug database issues.&lt;/p&gt;

&lt;p&gt;So, by default, only the core services start, and the monitoring and debugging tools are turned off.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;When we need monitoring and debugging, we can start the services by using the &lt;code&gt;--profile&lt;/code&gt; flag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; monitoring up &lt;span class="c"&gt;# to start monitoring tools&lt;/span&gt;
docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; debug up &lt;span class="c"&gt;# to start debugging tools&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also combine the profiles to start multiple services at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; monitoring &lt;span class="nt"&gt;--profile&lt;/span&gt; debug up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it. This approach keeps your Compose file clean and avoids running unnecessary services.&lt;/p&gt;

&lt;p&gt;As always, I'm glad you made it to the end. Thank you for your support and reading. I regularly share tips on &lt;a href="https://x.com/pradumna_saraf" rel="noopener noreferrer"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt; (It will always be Twitter ;)). You can connect with me there.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>containers</category>
      <category>devops</category>
      <category>development</category>
    </item>
    <item>
      <title>Improving Container Security with Docker Hardened Images</title>
      <dc:creator>Pradumna Saraf</dc:creator>
      <pubDate>Mon, 22 Dec 2025 05:30:21 +0000</pubDate>
      <link>https://dev.to/pradumnasaraf/improving-container-security-with-docker-hardened-images-3bia</link>
      <guid>https://dev.to/pradumnasaraf/improving-container-security-with-docker-hardened-images-3bia</guid>
      <description>&lt;p&gt;Container security remains a significant concern. Base images are bloated and contain unnecessary/or too many tools and packages. Due to this, container images like Node, Ubuntu, etc, have a large attack surface in production. &lt;strong&gt;More packages = more CVEs&lt;/strong&gt;, and it’s hard to track which is going inside and which tool is getting hit by vulnerabilities. And we have recently heard a lot of attacks on various companies and tools.&lt;/p&gt;

&lt;p&gt;Yes, security scanners do their job and report these issues, but they don’t reduce the attack surface by themselves. That issue still falls on the image you choose. We need to &lt;strong&gt;STOP the habit of “Scan and fix later”&lt;/strong&gt;, and reduce the risk and make things secure at the image level. To deal with that, Docker made &lt;strong&gt;Docker Harden Image (DHI)&lt;/strong&gt; &lt;strong&gt;FREE for everyone&lt;/strong&gt; (here is a &lt;a href="https://www.docker.com/blog/docker-hardened-images-for-every-developer/" rel="noopener noreferrer"&gt;blog&lt;/a&gt; release for this). Back when it was released, it was under the paywall, and the Docker team thought security should be available to everyone, and everyone should have access to it and make their application secure.&lt;/p&gt;

&lt;p&gt;In this blog, we will look at what DHI is, the problem it solves. Then I will walk you through a demo application to show how to use DHI with your current Dockerfile and workflow. Finally, we will compare in standard image with DHI to get a clear picture of its potential and necessity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Docker Hardened Image (DHI)?
&lt;/h2&gt;

&lt;p&gt;Docker Hardened Image or DHI (we will be calling “DHI” throughout the blog) is a base image which is &lt;strong&gt;Open Source&lt;/strong&gt;, ultra-minimal, with near-zero CVEs, full transparency (SBOMs and Provenance), and built on top of distros like Alpine and Debian with &lt;strong&gt;SLSA Build L3.&lt;/strong&gt; So, using the DHI will reduce the attack surface, making the production secure by default, and Less noise on the scanning side, and fewer things to manage.&lt;/p&gt;

&lt;p&gt;To make the discovery, usability, and transparency simpler. Docker built a dedicated &lt;a href="https://hub.docker.com/hardened-images/catalog" rel="noopener noreferrer"&gt;DHI catalogue&lt;/a&gt;. There are thousands of Hardened images with various versions for the tool or language. You can visit &lt;a href="https://dhi.io" rel="noopener noreferrer"&gt;dhi.io&lt;/a&gt; (yes, they went ahead and got this domain ^^, how cool is that).&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%2F0wmw6oijti187ymnn6zm.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%2F0wmw6oijti187ymnn6zm.png" alt="dhi catalouge" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of my favourites feature in the whole DHI catalogue thing is the “&lt;strong&gt;Tool Included&lt;/strong&gt;” section on the website. In many images, you will find a dedicated column on the right with a list of tools included in that image. This brings a lot of transparency and ease.&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%2Fpw05lk277fflb9shebef.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%2Fpw05lk277fflb9shebef.png" alt="Node dhi catalouge" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are a lot of Hardened images in the market, and calling an image “Hardened” actually does not make it hardened. Here is a really nice comparison of &lt;strong&gt;DHI vs Others&lt;/strong&gt;:&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%2Fqvm9v8gnw01xqmc6ui63.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%2Fqvm9v8gnw01xqmc6ui63.png" alt="dhi comparison" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source: Docker.com&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Docker Hardened Image
&lt;/h2&gt;

&lt;p&gt;Using the DHI is the same as using the Docker official images. There is no change in how we used to specify the base image in the Dockerfile, and the command to build and run images. It’s the same workflow that we use every day. The only thing that has changed is the base image naming, &lt;strong&gt;which now points to a new dhi.io registry&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We will see all in detail in this demo. For that, I have created a simple &lt;a href="https://github.com/Pradumnasaraf/node-dhi-demo" rel="noopener noreferrer"&gt;Node-Express demo&lt;/a&gt; repo. You can clone and keep it handy if you want to follow along and test it out. Once you are done with that, first, we need to sign in to the DHI registry.&lt;/p&gt;

&lt;p&gt;To do that, execute the command below, and you will be prompted to enter your DockerHub username and password. Use your personal access as your password.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker login dhi.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you successfully log in. Then we can pull the &lt;strong&gt;DHI for Node.js&lt;/strong&gt;, as our project is using Node. We will be pulling the Node 22 DHI, and the image for that will be &lt;strong&gt;dhi.io/node:22.&lt;/strong&gt; The &lt;code&gt;22&lt;/code&gt; here refers to the latest version &lt;strong&gt;22.x&lt;/strong&gt; of Node. If you are using a different version of Node, you can check the catalogue &lt;a href="https://hub.docker.com/hardened-images/catalog/dhi/node" rel="noopener noreferrer"&gt;here&lt;/a&gt; and chnage accordingly.&lt;/p&gt;

&lt;p&gt;To pull, we use the same Docker pull command to pull the 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 pull dhi.io/node:22
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let’s modify our Dockerfile to make use of DHI instead of the standard official image. You will find the Dockerfile in the clone report as well.&lt;/p&gt;

&lt;p&gt;We have to make a couple of changes to make the application work with DHI and be secure. The current Dockerfile looks like below, it’s a simple and typical Docker file we used:&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;# Use the official Node.js image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:22&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Copy package files and install dependencies&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 &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--production&lt;/span&gt;

&lt;span class="c"&gt;# Copy application code&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;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT=3000&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["npm", "start"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The modifications are changing the base image in the &lt;strong&gt;FROM&lt;/strong&gt; statement and using &lt;strong&gt;Multi-stage Docker Build&lt;/strong&gt; (For this particular case, not mandatory while using DHI; we will see why we did that).&lt;/p&gt;

&lt;p&gt;After modification, the Dockerfile will have below structure. Let’s understand the modifications in more detail below.&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;# Build stage - use regular node image for building&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:22&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;builder&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 &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--production&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Production stage - use DHI&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; dhi.io/node:22&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Copy node_modules and app files from builder&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/server.js ./&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT=3000&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, coming to why we use &lt;strong&gt;Multi-stage builds&lt;/strong&gt;, yes, using it makes the image minimal and more secure, and that’s the biggest advantage of using that, but in this case, it becomes mandatory because in Node DHI shell is not installed by default, for security, and the &lt;strong&gt;RUN&lt;/strong&gt; will not work when we try to install the npm dependencies.&lt;/p&gt;

&lt;p&gt;Which is why in the Builder stage. We used the &lt;strong&gt;node:22&lt;/strong&gt; image and used &lt;strong&gt;RUN&lt;/strong&gt; to install all the dependencies, and then in the Production stage, in the base image (FROM), we used Node DHI for the 22.x version. Docker made it simple to use the DHI, just by prefixing the base image name with &lt;code&gt;dhi.io/&lt;/code&gt;, and it will use DHI instead of the Docker official image (Make sure you first check the availability).&lt;/p&gt;

&lt;p&gt;Then, finally, we simply copied the artefacts like node_modules, package.json, and server.js from the builder to production and exposed the port and ran the server. The gist is that nothing has changed. Only the naming convention for the base image has changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing and scanning the images
&lt;/h2&gt;

&lt;p&gt;Of course, we can’t end the blog with comparing and getting those numbers. We, developers, love numbers :) So, I built two images. The first image &lt;code&gt;pradumnasaraf/node-without-dhi&lt;/code&gt; is built with the first Dockerfile above, and uses &lt;code&gt;node:22&lt;/code&gt;. And then we built the &lt;code&gt;pradumnasaraf/node-with-dhi&lt;/code&gt; image with the other Dockerfile, which uses the &lt;code&gt;dhi.io/node:22&lt;/code&gt; DHI.&lt;/p&gt;

&lt;p&gt;Then I used &lt;strong&gt;Docker Scout&lt;/strong&gt; and ran &lt;code&gt;docker scout quickview&lt;/code&gt; for both images to check how vulnerable each image is, and the result is expected, but still shocking. In the screenshot below, the number of High and Medium vulnerabilities the first image contains is magnificent. And DHI has just had 8 Low, that’s a huge leap in overall security!&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%2Fgcwetxoi5qgvkqi5sebd.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%2Fgcwetxoi5qgvkqi5sebd.png" alt="terminal screenshot" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note: This is not the end of the security/vulnerability optimisation :) This was just to demo what and how to use DHI. The Dockerfile can be improved further by introducing best practices, such as running containers as a non-root user, tightening permissions, etc.&lt;/p&gt;

&lt;p&gt;That was it. That’s how you use Docker Hardened Images to make your container secure. Remember, most of the container security issues start with the base image, and fixing them later is like never fixing them. Docker is here again, with their Hardened image, saving the developers and keeping the experience and the workflow simpler. Again, thanks, team Docker, for making this available to everyone :).&lt;/p&gt;

&lt;p&gt;As always, I'm glad and super thankful that you made it to the end. Thank you for your support and reading. I regularly share tips on &lt;a href="https://x.com/pradumna_saraf" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;. You can connect with me there.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>security</category>
      <category>containers</category>
    </item>
    <item>
      <title>Running AI Models with Docker Compose</title>
      <dc:creator>Pradumna Saraf</dc:creator>
      <pubDate>Tue, 19 Aug 2025 04:39:59 +0000</pubDate>
      <link>https://dev.to/pradumnasaraf/running-ai-models-with-docker-compose-27ng</link>
      <guid>https://dev.to/pradumnasaraf/running-ai-models-with-docker-compose-27ng</guid>
      <description>&lt;p&gt;Docker Compose has completely changed the game in how we run and connect a multi-service application. Just execute a single line of command, and everything is up and running, and all the services are well interconnected.&lt;/p&gt;

&lt;p&gt;When Docker introduced the &lt;a href="https://docs.docker.com/ai/model-runner/" rel="noopener noreferrer"&gt;Docker Model Runner&lt;/a&gt; (Or DMR, we call it internally in Docker), there was a missing piece (at least for me). To use an AI model with a Compose application, we separately need to run the model with DMR and then connect our Compose application service by passing the config of that running model.&lt;/p&gt;

&lt;p&gt;But Docker knew this, and it sorted it out by &lt;a href="https://docs.docker.com/ai/compose/models-and-compose/" rel="noopener noreferrer"&gt;adding the capability&lt;/a&gt; to describe an AI model in YAML, &lt;code&gt;compose.yml&lt;/code&gt; to run and destroy the AI model on demand. Like we write and do the configuration for &lt;code&gt;services&lt;/code&gt;, &lt;code&gt;networks&lt;/code&gt;, and &lt;code&gt;volumes&lt;/code&gt;. We can do the same for the AI models with &lt;code&gt;models&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisite
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Docker and Docker Compose are installed&lt;/li&gt;
&lt;li&gt;Understanding of AI and LLMs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Let’s get started. To have a better understanding of the concept and working, I have created a GitHub project: &lt;a href="https://github.com/Pradumnasaraf/Saraf-AI" rel="noopener noreferrer"&gt;Pradumnasaraf/Saraf-AI&lt;/a&gt; (Yes, it’s my last name “&lt;strong&gt;Saraf&lt;/strong&gt;” and I added “&lt;strong&gt;AI&lt;/strong&gt;” to it :)). It’s a Next.js chat application that communicates with the Docker AI Model with the help of the OpenAI framework. You can clone it down and keep it ready; we will be referencing that many times.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Compose AI models component
&lt;/h3&gt;

&lt;p&gt;First, let’s have a look at the &lt;code&gt;compose.yml&lt;/code&gt;. Like we are familiar with the &lt;code&gt;services&lt;/code&gt;, &lt;code&gt;volumes&lt;/code&gt;, etc, we have defined &lt;code&gt;models&lt;/code&gt; as the top-level element. This is the new element for defining AI models.&lt;/p&gt;

&lt;p&gt;So what we have done is define a service named &lt;code&gt;saraf-ai&lt;/code&gt; that utilises the model &lt;code&gt;llm&lt;/code&gt;. We have defined &lt;code&gt;models&lt;/code&gt; as an element. And the model definition for &lt;code&gt;llm&lt;/code&gt; that references the &lt;code&gt;ai/smollm2&lt;/code&gt; model image.&lt;/p&gt;

&lt;p&gt;The complete config can be found in &lt;code&gt;compose.yml&lt;/code&gt; in the root of the repo.&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;# compose.yml&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;saraf-ai&lt;/span&gt;&lt;span class="pi"&gt;:&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;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;3000:3000&lt;/span&gt;
    &lt;span class="c1"&gt;# Models to run&lt;/span&gt;
    &lt;span class="na"&gt;models&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;llm&lt;/span&gt;

&lt;span class="na"&gt;models&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Model Name&lt;/span&gt;
  &lt;span class="na"&gt;llm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Model Image&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ai/smollm2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we understand how the config looks, but how can our app connect and communicate with this AI model? How are we setting up environment variables like model name, URL and API key, as we will be using the OpenAI specification?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is where Docker shines!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As we add config to use a model in a service, Docker will auto-generate and inject two environment variables into our service application based on the model name (in our case, &lt;code&gt;llm&lt;/code&gt;). So the two variables will be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;LLM_MODEL&lt;/code&gt;: Contains the model name.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;LLM_URL&lt;/code&gt;: Contains the model endpoint to communicate with.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we can reference these in our application and use them. If that sounds confusing, you can read more about them &lt;a href="https://docs.docker.com/ai/compose/models-and-compose/#short-syntax" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, if we are using multiple AI models and we want to explicitly define how the variable naming should be. For example, we are defining two models below.&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&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;my-app&lt;/span&gt;
    &lt;span class="na"&gt;models&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;llm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;endpoint_var&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AI_MODEL_URL&lt;/span&gt;
        &lt;span class="na"&gt;model_var&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AI_MODEL_NAME&lt;/span&gt;
      &lt;span class="na"&gt;embedding-model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;endpoint_var&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EMBEDDING_URL&lt;/span&gt;
        &lt;span class="na"&gt;model_var&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EMBEDDING_NAME&lt;/span&gt;

&lt;span class="na"&gt;models&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;llm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ai/smollm2&lt;/span&gt;
  &lt;span class="na"&gt;embedding-model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ai/all-minilm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, instead of the default &lt;code&gt;LLM_Model&lt;/code&gt; and &lt;code&gt;LLM_URL&lt;/code&gt;, the application will be injected with &lt;code&gt;AI_MODEL_URL&lt;/code&gt; and &lt;code&gt;AI_MODEL_NAME&lt;/code&gt;. And for &lt;code&gt;embedding-model&lt;/code&gt;, it will inject &lt;code&gt;EMBEDDING_URL&lt;/code&gt; and &lt;code&gt;EMBEDDING_NAME&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, let’s look at our Next.js application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application config
&lt;/h2&gt;

&lt;p&gt;We have created a Next.js application and are using the OpenAI framework (which is standard in the industry) to communicate with the Docker AI model. And it will automatically pick up those environment variables that Docker injected into the application.&lt;/p&gt;

&lt;p&gt;We don’t need &lt;code&gt;apiKey&lt;/code&gt;, as it’s not a cloud LLM and quota kind of thing.&lt;/p&gt;

&lt;p&gt;Below is the complete code. You will also find the complete code in the &lt;code&gt;src/app/api/chat/route.ts&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;OpenAI&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LLM_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;key-not-needed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LLM_MODEL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Validate input&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Message is required and must be a string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Messages must be an array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReadableStream&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;streamError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Streaming error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;streamError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;streamError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/plain; charset=utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Connection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keep-alive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OpenAI API error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unknown error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt; &lt;span class="p"&gt;})?.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to get response from AI&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;errorStatus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&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;h2&gt;
  
  
  Dockerizing the application
&lt;/h2&gt;

&lt;p&gt;Now, let’s Dockerize our application. For that, we will create a &lt;strong&gt;Dockerfile&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You will find the &lt;code&gt;Dockerfile&lt;/code&gt; file in the root of the project.&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;# Build stage&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:24-alpine&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;builder&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Copy package files&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="c"&gt;# Install dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production

&lt;span class="c"&gt;# Copy source code&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Build the application&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# Production stage&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:24-alpine&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;runner&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Create a non-root user&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--gid&lt;/span&gt; 1001 nodejs
&lt;span class="k"&gt;RUN &lt;/span&gt;adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--uid&lt;/span&gt; 1001 nextjs

&lt;span class="c"&gt;# Copy built application from builder stage&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/public ./public&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/.next/standalone ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/.next/static ./.next/static&lt;/span&gt;

&lt;span class="c"&gt;# Set ownership to nextjs user&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; nextjs:nodejs /app

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nextjs&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT 3000&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; HOSTNAME "0.0.0.0"&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have implemented a couple of best practices, such as multi-stage builds and a non-root user, to make the container image smaller, faster, and more secure.&lt;/p&gt;

&lt;p&gt;Once we are done with that, now, let’s run the Compose application by executing &lt;code&gt;docker compose up&lt;/code&gt; command in the terminal. You will see a similar output in the terminal as shown in the screenshot.&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%2F3zwfjs0idls2a5fjiqdy.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%2F3zwfjs0idls2a5fjiqdy.png" alt="code editor screenshot" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we can head over to &lt;code&gt;localhost:3000&lt;/code&gt; in our browser and test out the application. You will have a chat window like ChatGPT, type your prompt and ask questions.&lt;/p&gt;

&lt;p&gt;Here is a short demo:&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%2Fmz2yboi6sh2ks8a3xjvf.gif" 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%2Fmz2yboi6sh2ks8a3xjvf.gif" alt="project demo" width="720" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That was it. That’s how you can run Running AI Models with Docker Compose.&lt;/p&gt;

&lt;p&gt;As always, I'm glad you made it to the end. Thank you for your support and reading. I regularly share tips on &lt;a href="https://x.com/pradumna_saraf" rel="noopener noreferrer"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt; (It will always be Twitter ;)). You can connect with me there.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>ai</category>
      <category>devops</category>
      <category>programming</category>
    </item>
    <item>
      <title>Run MCP Servers In Seconds With Docker</title>
      <dc:creator>Pradumna Saraf</dc:creator>
      <pubDate>Mon, 23 Jun 2025 07:55:10 +0000</pubDate>
      <link>https://dev.to/pradumnasaraf/run-mcp-servers-in-seconds-with-docker-1ik5</link>
      <guid>https://dev.to/pradumnasaraf/run-mcp-servers-in-seconds-with-docker-1ik5</guid>
      <description>&lt;p&gt;Model Context Protocol (MCP) has taken the AI world by storm. It has become the de facto standard for how an AI Agent connect with tools, services, and data. As this is shaping up rapidly, working with different MCP servers, setting them up is still not an easy task, and it requires a learning curve. Docker has a track record of making developers’ lives easier to make, build and ship things faster and again it chimes in to the MCP space, bringing that same clarity, trust, and scalability. That’s exactly what Docker is doing with and introduction of &lt;strong&gt;Docker MCP Catalog&lt;/strong&gt; and &lt;strong&gt;Docker MCP Toolkit&lt;/strong&gt; after the Docker Model Runner (if you haven’t checked it out, here is the &lt;a href="https://docs.docker.com/ai/model-runner/" rel="noopener noreferrer"&gt;link&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In this blog, we will first under what &lt;strong&gt;Docker MCP Catalog&lt;/strong&gt; and &lt;strong&gt;MCP Toolkit&lt;/strong&gt; are. Then we will see step-by-step how we can use Docker MCP Toolkit using Docker Desktop to interact with various tools using MCP Clients offered by Claude, Cursor, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Docker MCP Catalog?
&lt;/h3&gt;

&lt;p&gt;Docker MCP Catalog is a trusted &lt;a href="https://hub.docker.com/catalogs/mcp" rel="noopener noreferrer"&gt;collection of MCP servers&lt;/a&gt;. Currently has verified tools from 100 verified (and the number keeps bumping while writing this) tools publishers like Stripe, Elastic, Grafana, etc. And the tools are it’s just like container images, that means like traditional pull mechanism, we can pull and use it (or use MCP toolkit for UI perks, more on that later) without any hassle to find and configure it manually.&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%2Fcfpmpatmdcikr6oejt9k.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%2Fcfpmpatmdcikr6oejt9k.png" alt="mcp server list website" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Docker MCP Toolkit?
&lt;/h3&gt;

&lt;p&gt;With &lt;a href="https://hub.docker.com/extensions/docker/labs-ai-tools-for-devs" rel="noopener noreferrer"&gt;Docker MCP Toolkit&lt;/a&gt;, with a single click of a button from Docker Desktop, we can spin MCP servers in seconds and connect to our favourite client like Cluade, Cursor, Windsurf, Docker AI Agent, etc. The way it works is that a Gateway MCP Server is created and dynamically exposes enabled tools to compatible clients. This makes it so easy to manage all the tools in one place.&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%2Fi8vewfrqab8j0t5m005o.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%2Fi8vewfrqab8j0t5m005o.png" alt="mcp server list docker desktop" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Docker MCP Toolkit
&lt;/h2&gt;

&lt;p&gt;Let’s now test Docker MCP Toolkit. Make sure you have the latest version of Docker Desktop. My current version is Docker Desktop (Mac) is &lt;strong&gt;4.43.0 (196668)&lt;/strong&gt;. Once you open it, you will see the &lt;strong&gt;MCP Toolkit&lt;/strong&gt; button on the sidebar. Initially, it was shipped as an extension; now it’s baked into the Docker Desktop itself.&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%2F4ddohv54bee4k45s6cs1.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%2F4ddohv54bee4k45s6cs1.png" alt="mcp docker desktop catalog" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let’s install/turn on some MCP servers like &lt;strong&gt;curl&lt;/strong&gt; and &lt;strong&gt;Wikipedia&lt;/strong&gt;. You can search and add it. It’s that simple. It’s really handy to add and remove when needed. No copying and pasting of manual config, and managing them.&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%2Fs5sic85ulh4gbi50resc.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%2Fs5sic85ulh4gbi50resc.png" alt="mcp tookit tools" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let’s connect the Dockerized MCP servers to our MCP clients. I will be using Claude; you can use any according to your preference. We simply need to click on the Connect button, and it will automatically add the Docker configuration to Claude Desktop's MCP server config &lt;code&gt;claude_desktop_config.json&lt;/code&gt; file. The same goes for other MCP Clients.&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%2Fl90egm5fjruxwoqrgbt9.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%2Fl90egm5fjruxwoqrgbt9.png" alt="tool clients" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s open Claude and see it. It will be &lt;strong&gt;Settings &amp;gt; Developer &amp;gt; MCP_DOCKER.&lt;/strong&gt; As you can see, it’s running, which means everything is correctly configured. If we click on the &lt;strong&gt;Edit Config&lt;/strong&gt; button, we can see the config and how it works.&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%2F5it9f7nr2idq76kva6uo.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%2F5it9f7nr2idq76kva6uo.png" alt="claude mcp &amp;lt;br&amp;gt;
![ ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sefrakhw7nzvuybmeb77.png)config" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Config&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"MCP_DOCKER"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"docker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"gateway"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s close the config and open the chat screen on Claude. Now, click on the &lt;strong&gt;Search and Tools&lt;/strong&gt; option to see all the MCP servers, for just, it’s just one, &lt;strong&gt;MCP_DOCKER,&lt;/strong&gt;, having &lt;strong&gt;10&lt;/strong&gt; tools. If you are not seeing it, completely close down Claude and re-open it, and it will start showing up.&lt;/p&gt;

&lt;p&gt;!&lt;br&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%2Fda7t1ir8hz9lb505aazi.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%2Fda7t1ir8hz9lb505aazi.png" alt="cluade desktop" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can click on the arrow next to &lt;strong&gt;10&lt;/strong&gt; to see all the available tools.&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%2F0crwg1rld6dr0xbg4p3l.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%2F0crwg1rld6dr0xbg4p3l.png" alt="tools list" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let’s test it out.&lt;/p&gt;

&lt;p&gt;To test &lt;strong&gt;curl,&lt;/strong&gt; I will ask whether the website is up or not. When you enter the prompt, you might get a pop-up saying “&lt;strong&gt;Claude would like to use an external integration”;&lt;/strong&gt; it is just to determine whether you want to use the MCP tools or not. You can either choose, &lt;strong&gt;always allow&lt;/strong&gt; or &lt;strong&gt;allow once&lt;/strong&gt;, depending on your preference.&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%2F2fp8gdemwhrw04yuqj3b.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%2F2fp8gdemwhrw04yuqj3b.png" alt="curl test" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s now search for some history so that it uses the &lt;strong&gt;Wikipedia&lt;/strong&gt; tool. As you can see, it is called both &lt;code&gt;search_wikipedia&lt;/code&gt; and &lt;code&gt;get_wikipedia&lt;/code&gt; and gives the result.&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%2Fro06l53wvn675tx4b34a.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%2Fro06l53wvn675tx4b34a.png" alt="Wikipedia test" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That was it. That’s how you can use MCP Servers with less hassle and focus more on development and solving problems instead of worrying about managing them.&lt;/p&gt;

&lt;p&gt;As always, I'm glad you made it to the end. Thank you so much for your support. I regularly share tips on &lt;a href="https://x.com/pradumna_saraf" rel="noopener noreferrer"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt; (It will always be Twitter ;)). You can connect with me there.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>docker</category>
      <category>mcp</category>
      <category>llm</category>
    </item>
    <item>
      <title>Monitoring Go Applications Using Prometheus, Grafana, and Docker</title>
      <dc:creator>Pradumna Saraf</dc:creator>
      <pubDate>Mon, 21 Apr 2025 09:28:08 +0000</pubDate>
      <link>https://dev.to/pradumnasaraf/monitoring-go-applications-using-prometheus-grafana-and-docker-33i5</link>
      <guid>https://dev.to/pradumnasaraf/monitoring-go-applications-using-prometheus-grafana-and-docker-33i5</guid>
      <description>&lt;p&gt;Monitoring is important for any application. It helps us ensure that our application is running smoothly and allows us to detect any issues before they become critical. Because in real case scenarios, we are running multiple services, and it's hard to test each service and check if it's working. That is why we set up monitoring to make our lives easier.&lt;/p&gt;

&lt;p&gt;In the blog, we will create a Golang application that will be monitored using Prometheus and Grafana. We will be using the &lt;code&gt;go-prometheus&lt;/code&gt; library to expose metrics from our Golang application. Then will visualise the metrics using Grafana. We will be using Docker and Docker Compose to run our application and the monitoring stack, and connect them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A good understanding of &lt;strong&gt;Golang&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A good understanding of &lt;strong&gt;Docker&lt;/strong&gt; and &lt;strong&gt;Docker Compose&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A good knowledge of &lt;strong&gt;Prometheus&lt;/strong&gt; and &lt;strong&gt;Grafana&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;For better understanding, we will be breaking the blog into multiple sections.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Golang Application
&lt;/h3&gt;

&lt;p&gt;Let's first create a Golang server. Create a new directory and initialise a new Golang project by &lt;code&gt;go mod init &amp;lt;project-name&amp;gt;&lt;/code&gt;. Then create a new file &lt;code&gt;main.go&lt;/code&gt; and add the following code to it:&lt;/p&gt;

&lt;p&gt;We will be breaking down and understanding each part of the code. Giving the complete code before is for better understanding and having clear pictures of how different pieces of the code are connected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"strconv"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/gin-gonic/gin"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/prometheus/client_golang/prometheus"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/prometheus/client_golang/prometheus/promhttp"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Define metrics&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;HttpRequestTotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewCounterVec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CounterOpts&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"api_http_request_total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Help&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Total number of requests processed by the API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;HttpRequestErrorTotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewCounterVec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CounterOpts&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"api_http_request_error_total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Help&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Total number of errors returned by the API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Custom registry (without default Go metrics)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customRegistry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRegistry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;// Register metrics with custom registry&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;customRegistry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustRegister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpRequestTotal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpRequestErrorTotal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Register /metrics before middleware&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/metrics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PrometheusHandler&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RequestMetricsMiddleware&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/health"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Up and running!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/v1/users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Hello from /v1/users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Custom metrics handler with custom registry&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;PrometheusHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;promhttp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customRegistry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;promhttp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerOpts&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Middleware to record incoming requests metrics&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;RequestMetricsMiddleware&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;HttpRequestTotal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithLabelValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Itoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Inc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;HttpRequestErrorTotal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithLabelValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Itoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Inc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&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;Now, you can execute &lt;code&gt;go mod tidy&lt;/code&gt; in the terminal to install all the dependencies we mentioned.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"strconv"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/gin-gonic/gin"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/prometheus/client_golang/prometheus"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/prometheus/client_golang/prometheus/promhttp"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Define metrics&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;HttpRequestTotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewCounterVec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CounterOpts&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"api_http_request_total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Help&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Total number of requests processed by the API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;HttpRequestErrorTotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewCounterVec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CounterOpts&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"api_http_request_error_total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Help&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Total number of errors returned by the API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"status"&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;In the above code, we have imported the required packages. For creating the server, we will use the &lt;strong&gt;gin-gonic&lt;/strong&gt;, and &lt;code&gt;prometheus/client_golang&lt;/code&gt; for exposing the metrics. After that, we have created two variables to define the metrics. The first one is &lt;code&gt;HttpRequestTotal&lt;/code&gt; which will count the total number of requests processed by the API. The second one is &lt;code&gt;HttpRequestErrorTotal&lt;/code&gt; which will count the total number of errors returned by the API. Both of them are of the type &lt;code&gt;CounterVec&lt;/code&gt; , which is a type of metric that counts the number of occurrences of an event. We have also defined two labels for both metrics: &lt;code&gt;path&lt;/code&gt; and &lt;code&gt;status&lt;/code&gt;. The label &lt;code&gt;path&lt;/code&gt; will contain the path of the request, and the &lt;code&gt;status&lt;/code&gt; label will contain the status code of the response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Custom registry (without default Go metrics)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customRegistry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRegistry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;// Register metrics with custom registry&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;customRegistry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustRegister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpRequestTotal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpRequestErrorTotal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Register /metrics before middleware&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/metrics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PrometheusHandler&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RequestMetricsMiddleware&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/health"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Up and running!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/v1/users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Hello from /v1/users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8000"&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;In this section of code, we have created a custom registry to register the metrics with the variable &lt;code&gt;customRegistry&lt;/code&gt;. The reason we are creating a custom registry is to avoid registering the default Golang metrics. The default Golang metrics are registered with the default registry, which is used by the &lt;code&gt;promhttp&lt;/code&gt; handler. By creating a custom registry, we can register our metrics and avoid the default Go metrics.&lt;/p&gt;

&lt;p&gt;We created a new &lt;code&gt;gin&lt;/code&gt; router and registered the &lt;code&gt;/metrics&lt;/code&gt; endpoint before the middleware. The reason we are registering the &lt;code&gt;/metrics&lt;/code&gt; endpoint before the middleware is to ensure that the metrics are collected before the middleware is executed. After that, we have created two endpoints: &lt;code&gt;/health&lt;/code&gt; and &lt;code&gt;/v1/users&lt;/code&gt;. The endpoint &lt;code&gt;/health&lt;/code&gt; will return a JSON response with the message "Up and running!" and the endpoint &lt;code&gt;/v1/users&lt;/code&gt; will return a JSON response with the message "Hello from /v1/users". Finally, we have started the server on port &lt;strong&gt;8000&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Custom metrics handler with custom registry&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;PrometheusHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;promhttp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customRegistry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;promhttp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerOpts&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Middleware to record incoming requests metrics&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;RequestMetricsMiddleware&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;HttpRequestTotal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithLabelValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Itoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Inc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;HttpRequestErrorTotal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithLabelValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Itoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Inc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&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;Lastly, we have created a custom metrics handler with the custom registry. The &lt;code&gt;PrometheusHandler&lt;/code&gt; function returns a &lt;code&gt;gin.HandlerFunc&lt;/code&gt; value that is used to serve the metrics. The &lt;code&gt;RequestMetricsMiddleware&lt;/code&gt; function is a middleware that records the incoming requests’ metrics. It gets the path of the request and the status code of the response and increments the corresponding metric.&lt;/p&gt;

&lt;p&gt;The function &lt;code&gt;c.Next()&lt;/code&gt; is used to call the next middleware in the chain. After that, we get the status code of the response and check if it is less than &lt;strong&gt;400&lt;/strong&gt;. If it is, we increment the &lt;code&gt;HttpRequestTotal&lt;/code&gt; metric. If it is greater than or equal to &lt;strong&gt;400&lt;/strong&gt;, we increment the &lt;code&gt;HttpRequestErrorTotal&lt;/code&gt; metric. The &lt;code&gt;WithLabelValues&lt;/code&gt; function is used to set the label values for the metric. The &lt;code&gt;Inc()&lt;/code&gt; function is used to increment the metric by &lt;strong&gt;1&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Run the Application
&lt;/h4&gt;

&lt;p&gt;Now we have created the application. Let's run the application to check if it's registered the metrics correctly. Make sure you are in the root directory of the project and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will start the server on port &lt;strong&gt;8000&lt;/strong&gt;. You can check if the server is running by opening your browser and going to &lt;code&gt;http://localhost:8000/health&lt;/code&gt;. You should see a JSON response with the message "&lt;strong&gt;Up and running!&lt;/strong&gt;". If you can see the message, then the server is running fine. You can also check the &lt;code&gt;/v1/users&lt;/code&gt; endpoint by going to &lt;code&gt;http://localhost:8000/v1/users&lt;/code&gt;. You should see a JSON response with the message "&lt;strong&gt;Hello from /v1/users&lt;/strong&gt;".&lt;/p&gt;

&lt;p&gt;Now, let's check if the metrics are registered correctly. You can do that by going to &lt;code&gt;http://localhost:8000/metrics&lt;/code&gt;. You will see similar output like this:&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;# HELP api_http_request_error_total Total number of errors returned by the API&lt;/span&gt;
&lt;span class="c"&gt;# TYPE api_http_request_error_total counter&lt;/span&gt;
api_http_request_error_total&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;,status&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"404"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 1
api_http_request_error_total&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"//v1/users"&lt;/span&gt;,status&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"404"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 1
api_http_request_error_total&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/favicon.ico"&lt;/span&gt;,status&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"404"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 1
&lt;span class="c"&gt;# HELP api_http_request_total Total number of requests processed by the API&lt;/span&gt;
&lt;span class="c"&gt;# TYPE api_http_request_total counter&lt;/span&gt;
api_http_request_total&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/health"&lt;/span&gt;,status&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"200"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 2
api_http_request_total&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/v1/users"&lt;/span&gt;,status&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"200"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see the metrics that we have defined in the code. The &lt;code&gt;api_http_request_total&lt;/code&gt; metric will show the total number of requests processed by the API and the &lt;code&gt;api_http_request_error_total&lt;/code&gt;. The metric will show the total number of errors returned by the API. You can also see the labels for both metrics: &lt;code&gt;path&lt;/code&gt; and &lt;code&gt;status&lt;/code&gt;. The label &lt;code&gt;path&lt;/code&gt; will contain the path of the request and the label &lt;code&gt;status&lt;/code&gt; will contain the status code of the response.&lt;/p&gt;

&lt;p&gt;This validates that our application is working fine and the metrics are registered correctly. Now we will be creating a Dockerfile to run the application in a Docker container. Later we will also be using Docker Compose to run the application and the monitoring stack together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dockerize the Application
&lt;/h3&gt;

&lt;p&gt;In the root directory of the project, create a new file called &lt;code&gt;Dockerfile&lt;/code&gt;, and add the following code to it:&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;golang:1.24-alpine&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;builder&lt;/span&gt;
&lt;span class="c"&gt;# Set environment variables&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; CGO_ENABLED=0 \&lt;/span&gt;
    GOOS=linux \
    GOARCH=amd64
&lt;span class="c"&gt;# Set working directory inside the container&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /build&lt;/span&gt;
&lt;span class="c"&gt;# Copy go.mod and go.sum files for dependency installation&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.mod go.sum ./&lt;/span&gt;
&lt;span class="c"&gt;# Download dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download
&lt;span class="c"&gt;# Copy the entire application source&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="c"&gt;# Build the Go binary&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go build &lt;span class="nt"&gt;-o&lt;/span&gt; /app .
&lt;span class="c"&gt;# Final lightweight stage&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;alpine:3.17&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="c"&gt;# Copy the compiled binary from the builder stage&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app /bin/app&lt;/span&gt;
&lt;span class="c"&gt;# Expose the application's port&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;
&lt;span class="c"&gt;# Run the application&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["bin/app"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Understanding the Dockerfile
&lt;/h4&gt;

&lt;p&gt;Let's understand the Dockerfile. We will be using a multi-stage build to create a lightweight and secure Docker image. The multi-stage build allows us to separate the build environment from the runtime environment, which results in a smaller final image size. This is especially useful for Go applications, as we can build a static binary and then copy it to a minimal base image.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build stage&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&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;golang:1.24-alpine&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;builder&lt;/span&gt;
&lt;span class="c"&gt;# Set environment variables&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; CGO_ENABLED=0 \&lt;/span&gt;
    GOOS=linux \
    GOARCH=amd64
&lt;span class="c"&gt;# Set working directory inside the container&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /build&lt;/span&gt;
&lt;span class="c"&gt;# Copy go.mod and go.sum files for dependency installation&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.mod go.sum ./&lt;/span&gt;
&lt;span class="c"&gt;# Download dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download
&lt;span class="c"&gt;# Copy the entire application source&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="c"&gt;# Build the Go binary&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go build &lt;span class="nt"&gt;-o&lt;/span&gt; /app .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This stage uses the official Golang Alpine image as the base and sets the necessary environment variables. It also sets the working directory inside the container, copies the &lt;code&gt;go.mod&lt;/code&gt; and &lt;code&gt;go.sum&lt;/code&gt; files for dependency installation, downloads the dependencies, copies the entire application source, and builds the Go binary.&lt;/p&gt;

&lt;p&gt;We use the &lt;code&gt;golang:1.24-alpine&lt;/code&gt; image as the base image for the build stage. The &lt;code&gt;CGO_ENABLED=0&lt;/code&gt; environment variable disables CGO, which is useful for building static binaries. We also set the &lt;code&gt;GOOS&lt;/code&gt; and &lt;code&gt;GOARCH&lt;/code&gt; environment variables to &lt;code&gt;linux&lt;/code&gt; and &lt;code&gt;amd64&lt;/code&gt;, respectively, to build the binary for the Linux platform.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Final stage&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Final lightweight stage&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;alpine:3.17&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="c"&gt;# Copy the compiled binary from the builder stage&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app /bin/app&lt;/span&gt;
&lt;span class="c"&gt;# Expose the application's port&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;
&lt;span class="c"&gt;# Run the application&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["bin/app"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This stage uses the official Alpine image as the base and copies the compiled binary from the build stage. It also exposes the application's port and runs the application.&lt;/p&gt;

&lt;p&gt;We use the &lt;code&gt;alpine:3.17&lt;/code&gt; image as the base image for the final stage. We copy the compiled binary from the build stage to the final image. We expose the application's port using the &lt;code&gt;EXPOSE&lt;/code&gt; instruction and run the application using the &lt;code&gt;CMD&lt;/code&gt; instruction.&lt;/p&gt;

&lt;p&gt;Apart from the multi-stage build, the Dockerfile also follows best practices such as using the official images, setting the working directory, and copying only the necessary files to the final image. We can further optimise the Dockerfile by other best practices.&lt;/p&gt;

&lt;h4&gt;
  
  
  Build the Docker Image
&lt;/h4&gt;

&lt;p&gt;Let's build and run the Docker image. In the root directory of the project, run the following 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 build &lt;span class="nt"&gt;-t&lt;/span&gt; go-prom-monitor &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that the image is built, we can run the Docker container. Run the following command to run the Docker container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8000:8000 &lt;span class="nt"&gt;--name&lt;/span&gt; go-prom-monitor go-prom-monitor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, like we did before, you can check if the server is running by opening your browser and going to &lt;code&gt;/health&lt;/code&gt; and &lt;code&gt;/v1/users&lt;/code&gt;. You should see the same JSON response as before. You can also check the &lt;code&gt;/metrics&lt;/code&gt; endpoint by going to &lt;code&gt;http://localhost:8000/metrics&lt;/code&gt;. You should see the same metrics as before.&lt;/p&gt;

&lt;p&gt;If you can see the same metrics, then our application inside the Docker container is running as expected. And we are good to go with the next step. Now we will be creating a Docker Compose file to run the application and the monitoring stack together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting the Application with Prometheus and Grafana
&lt;/h3&gt;

&lt;p&gt;Before jumping into the Docker Compose file, why even we are bothering to use Docker Compose? We can run Prometheus and Grafana separately and connect them to the application. But it's all manual, and there can be chances of errors. So, using Docker Compose, we can convert all the services into a single command and obtain more Infrastructure as code. This will help us in the future to scale the application and add more services to it.&lt;/p&gt;

&lt;p&gt;Let's get into it.&lt;/p&gt;

&lt;p&gt;In the root directory of the project, create a new file called &lt;code&gt;compose.yml&lt;/code&gt; (Yes, the new conversion is &lt;strong&gt;compose.yml&lt;/strong&gt;. You are welcome.) and add the following code to it:&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go-api&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;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go-api:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8000:8000&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;go-network&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8080/health"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;develop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
          &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rebuild&lt;/span&gt;

  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prometheus&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;prom/prometheus:v2.55.0&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;./Docker/prometheus.yml:/etc/prometheus/prometheus.yml&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;9090:9090&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;go-network&lt;/span&gt;

  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana&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;grafana/grafana:11.3.0&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;./Docker/grafana.yml:/etc/grafana/provisioning/datasources/datasource.yaml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;grafana-data:/var/lib/grafana&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;3000:3000&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;go-network&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GF_SECURITY_ADMIN_USER=admin&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GF_SECURITY_ADMIN_PASSWORD=password&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;grafana-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;go-network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, create a new directory &lt;code&gt;Docker&lt;/code&gt; in the root directory of the project. Inside the &lt;code&gt;Docker&lt;/code&gt; directory, create two new files called &lt;code&gt;prometheus.yml&lt;/code&gt; and &lt;code&gt;grafana.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Add the following code to the &lt;code&gt;prometheus.yml&lt;/code&gt; file:&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="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;scrape_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
    &lt;span class="na"&gt;evaluation_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp&lt;/span&gt;
        &lt;span class="s"&gt;static_configs&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api:8000"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And add the following code to the &lt;code&gt;grafana.yml&lt;/code&gt; file:&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="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;datasources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prometheus (Main)&lt;/span&gt;
    &lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prometheus&lt;/span&gt;
    &lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://prometheus:9090&lt;/span&gt;
    &lt;span class="s"&gt;isDefault&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will understand why we have created the &lt;code&gt;Docker&lt;/code&gt; directory and the &lt;code&gt;prometheus.yml&lt;/code&gt; and &lt;code&gt;grafana.yml&lt;/code&gt; files in the next section. For clarity, the directory structure of the project should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;├── Docker
│   ├── grafana.yml
│   └── prometheus.yml
├── Dockerfile
├── compose.yml
├── go.mod
├── go.sum
└── main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Understanding the Docker Compose File
&lt;/h4&gt;

&lt;p&gt;The Docker Compose file consists of three services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Golang application service&lt;/strong&gt;: This service builds the Golang application using the Dockerfile and runs it in a container. It exposes the application's port &lt;code&gt;8000&lt;/code&gt; and connects to the &lt;code&gt;go-network&lt;/code&gt; network. It also defines a health check to monitor the application's health. We have also used &lt;code&gt;healthcheck&lt;/code&gt; to monitor the health of the application. The health check runs every 30 seconds and retries 5 times if the health check fails. The health check uses the &lt;code&gt;curl&lt;/code&gt; command to check the &lt;code&gt;/health&lt;/code&gt; endpoint of the application. Apart from the health check, we have also added a &lt;code&gt;develop&lt;/code&gt; section to watch the changes in the application's source code and rebuild the application using the Docker Compose Watch feature.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prometheus service&lt;/strong&gt;: This service runs the Prometheus server in a container. It uses the official Prometheus image &lt;code&gt;prom/prometheus:v2.55.0&lt;/code&gt;. It exposes the Prometheus server on a port &lt;code&gt;9090&lt;/code&gt; and connects to the &lt;code&gt;go-network&lt;/code&gt; network. We have also mounted the &lt;code&gt;prometheus.yml&lt;/code&gt; file from the &lt;code&gt;Docker&lt;/code&gt; directory that is present in the root directory of our project. The &lt;code&gt;prometheus.yml&lt;/code&gt; file contains the Prometheus configuration to scrape the metrics from the Golang application. This is how we connect the Prometheus server to the Golang application.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scrape_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
  &lt;span class="na"&gt;evaluation_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;

&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api:8000"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;prometheus.yml&lt;/code&gt; file, we have defined a job &lt;code&gt;myapp&lt;/code&gt; to scrape the metrics from the Golang application. The &lt;code&gt;targets&lt;/code&gt; field specifies the target to scrape the metrics from. In this case, the target is the Golang application running on port &lt;code&gt;8000&lt;/code&gt;. The &lt;code&gt;api&lt;/code&gt; is the service name of the Golang application in the Docker Compose file. The Prometheus server will scrape the metrics from the Golang application every 10 seconds.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grafana service&lt;/strong&gt;: This service runs the Grafana server in a container. It uses the official Grafana image &lt;code&gt;grafana/grafana:11.3.0&lt;/code&gt;. It exposes the Grafana server on a port &lt;code&gt;3000&lt;/code&gt; and connects to the &lt;code&gt;go-network&lt;/code&gt; network. We have also mounted the &lt;code&gt;grafana.yml&lt;/code&gt; file from the &lt;code&gt;Docker&lt;/code&gt; directory that is present in the root directory of your project. The &lt;code&gt;grafana.yml&lt;/code&gt; file contains the Grafana configuration to add the Prometheus data source. This is how we connect the Grafana server to the Prometheus server. In the environment variables, we have set the Grafana admin user and password, which will be used to log in to the Grafana dashboard.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;datasources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prometheus (Main)&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prometheus&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://prometheus:9090&lt;/span&gt;
  &lt;span class="na"&gt;isDefault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;grafana.yml&lt;/code&gt; file, we have defined a Prometheus data source named &lt;code&gt;Prometheus (Main)&lt;/code&gt;. The &lt;code&gt;type&lt;/code&gt; field specifies the type of the data source, which is &lt;code&gt;prometheus&lt;/code&gt;. The &lt;code&gt;url&lt;/code&gt; field specifies the URL of the Prometheus server to fetch the metrics from. In this case, the URL is &lt;code&gt;http://prometheus:9090&lt;/code&gt;. &lt;code&gt;prometheus&lt;/code&gt; is the service name of the Prometheus server in the Docker Compose file. The &lt;code&gt;isDefault&lt;/code&gt; field specifies whether the data source is the default data source in Grafana.&lt;/p&gt;

&lt;p&gt;Apart from the services, the Docker Compose file also defines a volume &lt;code&gt;grafana-data&lt;/code&gt; to persist the Grafana data and a network &lt;code&gt;go-network&lt;/code&gt; to connect the services. We have created a custom network &lt;code&gt;go-network&lt;/code&gt; to connect the services. The &lt;code&gt;driver: bridge&lt;/code&gt; field specifies the network driver to use for the network.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the services with Docker Compose
&lt;/h3&gt;

&lt;p&gt;Now that we have created the Docker Compose file, we can run the services using Docker Compose. In the root directory of the project, run the following 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 compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will see a similar output in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; ✔ Network go-prometheus-monitoring_go-network  Created                                                           0.0s 
 ✔ Container grafana                            Created                                                           0.3s 
 ✔ Container go-api                             Created                                                           0.2s 
 ✔ Container prometheus                         Created                                                           0.3s 
Attaching to go-api, grafana, prometheus
go-api      | &lt;span class="o"&gt;[&lt;/span&gt;GIN-debug] &lt;span class="o"&gt;[&lt;/span&gt;WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
go-api      | 
go-api      | &lt;span class="o"&gt;[&lt;/span&gt;GIN-debug] &lt;span class="o"&gt;[&lt;/span&gt;WARNING] Running &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"debug"&lt;/span&gt; mode. Switch to &lt;span class="s2"&gt;"release"&lt;/span&gt; mode &lt;span class="k"&gt;in &lt;/span&gt;production.
go-api      |  - using &lt;span class="nb"&gt;env&lt;/span&gt;:     &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GIN_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;release
go-api      |  - using code:    gin.SetMode&lt;span class="o"&gt;(&lt;/span&gt;gin.ReleaseMode&lt;span class="o"&gt;)&lt;/span&gt;
go-api      | 
go-api      | &lt;span class="o"&gt;[&lt;/span&gt;GIN-debug] GET    /metrics                  &lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; main.PrometheusHandler.func1 &lt;span class="o"&gt;(&lt;/span&gt;3 handlers&lt;span class="o"&gt;)&lt;/span&gt;
go-api      | &lt;span class="o"&gt;[&lt;/span&gt;GIN-debug] GET    /health                   &lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; main.main.func1 &lt;span class="o"&gt;(&lt;/span&gt;4 handlers&lt;span class="o"&gt;)&lt;/span&gt;
go-api      | &lt;span class="o"&gt;[&lt;/span&gt;GIN-debug] GET    /v1/users                 &lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; main.main.func2 &lt;span class="o"&gt;(&lt;/span&gt;4 handlers&lt;span class="o"&gt;)&lt;/span&gt;
go-api      | &lt;span class="o"&gt;[&lt;/span&gt;GIN-debug] &lt;span class="o"&gt;[&lt;/span&gt;WARNING] You trusted all proxies, this is NOT safe. We recommend you to &lt;span class="nb"&gt;set &lt;/span&gt;a value.
go-api      | Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies &lt;span class="k"&gt;for &lt;/span&gt;details.
go-api      | &lt;span class="o"&gt;[&lt;/span&gt;GIN-debug] Listening and serving HTTP on :8000
prometheus  | &lt;span class="nv"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2025-03-15T05:57:06.676Z &lt;span class="nb"&gt;caller&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main.go:627 &lt;span class="nv"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"No time or size retention was set so using the default time retention"&lt;/span&gt; &lt;span class="nv"&gt;duration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;15d
prometheus  | &lt;span class="nv"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2025-03-15T05:57:06.678Z &lt;span class="nb"&gt;caller&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main.go:671 &lt;span class="nv"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Starting Prometheus Server"&lt;/span&gt; &lt;span class="nv"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;server &lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"(version=2.55.0, branch=HEAD, revision=91d80252c3e528728b0f88d254dd720f6be07cb8)"&lt;/span&gt;
grafana     | &lt;span class="nv"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;settings &lt;span class="nv"&gt;t&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2025-03-15T05:57:06.865335506Z &lt;span class="nv"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Config overridden from command line"&lt;/span&gt; &lt;span class="nv"&gt;arg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"default.log.mode=console"&lt;/span&gt;
grafana     | &lt;span class="nv"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;settings &lt;span class="nv"&gt;t&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2025-03-15T05:57:06.865337131Z &lt;span class="nv"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Config overridden from Environment variable"&lt;/span&gt; &lt;span class="nv"&gt;var&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"GF_PATHS_DATA=/var/lib/grafana"&lt;/span&gt;
grafana     | &lt;span class="nv"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ngalert.state.manager &lt;span class="nv"&gt;t&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2025-03-15T05:57:07.088956839Z &lt;span class="nv"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"State
.
.
grafana     | logger=plugin.angulardetectorsprovider.dynamic t=2025-03-15T05:57:07.530317298Z level=info msg="&lt;/span&gt;Patterns update finished&lt;span class="s2"&gt;" duration=440.489125ms
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The services will start running, and we can access the Golang application at &lt;code&gt;http://localhost:8000&lt;/code&gt;, Prometheus at &lt;code&gt;http://localhost:9090/health&lt;/code&gt;, and Grafana at &lt;code&gt;http://localhost:3000&lt;/code&gt;. We should see the three services running: &lt;code&gt;go-api&lt;/code&gt;, &lt;code&gt;prometheus&lt;/code&gt;, and &lt;code&gt;grafana&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can also check the services logs using the &lt;code&gt;docker compose logs&lt;/code&gt; command. This will show us the logs of all the services running in the Docker Compose file. We can also check the logs of a specific service by using the &lt;code&gt;docker compose logs &amp;lt;service-name&amp;gt;&lt;/code&gt; command. For example, to check the logs of the Golang application, we can run the following 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 compose logs api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was it for running the services using Docker Compose. Next, we will be looking at how we can develop the application using Docker Compose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Developing the Application using Docker Compose
&lt;/h3&gt;

&lt;p&gt;Now, if we make any changes to our Golang application locally, it needs to reflect in the container, right? To do that, one approach is to use the &lt;code&gt;--build&lt;/code&gt; flag in Docker Compose after making changes in the code. This will rebuild all the services that have the &lt;code&gt;build&lt;/code&gt; instruction in the &lt;code&gt;compose.yml&lt;/code&gt; file, in our case, the &lt;code&gt;api&lt;/code&gt; service (Golang application).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;docker compose up --build
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this is not the best approach. This is not efficient. Every time we make a change in the code, we need to rebuild manually. This is not a good flow for development.&lt;/p&gt;

&lt;p&gt;The better approach is to use Docker Compose Watch. Docker, almost a year back, added a new feature called Docker Compose Watch. This feature allows watching the changes in the application's source code and rebuilding/restarting the application using Docker Compose. More like a hot reload feature. And if you look closely, we have added a &lt;code&gt;develop&lt;/code&gt; section in the Docker Compose file.&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go-api&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;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go-api:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8000:8000&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;go-network&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8080/health"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;develop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# This is the develop section&lt;/span&gt;
      &lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
          &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rebuild&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if we modify our &lt;code&gt;main.go&lt;/code&gt; or any other file in the project, the &lt;code&gt;api&lt;/code&gt; service will be rebuilt automatically. We will see the following output in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Rebuilding service&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"api"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; after changes were detected...
&lt;span class="o"&gt;[&lt;/span&gt;+] Building 8.1s &lt;span class="o"&gt;(&lt;/span&gt;15/15&lt;span class="o"&gt;)&lt;/span&gt; FINISHED                                                                                                        docker:desktop-linux
 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;api internal] load build definition from Dockerfile                                                                                                  0.0s
 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; transferring dockerfile: 704B                                                                                                                      0.0s
 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;api internal] load metadata &lt;span class="k"&gt;for &lt;/span&gt;docker.io/library/alpine:3.17                                                                                        1.1s
  &lt;span class="nb"&gt;.&lt;/span&gt;                             
 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; exporting manifest list sha256:89ebc86fd51e27c1da440dc20858ff55fe42211a1930c2d51bbdce09f430c7f1                                                    0.0s
 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; naming to docker.io/library/go-api:latest                                                                                                          0.0s
 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; unpacking to docker.io/library/go-api:latest                                                                                                       0.0s
 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;api] resolving provenance &lt;span class="k"&gt;for &lt;/span&gt;metadata file                                                                                                          0.0s
service&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"api"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; successfully built
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it for the development flow. Next, we will be looking at how to access the Grafana dashboard and visualise the metrics that we are registering in the Golang application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessing the Grafana Dashboard
&lt;/h3&gt;

&lt;p&gt;Now that we have our application running, head over to the Grafana dashboard to visualise the metrics we are registering. Open your browser and navigate to &lt;code&gt;http://localhost:3000&lt;/code&gt;. We will be greeted with the Grafana login page. The login credentials are the ones provided in the Compose file.&lt;/p&gt;

&lt;p&gt;Once we are logged in, we can create a new dashboard. While creating a dashboard, you will notice that the default data source is &lt;code&gt;Prometheus&lt;/code&gt;. This is because we have already configured the data source in the &lt;code&gt;grafana.yml&lt;/code&gt; file.&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%2Frvi3vntfnp1ki1ls9vwy.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%2Frvi3vntfnp1ki1ls9vwy.png" alt="Grafana Dashboard" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can use different panels to visualise the metrics. This guide doesn't go into details of Grafana. We can refer to the &lt;a href="https://grafana.com/docs/grafana/latest/" rel="noopener noreferrer"&gt;Grafana documentation&lt;/a&gt; for more information. There is a Bar Gauge panel to visualise the total number of requests from different endpoints. We used the &lt;code&gt;api_http_request_total&lt;/code&gt; and &lt;code&gt;api_http_request_error_total&lt;/code&gt; metrics to get the data.&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%2F0k6uxwyd4rqq8a235ryk.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%2F0k6uxwyd4rqq8a235ryk.png" alt="Grafana Dashboard Pannels" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We created this panel to visualise the total number of requests from different endpoints to compare the successful and failed requests. For all the good requests, the bar will be green, and for all the failed requests, the bar will be red. Plus, it will also show from which endpoint the request is coming, whether it's a successful request or a failed request. If you want to get the dashboard JSON, you can visit this repo &lt;a href="https://github.com/Pradumnasaraf/Blog-Demo/tree/main/go-prometheus-monitoring" rel="noopener noreferrer"&gt;here&lt;/a&gt;. You will also find the complete code for the Golang application, Dockerfile and Docker Compose file we created in this blog.&lt;/p&gt;

&lt;p&gt;That's it! You have successfully created a Golang application that is monitored using Prometheus and Grafana. You have also learned how to Dockerize the application and run it using Docker Compose. You can now use this setup to monitor your Golang applications in production.&lt;/p&gt;

&lt;p&gt;That’s it about the Blog. As always, I'm glad you made it to the end—thank you so much for your support. I regularly share tips on &lt;a href="https://x.com/pradumna_saraf" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;. You can connect with me there.&lt;/p&gt;

</description>
      <category>go</category>
      <category>docker</category>
      <category>monitoring</category>
      <category>devops</category>
    </item>
    <item>
      <title>Docker Can Run LLMs Locally. Wait, What!?</title>
      <dc:creator>Pradumna Saraf</dc:creator>
      <pubDate>Mon, 07 Apr 2025 05:35:16 +0000</pubDate>
      <link>https://dev.to/pradumnasaraf/docker-can-run-llms-locally-wait-what-35fn</link>
      <guid>https://dev.to/pradumnasaraf/docker-can-run-llms-locally-wait-what-35fn</guid>
      <description>&lt;p&gt;Using Docker to run Large Language Models (LLMs) locally? Yes, you heard that right. Docker is now much more than just running a container image. With &lt;strong&gt;Docker Model Runner&lt;/strong&gt;, you can run and interact with LLMs locally.&lt;/p&gt;

&lt;p&gt;It’s a no-brainer that we’ve seen a huge shift in development towards AI and GenAI. And it’s not easy to develop a GenAI-powered application, considering all the hassle—from cost to setup. As always, Docker steps in and does what it’s known for: making GenAI development easier so developers can build and ship products and projects faster. We can run AI models on our machines natively! Yes, it runs models outside of containers. Right now, Docker Model Runner is in Beta and available for Docker Desktop for Mac with Apple Silicon, requiring Docker Desktop version 4.40 or later.&lt;/p&gt;

&lt;p&gt;In this blog, we will explore the benefits of the Docker Model Runner and how to use it in various forms. Let’s get straight in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Docker Model Runner
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Developer Flow&lt;/strong&gt;: One of the most important aspects as a developer that we don’t like is the context switching and using 100 different tools, and Docker, used by almost every other developer, make things easy and reduces the learning curve.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;GPU Acceleration&lt;/strong&gt;: Docker Desktop runs &lt;strong&gt;llama.cpp&lt;/strong&gt; directly on your host machine. The inference server can access Apple's Metal AP, which allows direct access to the hardware GPU acceleration on Apple Silicon.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OCI Artifcats&lt;/strong&gt;: Store AI models as OCI artifcats instead of storing them as Docker Images. This saves disk space and reduces the extraction of everything. Also, this will improve compatibility and adaptability as it’s an industry-standard format.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Everything Local&lt;/strong&gt;: You don’t need to face the hassle of Cloud LLMs API Key, rate limiting, latency, etc, while binding products locally and paying those expensive bills. Another big aspect is data privacy and security comes on top of it. Models are dynamically loaded into memory by llama.cpp when needed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  In Action
&lt;/h2&gt;

&lt;p&gt;Make sure you have Docker Desktop v4.40 or above installed in your system. Once you have that, make sure you have enabled the &lt;strong&gt;Enable Docker Model Runner&lt;/strong&gt; by going to &lt;strong&gt;settings &amp;gt; Features in development&lt;/strong&gt;. You can also check &lt;strong&gt;Enable host-side TCP support&lt;/strong&gt; to communicate form your localhost (we will see a demo below for that).&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%2Fittnakulmqoscmc01snb.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%2Fittnakulmqoscmc01snb.png" alt="docker desktop" width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you are done. Click on &lt;strong&gt;Apply &amp;amp; restart,&lt;/strong&gt; and we are all set. To test it’s working, open any terminal any type &lt;code&gt;docker model&lt;/code&gt;, you will see the output of all the available commands and this verifies everything is working as expected.&lt;/p&gt;

&lt;p&gt;So, to intrext with the LLMs, we have two methods (as of now, stay tuned) from the CLI or the API (OpenAI-compatible). The CLI is pretty straightforward on the API front. We can interact with API either from inside a running container or from the localhost. Let’s look at these in much more detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  From the CLI
&lt;/h3&gt;

&lt;p&gt;If you have used docker cli (which almost every developer has who ever worked with the container) and used the commands, like &lt;code&gt;docker pull&lt;/code&gt;, &lt;code&gt;docker run&lt;/code&gt;, etc, the docker model uses the same pattern, only there is sub command addition which is the &lt;strong&gt;model&lt;/strong&gt; keyword, so to pull a model we will do &lt;code&gt;docker model pull &amp;lt;model name&amp;gt;&lt;/code&gt; or to run a pulled model &lt;code&gt;docker model run &amp;lt;model name&amp;gt;&lt;/code&gt;. It makes things so much easier because we don’t need to learn whole new wording for a new tool.&lt;/p&gt;

&lt;p&gt;Here are all the commands that are currently supported. Some more are coming soon (some are my favourites, too). Stay tuned!&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%2Fnfvsf0lgz8u6lrr02eqc.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%2Fnfvsf0lgz8u6lrr02eqc.png" alt="terminal screenshot" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, to run a mode, we first need to pull it. So, for example, we will run &lt;code&gt;llama3.2&lt;/code&gt;. You will find all available models on the &lt;a href="https://hub.docker.com/catalogs/gen-ai" rel="noopener noreferrer"&gt;Docker Hub’s GenAI Catalog&lt;/a&gt;. So, open the terminal and run &lt;code&gt;docker model pull ai/llama3.2&lt;/code&gt;. It will take some time to pull it depending on the Model size and your internet bandwidth. Once you pull it, run the &lt;code&gt;docker model run ai/llama3.2&lt;/code&gt;, and it will start an inactive chat like you have a normal chatbot or ChatGPT, and once you are done, you can use &lt;code&gt;/bye&lt;/code&gt; it to exit the interactive chat mode. Here is a screenshot:&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%2F7gsdtex38d8myxkknwsx.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%2F7gsdtex38d8myxkknwsx.png" alt="terminal screenshot" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  From the API (OpenAI)
&lt;/h3&gt;

&lt;p&gt;One of the fantastic things about Model Runner is that it implements OpenAI-compatible endpoints. We can interact with the API in many ways, like inside a running container or from the host machine using TCP or Unix Sockets.&lt;/p&gt;

&lt;p&gt;We will see examples of different ways, but before that, here are the available endpoints. The endpoints will remain the same whether we interact with the API from inside a container or from the host. Only the host will change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# OpenAI endpoints
    GET /engines/llama.cpp/v1/models
    GET /engines/llama.cpp/v1/models/{namespace}/{name}
    POST /engines/llama.cpp/v1/chat/completions
    POST /engines/llama.cpp/v1/completions
    POST /engines/llama.cpp/v1/embeddings
    Note: You can also omit llama.cpp.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;From Inside the Container&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;From inside the container, we will use &lt;a href="http://model-runner.docker.internal/" rel="noopener noreferrer"&gt;&lt;code&gt;http://model-runner.docker.internal&lt;/code&gt;&lt;/a&gt; it as the base URL, and we can hit any endpoint mentioned above. For example, we will hit &lt;code&gt;/engines/llama.cpp/v1/chat/completions&lt;/code&gt; the endpoint to do a chat.&lt;/p&gt;

&lt;p&gt;We will be using the &lt;code&gt;curl&lt;/code&gt;. You can see it uses the same schema structure as OpenAI API. Make sure you have already pulled the model that you are trying to use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    curl http://model-runner.docker.internal/engines/llama.cpp/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
        "model": "ai/llama3.2",
        "messages": [
            {
                "role": "system",
                "content": "You are a helpful assistant."
            },
            {
                "role": "user",
                "content": "Please write 100 words about the docker compose."
            }
        ]
    }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, to test it out, that it works from inside the running container, I am running the &lt;code&gt;jonlabelle/network-tools&lt;/code&gt; image in an interactive mode and then using the above curl command to talk to the API. And it worked.&lt;/p&gt;

&lt;p&gt;As you can see, below is the response I got. The response is in JSON format, including the generated message, token usage, model details, and response timing. Just like the standard.&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%2Fthznzmzfh0mki3dchrar.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%2Fthznzmzfh0mki3dchrar.png" alt="terminal screenshot" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From the Host&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As I mentioned previously, to interact with the A, you must be sure you have enabled the TCP. You can verify it’s working by visiting the &lt;code&gt;localhost:12434&lt;/code&gt;. You will see a message saying &lt;strong&gt;Docker Model Runner. The service is running.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this, we will have &lt;code&gt;http://localhost:12434&lt;/code&gt; as the base URL and the same endpoints will be followed. The same goes for the curl command; we will just replace the base URL, and everything will remain the same.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    curl http://localhost:12434/engines/llama.cpp/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
        "model": "ai/llama3.2",
        "messages": [
            {
                "role": "system",
                "content": "You are a helpful assistant."
            },
            {
                "role": "user",
                "content": "Please write 100 words about the docker compose."
            }
        ]
    }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s try it out by running it in our terminal:&lt;/p&gt;

&lt;p&gt;It will return the same JSON format response as the other one, including the generated message, token usage, model details, and response timing.&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%2Fp6go1vdhupzjbek7xadi.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%2Fp6go1vdhupzjbek7xadi.png" alt="terminal screenshot" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this TCP support, we are not just limited to interacting with the applications that are running inside our container but anywhere.&lt;/p&gt;

&lt;p&gt;That’s it about the Blog. You can learn more about the Docker Model Runner from the official docs &lt;a href="https://docs.docker.com/desktop/features/model-runner/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. And keep an eye out for the Docker announcements; there will be a lot more coming. As always, I'm glad you made it to the end—thank you so much for your support. I regularly share tips on &lt;a href="https://x.com/pradumna_saraf" rel="noopener noreferrer"&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt; (It will always be Twitter ;)). You can connect with me there.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>ai</category>
      <category>llm</category>
      <category>programming</category>
    </item>
    <item>
      <title>Using ARM-based GitHub Actions Runners for Workflows</title>
      <dc:creator>Pradumna Saraf</dc:creator>
      <pubDate>Wed, 29 Jan 2025 04:50:32 +0000</pubDate>
      <link>https://dev.to/pradumnasaraf/using-arm-based-github-actions-runners-for-workflows-l6o</link>
      <guid>https://dev.to/pradumnasaraf/using-arm-based-github-actions-runners-for-workflows-l6o</guid>
      <description>&lt;p&gt;As we have observed a significant transition toward ARM-based CPUs, such as Apple’s M series and Snapdragon's X, it's essential to build, test, and deploy the product and software in a multi-architecture environment to replicate the exact behaviour experienced by an end user.&lt;/p&gt;

&lt;p&gt;GitHub recently announced that Linux ARM-based (arm64) GitHub Actions are now available as hosted runners for free in public repositories. You can read the official announcement &lt;a href="https://github.blog/changelog/2025-01-16-linux-arm64-hosted-runners-now-available-for-free-in-public-repositories-public-preview" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Previously, developers had to rely on virtualization for Actions runs, which was cumbersome. To use it, we have to set the value for &lt;code&gt;runs-on:&lt;/code&gt; as &lt;code&gt;ubuntu-24.04-arm&lt;/code&gt; or &lt;code&gt;ubuntu-22.04-arm&lt;/code&gt; based on which version of Ubuntu we are going to use based on our needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's see in Action
&lt;/h3&gt;

&lt;p&gt;We will create a basic workflow to print "Hello World". First, create a GitHub repo and make sure you are at the root.  Then create a dir name &lt;code&gt;.github&lt;/code&gt; inside that, create a dir called &lt;code&gt;workflows&lt;/code&gt;, and inside that create a YAML file with any name, we will name it &lt;code&gt;hello.yaml&lt;/code&gt;. The complete file path will look like this &lt;code&gt;.github/workflows/hello.yaml&lt;/code&gt;. Now paste the below configuration.&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="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Hello World&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hello&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-24.04-arm&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Print Hello World&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;echo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Hello&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;World"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's commit the changes and head to the Actions tab to check the progress.&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%2Fs43aoujqzo2ae21l85bn.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%2Fs43aoujqzo2ae21l85bn.png" alt="github actions tab" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we can see, our action ran successfully without any issues. For more real-world workflows we can switch the runner label and it will use arm base runners.&lt;/p&gt;

&lt;p&gt;On a personal note, I find ARM-based runners much faster, complementing their nature. It may vary depending on the task and the computation power it needs. That's come to the end of this blog. As usual, glad you made it to the end—thank you so much for your support. I regularly share tips on &lt;a href="https://x.com/pradumna_saraf" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;. You can connect with me there.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>github</category>
      <category>cicd</category>
      <category>devops</category>
    </item>
    <item>
      <title>Publishing Multi-Arch Docker images to GHCR using Buildx and GitHub Actions</title>
      <dc:creator>Pradumna Saraf</dc:creator>
      <pubDate>Fri, 20 Dec 2024 07:00:51 +0000</pubDate>
      <link>https://dev.to/pradumnasaraf/publishing-multi-arch-docker-images-to-ghcr-using-buildx-and-github-actions-2k7j</link>
      <guid>https://dev.to/pradumnasaraf/publishing-multi-arch-docker-images-to-ghcr-using-buildx-and-github-actions-2k7j</guid>
      <description>&lt;p&gt;The industry has seen a huge shift in machines towards using ARM base CPUs like Apple Silicon to Snapdragon X from X86, and it's become essential to build images that support multiple architectures and run containers that are compatible and aligned with that architecture without facing any bottlenecks.&lt;/p&gt;

&lt;p&gt;Using Docker Buildx, we can very easily build multi-platform container images. All builds executed via &lt;code&gt;buildx&lt;/code&gt; run with the Moby Buildkit builder engine. You can read more in detail &lt;a href="https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the blog, we will learn how to automate the process of building a Multi-Arch image and pushing it to GitHub Container Registry (GHCR) using a GitHub workflow/Actions when there is a change in the repo. Also, I recently published a similar blog for publishing the image to DockerHub. You can read it here:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/pradumnasaraf" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F682769%2F6d44570c-3d95-4656-ba55-c8a4b0e95f9e.jpg" alt="pradumnasaraf"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/pradumnasaraf/docker-buildx-gha-dockerhub-3h2c" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Publishing Multi-Arch Docker image to DockerHub using Buildx and GitHub Actions&lt;/h2&gt;
      &lt;h3&gt;Pradumna Saraf ・ Oct 23 '24&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#docker&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#devops&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#development&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#github&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Prerequisite
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A good understanding of Docker&lt;/li&gt;
&lt;li&gt;A decent understanding of GitHub Actions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Getting started
&lt;/h3&gt;

&lt;p&gt;Before starting, I assume you have already Dockerized your project, created a Dockerfile, and pushed that to GitHub. In case, you haven't done one yet and still want to try the process out, you can create a GitHub repo with a minimal &lt;code&gt;Dockerfile&lt;/code&gt; in the root that prints "Hello World" by running an echo command using Alpine as the base image. Dockerfile syntax for it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;FROM alpine:3.20
CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"echo"&lt;/span&gt;, &lt;span class="s2"&gt;"Hello World!"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once done we are all set to write a workflow. Make sure you are on the root of the project, create a dir name &lt;code&gt;.github&lt;/code&gt; inside that create a dir called &lt;code&gt;workflows&lt;/code&gt; and inside that create a YAML file with any name, we will name it &lt;code&gt;ghcr.yaml&lt;/code&gt;. The complete file path will look like this &lt;code&gt;.github/workflows/ghcr.yaml&lt;/code&gt;. Now paste the below configuration. Don't commit it yet, first, we break down and understand the below configuration.&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="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Push Image to GHCR&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;REGISTRY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io&lt;/span&gt;
  &lt;span class="na"&gt;IMAGE_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.repository }}&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-push-image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
      &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Log in to the Container registry&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.REGISTRY }}&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.actor }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Docker Buildx&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-buildx-action@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and push Docker image&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&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;file&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;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt; 
            &lt;span class="s"&gt;ghcr.io/pradumnasaraf/devops:latest&lt;/span&gt;
          &lt;span class="na"&gt;platforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux/amd64,linux/arm64,linux/arm/v7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this section, we are triggering the workflow when a release is created. We can modify the &lt;code&gt;on:&lt;/code&gt; trigger according to our release flow, like triggering the workflow when a tag is pushed, etc. Then we created some environment variables &lt;code&gt;REGISTRY&lt;/code&gt; and &lt;code&gt;IMAGE_NAME&lt;/code&gt; for reusability in the workflow.&lt;/p&gt;

&lt;p&gt;Then we are using Ubuntu as a runner and checking out the repo code. And giving it content &lt;strong&gt;read&lt;/strong&gt; to read the content from the repo and give the workflow the &lt;strong&gt;write&lt;/strong&gt; permission to publish a package (In GitHub we called it packages). It's a registry for hosting and managing packages, including containers and other dependencies)&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="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;REGISTRY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io&lt;/span&gt;
  &lt;span class="na"&gt;IMAGE_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.repository }}&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-push-image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
      &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this part, we are checking out the repo code and then log into GHCR so that the workflow has the necessary rights and permission to push the image to the Registry. Then we set up the Docker Buildx. Buildx is the real deal that will help us build the Multi-Arch images from the same Dockerfile.&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="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Log in to the Container registry&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.REGISTRY }}&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.actor }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Docker Buildx&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-buildx-action@v3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the final step of the workflow here we are building the image and pushing it to GHCR. We can set a &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;file&lt;/code&gt;, here the Dockerfile is in the root with the name Dockerfile.&lt;/p&gt;

&lt;p&gt;We can set multiple &lt;code&gt;tags&lt;/code&gt; apart from the &lt;code&gt;latest&lt;/code&gt; one, For example, we can automate unique image versioning by pulling the git tag pushed to trigger this workflow. So, if we push a Git tag with &lt;code&gt;1.2.3&lt;/code&gt;, the image would be something like &lt;code&gt;pradumnasaraf/devops:1.2.3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Lastly, we are we are providing for which platforms we need to build it for. We can give the values for &lt;code&gt;platforms&lt;/code&gt; by comma separation. Here we are building for &lt;code&gt;linux/amd64&lt;/code&gt;,&lt;code&gt;linux/arm64&lt;/code&gt; and &lt;code&gt;linux/arm/v7&lt;/code&gt;.&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and push Docker image&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&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;file&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;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt; 
            &lt;span class="s"&gt;ghcr.io/pradumnasaraf/devops:latest&lt;/span&gt;
          &lt;span class="na"&gt;platforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux/amd64,linux/arm64,linux/arm/v7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it, that was all about the explanation workflow. Now commit the changes. Based on the type of trigger you set this workflow will run and push the images to DockerHub.&lt;/p&gt;

&lt;p&gt;I have created a release on my DevOps repo with &lt;code&gt;v2.3.3&lt;/code&gt;. Now, It will push an image with the &lt;code&gt;latest&lt;/code&gt; as well as &lt;code&gt;2.3.3&lt;/code&gt;. It is getting the version number from the &lt;code&gt;package.json&lt;/code&gt; using an action to extract it. You can do this kind of workaround to make it more seamless and powerful.&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%2Flj3fb0ci609cdzcqb9jm.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%2Flj3fb0ci609cdzcqb9jm.png" alt="GitHub Actions bar" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, go back to your repo and under the &lt;strong&gt;Packages&lt;/strong&gt; section you will see your package (image) got published with the name you provided, with a little container icon.&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%2Fjlf9flln06wfqku5l7us.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%2Fjlf9flln06wfqku5l7us.png" alt="GitHub repo" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you not now seeing the &lt;strong&gt;Packages&lt;/strong&gt; section, turn it on from the &lt;strong&gt;About&lt;/strong&gt; setting of the repo. And if it's turned on, the workflow runs successfully and you get a message &lt;strong&gt;No packages published&lt;/strong&gt;, head over to your GitHub profile page, click on the &lt;strong&gt;Packages&lt;/strong&gt; tab then click on the package name. It will ask you to link with a repo and link it with the repo you use to create the workflow. Sometimes due to mismatching of the repo and image name the package doesn't show automatically on the repo.&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%2F61kldg14955u7nsdejxg.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%2F61kldg14955u7nsdejxg.png" alt="Pradumna's GitHub Packages section" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you get the package linked to your repo, click on the package name, now you see will the image with all the architecture we provided.&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%2Fgay74rsz2f07u2t11qns.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%2Fgay74rsz2f07u2t11qns.png" alt="Pradumna's DevOps repo image" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now the great part is that if someone pulls an image, for eg &lt;code&gt;docker pull ghcr.io/pradumnasaraf/devops:2.3.3&lt;/code&gt; docker will pull the image for that architecture only we don't need to explicitly mention.&lt;/p&gt;

&lt;p&gt;That's come to the end of this blog. As usual, glad you made it to the end—thank you so much for your support. I regularly share Docker tips on &lt;a href="https://x.com/pradumna_saraf" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;. You can connect with me there.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>githubactions</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Rate Limiting a Golang API using Redis</title>
      <dc:creator>Pradumna Saraf</dc:creator>
      <pubDate>Tue, 12 Nov 2024 07:31:02 +0000</pubDate>
      <link>https://dev.to/pradumnasaraf/rate-limiting-a-golang-api-using-redis-ogi</link>
      <guid>https://dev.to/pradumnasaraf/rate-limiting-a-golang-api-using-redis-ogi</guid>
      <description>&lt;p&gt;To put &lt;strong&gt;Rate Limiting&lt;/strong&gt; in simpler words, it is a technique in which we limit the number of requests a user or client can make to an API within a given time frame. You might have encountered in the past getting a "rate limit exceeded" message when you tried to access a weather or a joke API. The are a lot of arguments around why to rate limit an API, but some important ones are to make fair use of it, make it secure, safeguard resources from overload, etc.&lt;/p&gt;

&lt;p&gt;In this blog, we will create an HTTP server with Golang using the Gin framework apply a rate limit functionality to an endpoint using Redis and store the total count of the requests made by an IP  made to the server in a timeframe. And if it exceeds the limit, we set, we will give an error message.&lt;/p&gt;

&lt;p&gt;In case you have no idea what Gin and Redis are. &lt;a href="https://github.com/gin-gonic/gin" rel="noopener noreferrer"&gt;Gin&lt;/a&gt; is a web framework written in Golang. It helps to create a simple and fast server without writing a lot of code. &lt;a href="https://github.com/redis/redis" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; it's an in-memory and key-value data store that can be used as a database or for caching capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisite
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Familiarity with Golang, Gin and Redis&lt;/li&gt;
&lt;li&gt;A Redis instance (We can use Docker or a remote machine)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;p&gt;To Initialize the project run &lt;code&gt;go mod init &amp;lt;github path&amp;gt;&lt;/code&gt; for eg, &lt;code&gt;go mod init github.com/Pradumnasaraf/go-redis&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then let's create a simple HTTP server with Gin Framework then we apply the logic for rate limiting it. You can copy the code below. It's very basic. The server will reply with a message when we hit the &lt;code&gt;/message&lt;/code&gt; endpoint. &lt;/p&gt;

&lt;p&gt;After you copy the below code, run &lt;code&gt;go mod tidy&lt;/code&gt; to automatically install the packages we have imported.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/gin-gonic/gin"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"You can make more requests"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8081"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;//listen and serve on localhost:8081&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can run the server by executing &lt;code&gt;go run main.go&lt;/code&gt; in the terminal and see this message in the terminal.&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%2Fvv244p4x4j3iafcu689d.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%2Fvv244p4x4j3iafcu689d.png" alt="VS code screenshot" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To test it, we can go to &lt;code&gt;localhost:8081/message&lt;/code&gt; we will see this message in the browser.&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%2F80xtx0vtzmjg0wmphc35.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%2F80xtx0vtzmjg0wmphc35.png" alt="browser screenshot" width="800" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now our server is running, let's set up a rate limit functionality for the &lt;code&gt;/message&lt;/code&gt; route. We will use the &lt;code&gt;go-redis/redis_rate&lt;/code&gt; package. Thanks to the creator of this package, we don't need to write the logic for handling and checking the limit from scratch. It will do all the heavy lifting for us.&lt;/p&gt;

&lt;p&gt;Below is the complete code after implementing the rate-limiting functionality. We will understand each bit of it. Just gave the complete code early to avoid any confusion and to understand how different pieces work together. &lt;/p&gt;

&lt;p&gt;Once you copy the code run &lt;code&gt;go mod tidy&lt;/code&gt; to install all the imported packages. Let's now jump and understand the code (Below the code snippet).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"errors"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/gin-gonic/gin"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/go-redis/redis_rate/v10"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/redis/go-redis/v9"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;rdb&lt;/span&gt;     &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;
    &lt;span class="n"&gt;limiter&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;redis_rate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Limiter&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;initRedis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rdb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Addr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"localhost:6379"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;limiter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis_rate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rdb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Initialize Redis client and rate limiter once&lt;/span&gt;
    &lt;span class="n"&gt;initRedis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;rdb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rateLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientIP&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusTooManyRequests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"you have hit the limit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"You can make more requests"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8081"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;rateLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientIP&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;limiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clientIP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redis_rate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PerMinute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Remaining&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Rate limit exceeded"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's first directly jump to the &lt;code&gt;initRedis()&lt;/code&gt; function. This will create an instance of a Redis client and rate limiter once when the application starts. This way, we don't need to create a new instance every time. We created global variables, &lt;code&gt;rdb&lt;/code&gt; to store the redis instance and &lt;code&gt;limiter&lt;/code&gt; to store the limter instance.&lt;/p&gt;

&lt;p&gt;Now let's understand the &lt;code&gt;rateLimiter()&lt;/code&gt; function. This function asks for an argument that is the request's IP address, which we can obtain via &lt;code&gt;c.ClientIP()&lt;/code&gt; in the &lt;code&gt;main&lt;/code&gt; function. And we return an error if the limit is hit otherwise keep it &lt;code&gt;nil&lt;/code&gt;. Most of the code is boilerplate we took from the official GitHub &lt;a href="https://github.com/go-redis/redis_rate" rel="noopener noreferrer"&gt;repo&lt;/a&gt;. The key functionality to look closer into here is the &lt;code&gt;limiter. Allow()&lt;/code&gt; function. &lt;code&gt;Addr:&lt;/code&gt; takes the URL path value for the Redis instance. I am using Docker to run it locally. You can use anything, make sure you replace the URL accordingly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;limiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clientIP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redis_rate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PerMinute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It takes three arguments, the first is &lt;code&gt;ctx&lt;/code&gt;, the second one is Key, Key (key for a value) for the Redis Database, and the third one is the the limit. So, the function stores the &lt;strong&gt;clientIP&lt;/strong&gt; address as a key and the default limit as the value and reduces it when a request is made. The reason for this structure is that the Redis database needs unique identification and a unique key for storing key-value pairs kind of data, and every IP address is unique in its way, this is why we are using IP addresses instead of usernames, etc. The 3rd argument &lt;code&gt;redis_rate.PerMinute(10)&lt;/code&gt; can be modified as per our need, we can set limit &lt;strong&gt;PerSecond&lt;/strong&gt;, &lt;strong&gt;PerHour&lt;/strong&gt;, etc, and set the value inside parentheses for how many requests can be made per minute/second/hour. In our case, it's &lt;strong&gt;10 per minute&lt;/strong&gt;. Yes, it's that simple to set.&lt;/p&gt;

&lt;p&gt;At last, we are checking if there is a remaining quota of not by &lt;code&gt;res.Remaining&lt;/code&gt;. If it's zero we will return an error with the message otherwise we'll return nil. For eg, you can also do &lt;code&gt;res.Limit.Rate&lt;/code&gt; to check the limit rate, etc. You can play around and dig deeper into that. One thing to note here is, that this is just an example of how to bring these two pieces together, as we have a single route we are not using any middleware, what if when we have 10s or 100s of routes?&lt;/p&gt;

&lt;p&gt;Now coming the &lt;code&gt;main()&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Initialize Redis client and rate limiter once&lt;/span&gt;
    &lt;span class="n"&gt;initRedis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;rdb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rateLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientIP&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusTooManyRequests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"you have hit the limit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"You can make more requests"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8081"&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;Everything is almost the same in the &lt;code&gt;main()&lt;/code&gt; function. We called the &lt;code&gt;initRedis()&lt;/code&gt; function to initialize the Redis client and rate limiter and then close the redis client using &lt;code&gt;defer&lt;/code&gt; once the application exits. In the &lt;code&gt;/message&lt;/code&gt; route, every time the route gets hit, we call the &lt;code&gt;rateLimit()&lt;/code&gt; function and pass it a &lt;strong&gt;ClientIP&lt;/strong&gt; address and store the return value (error) value in the &lt;code&gt;err&lt;/code&gt; variable. If there is an error we will return a &lt;strong&gt;429&lt;/strong&gt;, that is, &lt;code&gt;http.StatusTooManyRequests&lt;/code&gt;, and a message &lt;code&gt;"message": "You have hit the limit"&lt;/code&gt;. If the person has a remaining limit and the &lt;code&gt;rateLimit()&lt;/code&gt; returns no error it will work normally, as it did earlier and serve the request.&lt;/p&gt;

&lt;p&gt;That was all the explanation. Let's now test the working. Re-run the server by executing the same command. For the 1st time, we will see the same message we got earlier. Now refresh your browser 10 times (As we set a limit of 10 per minute), and you will see the error message in the browser.&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%2Fdnbdliigxtb7v63jvb8y.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%2Fdnbdliigxtb7v63jvb8y.png" alt="browser screenshot" width="800" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can also verify this by seeing the logs in the terminal. Gin offers great logging out of the box. After a minute it will restore our limit quota.&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%2Fs6xphoy1vszg6bnqc1fe.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%2Fs6xphoy1vszg6bnqc1fe.png" alt="terminal logs" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's come to the end of this blog, I hope you enjoy reading as much as I enjoy writing. I am glad you made it to the end—thank you so much for your support. I also talk regularly about Golang and other stuff like Open Source and Docker on &lt;a href="https://x.com/pradumna_saraf" rel="noopener noreferrer"&gt;X (Twitter)&lt;/a&gt;. You can connect me over there.&lt;/p&gt;

</description>
      <category>go</category>
      <category>redis</category>
      <category>api</category>
      <category>docker</category>
    </item>
    <item>
      <title>My Hacktobefest 2024 Experience</title>
      <dc:creator>Pradumna Saraf</dc:creator>
      <pubDate>Fri, 01 Nov 2024 07:53:27 +0000</pubDate>
      <link>https://dev.to/pradumnasaraf/my-hacktobefest-2024-experience-385f</link>
      <guid>https://dev.to/pradumnasaraf/my-hacktobefest-2024-experience-385f</guid>
      <description>&lt;p&gt;&lt;strong&gt;Hacktoberfest&lt;/strong&gt; has a special place in my tech journey because my tech and open source journey started during Hacktoberfest 2021 (it's been three years in tech and 4th year participating in Hacktoberfest, WOW!). This year, I was late to the party and registered for Hacktoberfest on 18 October, as I was stuck with some life problems.&lt;/p&gt;

&lt;p&gt;So, like every other year, I participated as a Maintainer and a Contributor. To make this article more fun, I will share my combined experience for both and break down this article into two sections &lt;strong&gt;As a Contributor&lt;/strong&gt; and &lt;strong&gt;As a Maintainer&lt;/strong&gt; (Sorry, Dev team, I jammed in both things in one).&lt;/p&gt;

&lt;h3&gt;
  
  
  As a Contributor
&lt;/h3&gt;

&lt;p&gt;Like last year I was looking for less popular projects that aren't popular, offering no swag or anything in return but need help, real help. I was so specific about the area of contribution because I wanted to upskill and test my knowledge in those things. I think this is what I like about open source most, there are so many pieces tied together to form a project, and we can choose which one we want to pick based on our interests. I tweeted the same as well.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1847299159914942946-428" src="https://platform.twitter.com/embed/Tweet.html?id=1847299159914942946"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1847299159914942946-428');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1847299159914942946&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;So, I found a couple of repos by looking on Twitter and Hacktoberfest Discord that fit what I was looking for and helped them with the GitHub Actions and CI stuff, which improved their workflow for better maintaining the quality of code. Below is a snapshot of Pull Resuests I made last week.&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%2F5ld20t0wr750inv3b62l.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%2F5ld20t0wr750inv3b62l.png" alt="merged pr section screenshot" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The two best takeaways as a contributor I got from this Hacktoberfest are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;There are a lot of projects that need help but don't have that much money, popularity and stars that can help them come under the limelight of contributors and people. We as contributors should try to find them and help them with the knowledge we have. There are many solopreneurs out there in the open source community, working hard to make an impact, and needed support.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can have a monumental impact on small and less known projects compared to big and utmost starts. There is nothing wrong with contributing to big projects, but with time, they become mature and the sheer volume of the contributors makes it difficult to cope. And, if you are new, you can have a tough time making your Pull Request work. A balance of both I think is great. I personally feel you learn a lot more in new and evolving projects as they tend to iterate and open to new ideas. So, they welcome contributions. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  As a Maintainer
&lt;/h3&gt;

&lt;p&gt;This year, I was unable to make my repo "ready" for Hacktoberfest, but I got some PRs on the non-technical front on the DevOps repo. In case you didn't know about this &lt;a href="https://github.com/Pradumnasaraf/DevOps" rel="noopener noreferrer"&gt;DevOps&lt;/a&gt; repo, it is one of the most popular open source repos in the world to learn DevOps with a whopping &lt;strong&gt;2.8k+ GitHub stars&lt;/strong&gt;.&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%2Fdzocpnp2od9yy8tww7ek.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%2Fdzocpnp2od9yy8tww7ek.png" alt="merge PR section" width="800" height="167"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a maintainer this year, my takeaway was that even though we think the project is "complete" and "picture perfect" there is so much room to improve, and make it better when you learn from contributors' knowledge, value and the perspective they bring to the table.&lt;/p&gt;

&lt;p&gt;2024 was fun. Looking forward to contributing even more next year. Happy Open Source!&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hacktoberfest</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Publishing Multi-Arch Docker image to DockerHub using Buildx and GitHub Actions</title>
      <dc:creator>Pradumna Saraf</dc:creator>
      <pubDate>Wed, 23 Oct 2024 04:36:15 +0000</pubDate>
      <link>https://dev.to/pradumnasaraf/docker-buildx-gha-dockerhub-3h2c</link>
      <guid>https://dev.to/pradumnasaraf/docker-buildx-gha-dockerhub-3h2c</guid>
      <description>&lt;p&gt;As you know, we have seen a huge shift in machines towards using ARM base CPUs like &lt;strong&gt;Apple Silicon&lt;/strong&gt; to &lt;strong&gt;Snapdragon X&lt;/strong&gt; from &lt;strong&gt;X86&lt;/strong&gt;, and it's become essential to build Docker Images that support multiple architectures and run containers that are compatible and aligned with that architecture without facing any bottlenecks.&lt;/p&gt;

&lt;p&gt;Using Docker &lt;strong&gt;Buildx&lt;/strong&gt; we can very easily build multi-platform images. All builds executed via &lt;code&gt;buildx&lt;/code&gt; run with the Moby Buildkit builder engine. You can read more in detail &lt;a href="https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the blog, we will leverage the functionality of Buildx to build a Multi-Arch Image, pushing it to DockerHub by automating all the tasks using a GitHub workflow/Actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisite
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A DockerHub account&lt;/li&gt;
&lt;li&gt;A good understanding of Docker&lt;/li&gt;
&lt;li&gt;A decent understanding of GitHub Actions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Getting started
&lt;/h3&gt;

&lt;p&gt;Before starting, I assume you have already Dockerized your project, created a Dockerfile, and pushed it to GitHub. In case, you haven't done one yet and still want to try the process out, you can create a GitHub repo with just a minimal &lt;code&gt;Dockerfile&lt;/code&gt; in the root that prints "Hello World" by running an echo command using Alpine as the base image. Dockerfile syntax for it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;FROM alpine:3.20
CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"echo"&lt;/span&gt;, &lt;span class="s2"&gt;"Hello World!"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the GitHub Workflow to have the privilege to push the Docker image to DockerHub we need to add a Docker username and Personal Access Token to GitHub Secrets. For that go into the repo settings then &lt;strong&gt;Secrets and Variables&lt;/strong&gt;, and select the secret type &lt;strong&gt;Actions&lt;/strong&gt; (as shown in the image below). Now create a secret name &lt;code&gt;DOCKERHUB_USERNAME&lt;/code&gt; and the secret value as your DockerHub username.&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%2Fq0tsmg1358fc6vkxicwf.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%2Fq0tsmg1358fc6vkxicwf.png" alt=" " width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we need to generate a DockerHub Personal Access Token, for that head over to your DockerHub Account, go to setting, then click on &lt;strong&gt;Personal Access Token&lt;/strong&gt; and then click on the &lt;strong&gt;Generate new Token&lt;/strong&gt; button. A new window will open (as shown in the image below. Give a token name and &lt;strong&gt;Access Permissions&lt;/strong&gt; to &lt;strong&gt;Read &amp;amp; Write&lt;/strong&gt; (It may vary according to your use case). Copy the generated token and create a GitHub Secret (like we did above) with the name &lt;code&gt;DOCKERHUB_TOKEN&lt;/code&gt;, and paste the copied token value in the secret value.&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%2Fjtvyujx2b90zemozhl8f.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%2Fjtvyujx2b90zemozhl8f.png" alt=" " width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once done we are all set to create a workflow. Make sure you are on the root of the project, create a dir name &lt;code&gt;.github&lt;/code&gt; inside that create a dir called &lt;code&gt;workflows&lt;/code&gt; and inside that create a YAML file with any name, we will name it &lt;code&gt;dockerhub.yaml&lt;/code&gt;. The complete path will look like this &lt;code&gt;.github/workflows/dockerhub.yaml&lt;/code&gt;. Now paste the below configuration. Don't commit it yet, first, we break down and understand the below configuration.&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="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Push Image to DockerHub&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&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;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DockerHub Login&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKERHUB_USERNAME }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKERHUB_TOKEN }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Docker Buildx&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-buildx-action@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and push Docker image&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&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;file&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;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ secrets.DOCKERHUB_USERNAME }}/devops:latest&lt;/span&gt;
          &lt;span class="na"&gt;platforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux/amd64,linux/arm64,linux/arm/v7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this section, we are triggering the workflow when a release is created. We can modify the &lt;code&gt;on:&lt;/code&gt; trigger according to our release flow, like triggering the workflow when a tag is pushed, etc. Then we are using Ubuntu as a runner and then checking out the repo code so that the workflow has access to our repo code.&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="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&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;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in this part, we are logging into the DockerHub with the credentials we provided via GitHub Secrets so that the workflow has the necessary permission to push the image to our DockerHub Account. Then we set up the Docker Buildx. Buildx is the real deal that will help us build the Multi-Arch images from the same Dockerfile.&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DockerHub Login&lt;/span&gt;
        &lt;span class="s"&gt;uses&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v3&lt;/span&gt;
        &lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKERHUB_USERNAME }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKERHUB_TOKEN }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Docker Buildx&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-buildx-action@v3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the final step of the workflow here we are building the image and pushing it to DockerHub. We can set a &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;file&lt;/code&gt;, here the Dockerfile is in the root with the name Dockerfile. &lt;code&gt;tags&lt;/code&gt; helps in tagging the Docker image as we do locally. We can set multiple &lt;code&gt;tags&lt;/code&gt; apart from the &lt;code&gt;latest&lt;/code&gt; one, For example, we can automate unique image versioning by pulling the git tag pushed to trigger this workflow. So, if we push a Git tag with &lt;code&gt;1.2.3&lt;/code&gt;, the image would be something like &lt;code&gt;pradumnasaraf/devops:1.2.3&lt;/code&gt;. Make sure you change the image name from &lt;code&gt;devops&lt;/code&gt;. Instead of hardcoding you can also set the repo name and get that value from the &lt;code&gt;github&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;Lastly, we are we are providing for which platforms we need to build it for. we can give the values for &lt;code&gt;platforms&lt;/code&gt; by comma separation. Here we are building for &lt;code&gt;linux/amd64&lt;/code&gt;,&lt;code&gt;linux/arm64&lt;/code&gt; and &lt;code&gt;linux/arm/v7&lt;/code&gt;.&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and push Docker image&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&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;file&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;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ secrets.DOCKERHUB_USERNAME }}/devops:latest&lt;/span&gt;
          &lt;span class="na"&gt;platforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux/amd64,linux/arm64,linux/arm/v7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it, that was all about the explanation workflow. Now commit the changes. Based on the type of trigger you set this workflow will run and push the images to DockerHub. &lt;/p&gt;

&lt;p&gt;I have created a release on my DevOps repo with &lt;code&gt;v2.3.3&lt;/code&gt;. Now, It will push an image with the &lt;code&gt;latest&lt;/code&gt; as well as &lt;code&gt;2.3.3&lt;/code&gt;. It is gets the version number from the &lt;code&gt;package.json&lt;/code&gt; using an action to extract it. You can do this kind of workaround to make it more seamless and powerful.&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%2Flj3fb0ci609cdzcqb9jm.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%2Flj3fb0ci609cdzcqb9jm.png" alt=" " width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's head over to DockerHub to check the pushed image. We can see it we have our image published on DockerHub with the &lt;code&gt;OS/ARCH&lt;/code&gt; we have provided.&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%2Fvkm25yabl86sdq1w4x6k.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%2Fvkm25yabl86sdq1w4x6k.png" alt=" " width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now the great part is that if someone pulls an image, for eg &lt;code&gt;docker pull pradumnasaraf/devops&lt;/code&gt; docker will pull the image for that architecture only we don't need to explicitly mention that.&lt;/p&gt;

&lt;p&gt;That's come to the end of this blog. As usual, glad you made it to the end—thank you so much for your support. I regularly share Docker tips on &lt;a href="https://x.com/pradumna_saraf" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;. You can connect with me there.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>development</category>
      <category>github</category>
    </item>
    <item>
      <title>How to Publish a Golang Package</title>
      <dc:creator>Pradumna Saraf</dc:creator>
      <pubDate>Thu, 19 Sep 2024 07:19:24 +0000</pubDate>
      <link>https://dev.to/pradumnasaraf/how-to-publish-a-golang-package-i12</link>
      <guid>https://dev.to/pradumnasaraf/how-to-publish-a-golang-package-i12</guid>
      <description>&lt;p&gt;Publishing a package is a good way to share your tool with the world. Someone can import the package and use it in their project, and achieve the functionality you have built. It's quite easy to publish a Golang package compared to other languages. We will do that in this blog. I recently published my GenCLI package you can check it out &lt;a href="https://pkg.go.dev/github.com/Pradumnasaraf/gencli" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For demo purposes, I have this &lt;a href="https://github.com/Pradumnasaraf/Blog-Demo/tree/main/Go-AI" rel="noopener noreferrer"&gt;CLI project&lt;/a&gt;. This AI-powered CLI. It provides you with the answers to your questions via the terminal, built with Cobra and Google Gemini API. Now this project is local, and the only way to run it is to go to the Root of the project and do &lt;code&gt;go run main.go&lt;/code&gt; and then use sub-commands. It will work fine, but the issue here is that this is not reliable, every time, we need to go to the project root and run it, ideally, it should run from anywhere on the computer, just like other CLI tools. Plus, not everyone will do this much hassle to use it. So, this is why it's necessary, as well as important to publish the tools.&lt;/p&gt;

&lt;p&gt;There can be multiple ways to name your package, but as we will host it on GitHub we will use the GitHub way. Make sure in your &lt;code&gt;go.mod&lt;/code&gt; file your module name is the following convention - &lt;code&gt;github.com/&amp;lt;username&amp;gt;/&amp;lt;repo-name module-name&amp;gt;&lt;/code&gt;, just like below.&lt;/p&gt;

&lt;p&gt;One thing to note is that the module is a collection of Packages. We generally say Publishing a Module Not Packages, but to keep it simple we kept it that way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Pradumnasaraf&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ai&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="m"&gt;1.22&lt;/span&gt;

&lt;span class="n"&gt;require&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="o"&gt;....&lt;/span&gt;
&lt;span class="o"&gt;....&lt;/span&gt;
&lt;span class="o"&gt;....&lt;/span&gt;

&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now once everything is set push your code to GitHub. Make sure, you push the code to the same GitHub username and repo name you mentioned in the module.&lt;/p&gt;

&lt;p&gt;Once you push the code to GitHub now it's time to publish your package, but before you do, here are a couple of best practices you should follow&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;License:&lt;/strong&gt; Have a license and try to place a minimal restriction on it so that it can easily be used, modified, and redistributed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Documentation:&lt;/strong&gt; We can comment on the top of the package file to explain the functionality and golang takes this as general package documentation and shows it under the Package documentation section.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; Tagging is good when a person has a particular version of the package also tagged versions give predictable outcomes during builds. Tags should follow Semver. Also, try to release stable versions with &lt;code&gt;1.0.0&lt;/code&gt; and above, this gives developers confidence.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now to publish the package head over to the URL &lt;code&gt;https://pkg.go.dev/github.com/&amp;lt;repo-url&amp;gt;&lt;/code&gt;. In my case, it would be &lt;code&gt;https://pkg.go.dev/github.com/Pradumnasaraf/go-ai&lt;/code&gt;. When you visit, you will see a request button; click on that to request adding the package to &lt;code&gt;pkg.go.dev&lt;/code&gt;. I will not because I don't want to publish this tool as it was just for a demo.&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%2Fi2jpp776zoudsy1wf6vh.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%2Fi2jpp776zoudsy1wf6vh.png" alt="Screenshot of go dev package website" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you’re done, after a few hours, it will be on the website. Once it’s live, you can download the CLI by using the &lt;code&gt;go install&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install&lt;/span&gt; &amp;lt;repo-url&amp;gt;
go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/Pradumnasaraf/go-ai@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it for this blog. I'm glad you're still reading and made it too. Thank you! I sometimes share tips on Golang on &lt;a href="https://x.com/pradumna_saraf" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;. You can connect with me there.&lt;/p&gt;

</description>
      <category>go</category>
      <category>cli</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
