<?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: Daniel Pepuho</title>
    <description>The latest articles on DEV Community by Daniel Pepuho (@danielcristho).</description>
    <link>https://dev.to/danielcristho</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%2F694497%2Fe70913a8-75a3-48ab-981d-39b2ae24cccc.jpg</url>
      <title>DEV Community: Daniel Pepuho</title>
      <link>https://dev.to/danielcristho</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danielcristho"/>
    <language>en</language>
    <item>
      <title>AWS EKS: Running Your First Workload Using AWS CDK</title>
      <dc:creator>Daniel Pepuho</dc:creator>
      <pubDate>Fri, 06 Feb 2026 05:55:38 +0000</pubDate>
      <link>https://dev.to/danielcristho/aws-eks-running-your-first-workload-using-aws-cdk-424g</link>
      <guid>https://dev.to/danielcristho/aws-eks-running-your-first-workload-using-aws-cdk-424g</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Created an EKS cluster and deployed a workload using AWS CDK&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Separated stack between infrastructure and application&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Defined Kubernetes resources (ConfigMap, Deployment, Service) in code&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Learned why &lt;code&gt;cdk deploy --all&lt;/code&gt; is often required to apply updates&lt;/p&gt;

&lt;p&gt;Now that we have a running EKS cluster created with AWS CDK, it’s time to deploy our first workload. In this post, we’ll define Kubernetes resources using AWS CDK, deploy a simple web workload to the cluster, and expose it so it can be accessed externally.&lt;/p&gt;

&lt;p&gt;This will give us a practical foundation before moving on to ingress, scaling, and Karpenter in later posts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining Kubernetes manifests in CDK
&lt;/h2&gt;

&lt;p&gt;By default, Kubernetes workloads are defined using &lt;strong&gt;YAML manifests and applied manually using &lt;code&gt;kubectl&lt;/code&gt;&lt;/strong&gt;. While this approach works, it often leads to duplicated configuration, limited reusability, and infrastructure logic being scattered across multiple files.&lt;/p&gt;

&lt;p&gt;With AWS CDK, we can define Kubernetes resources directly in code and let CDK handle applying them to the cluster. Under the hood, CDK still talks to the Kubernetes API, but we gain the benefits of using a real programming language—such as structure, composition, and version control.&lt;/p&gt;

&lt;p&gt;BTW, our project structure should be 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;running-first-workloads/
├── app.py
├── cdk.json
├── requirements.txt
├── eks
│   ├── __init__.py
│   └── eks_stack.py &lt;span class="c"&gt;# 1. Create EKS cluster &lt;/span&gt;
├── workloads
│   ├── __init__.py
│   └── running_first_workloads_stack.py &lt;span class="c"&gt;# 2. Running workload&lt;/span&gt;
└── tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;eks/eks_stack.py&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Defines the core EKS infrastructure, including the VPC, EKS control plane, managed node group, and IAM access configuration. This stack is responsible only for the cluster itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;workloads/running_first_workloads_stack.py&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Defines Kubernetes resources that run on top of the existing cluster. In this post, this includes the ConfigMap, Deployment, and Service for the Caddy web server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;app.py&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Acts as the entry point that wires the two stacks together. The EKS cluster created in &lt;code&gt;eks_stack.py&lt;/code&gt; is passed into the workload stack, allowing workloads to be deployed without recreating the cluster.&lt;/p&gt;

&lt;p&gt;Now let’s do some work and start defining our first Kubernetes workload using AWS CDK inside &lt;code&gt;running_first_workloads_stack.py&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stack Definition and Constructor
&lt;/h3&gt;

&lt;p&gt;We start by defining a new CDK stack class that accepts an existing EKS cluster as a parameter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RunningFirstWorkloadsStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;construct_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;construct_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike the cluster stack, this stack does not create any AWS infrastructure. Instead, it receives a reference to an already running EKS cluster. This reference allows CDK to know &lt;strong&gt;where Kubernetes resources should be applied&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For our first workload, we’ll deploy Caddy, a lightweight and modern web server. Caddy is a good fit for this example because it behaves like a real-world application component while remaining simple enough to focus on Kubernetes and CDK concepts rather than application logic.&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%2Fwxfubpvg7ubz4g0goce1.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%2Fwxfubpvg7ubz4g0goce1.png" alt="Caddy Logo" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Image Source: Caddy Community&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A ConfigMap to store the Caddy configuration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A Deployment to run the Caddy containers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A Service to expose the application externally&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these resources are defined using &lt;code&gt;cluster.add_manifest()&lt;/code&gt; in the workload stack. CDK takes care of applying the manifests to the cluster in the correct order, allowing us to focus on how the workload is structured rather than how it is manually deployed.&lt;/p&gt;

&lt;p&gt;Next, we’ll start by defining a ConfigMap to store the Caddy configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  ConfigMap: Storing the Caddy Config
&lt;/h3&gt;

&lt;p&gt;Before running any containers, we need to define how Caddy should behave. Instead of embedding configuration directly into the container image, we store the configuration in a ConfigMap. This is a common Kubernetes pattern that keeps application configuration separate from the runtime environment.&lt;/p&gt;

&lt;p&gt;For this example, the configuration is intentionally minimal. We configure Caddy to listen on port 80 and return a simple HTTP response. This keeps the focus on how the workload is deployed, not on application logic. This ConfigMap will create an object named &lt;code&gt;caddy-config&lt;/code&gt; that contains a &lt;code&gt;Caddyfile&lt;/code&gt;. Caddy automatically reads its configuration from this file when it starts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;        &lt;span class="c1"&gt;# ConfigMap for Caddyfile
&lt;/span&gt;        &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_manifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CaddyConfig&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apiVersion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ConfigMap&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy-config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Caddyfile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
:80 {
    respond &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello from Caddy v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
}
&lt;/span&gt;&lt;span class="sh"&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;h3&gt;
  
  
  Deployment: Running the Containers
&lt;/h3&gt;

&lt;p&gt;The next step is to run the application containers. In Kubernetes, this is done using a Deployment, which manages how pods are created, updated, and kept running.&lt;/p&gt;

&lt;p&gt;In this example, the Deployment is responsible for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Running the Caddy container image&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Mounting the ConfigMap as a volume&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Managing multiple replicas for availability&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;        &lt;span class="c1"&gt;# Caddy Deployment
&lt;/span&gt;        &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_manifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CaddyDeployment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apiVersion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apps/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deployment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replicas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;selector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;matchLabels&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;template&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;labels&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;annotations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configmap-reload-ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;containers&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy:2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ports&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;containerPort&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
                                        &lt;span class="p"&gt;}&lt;/span&gt;
                                    &lt;span class="p"&gt;],&lt;/span&gt;
                                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volumeMounts&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy-config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mountPath&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/caddy&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volumes&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy-config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configMap&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy-config&lt;/span&gt;&lt;span class="sh"&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="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;Let’s do some break down:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;replicas: 2&lt;/code&gt;: Runs two instances of the Caddy container, providing basic availability and allowing us to observe how Kubernetes distributes traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;container image&lt;/code&gt;: Uses the official caddy:2 image&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;volumeMounts and volumes&lt;/code&gt;: The ConfigMap created earlier is mounted into the container at &lt;code&gt;/etc/caddy&lt;/code&gt;, which is where Caddy expects its configuration file by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service: Exposing the Application
&lt;/h3&gt;

&lt;p&gt;At this point, the Caddy pods are running perfectly fine inside the cluster—buuut from the outside world, they might as well not exist. You can &lt;code&gt;kubectl get pods&lt;/code&gt; all day long, but your browser still won’t load anything.&lt;/p&gt;

&lt;p&gt;To make the application actually usable, we define a &lt;strong&gt;Service&lt;/strong&gt;, which gives our pods a stable identity and a proper way to receive traffic.&lt;/p&gt;

&lt;p&gt;In this example, we use a Service of type &lt;strong&gt;LoadBalancer&lt;/strong&gt;, which is the simplest way to expose an application externally on AWS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;        &lt;span class="c1"&gt;# Service to expose Caddy
&lt;/span&gt;        &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_manifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CaddyService&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apiVersion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Service&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy-service&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LoadBalancer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;selector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ports&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;targetPort&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the Service is created, Kubernetes and AWS work together to expose the application. After a short delay, the Service will receive an external IP or DNS name that can be used to access Caddy from a browser or via &lt;code&gt;curl&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying the Stack (EKS Stack and Workload Stack)
&lt;/h2&gt;

&lt;p&gt;Before we dive deeper, here is the full code for &lt;code&gt;eks_stack.py&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;aws_eks&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;aws_ec2&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cdk.lambda_layer_kubectl_v32&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;KubectlV32Layer&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_iam&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;iam&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;constructs&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CdkEksStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;construct_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;construct_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;kubectl_layer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KubectlV32Layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;KubectlLayer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;vpc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Vpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EksVpc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;max_azs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="c1"&gt;# AZ
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EksCluster&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KubernetesVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;V1_32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;vpc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;default_capacity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;kubectl_layer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;kubectl_layer&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_nodegroup_capacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ManagedNodeGroup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;desired_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;min_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;max_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;instance_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InstanceType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t3.medium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="c1"&gt;# 2vcpu/4gi
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aws_auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_user_mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_user_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AdminUser&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;YOUR_IAM&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system:masters&lt;/span&gt;&lt;span class="sh"&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;Also, the &lt;code&gt;running_first_workloads_stack.py&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;aws_eks&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;constructs&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; 

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RunningFirstWorkloadsStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;construct_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;construct_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RunningFirstWorkloadsStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;construct_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;construct_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# ConfigMap for Caddyfile
&lt;/span&gt;        &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_manifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CaddyConfig&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apiVersion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ConfigMap&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy-config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Caddyfile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
:80 {
    respond &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello from Caddy v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
}
&lt;/span&gt;&lt;span class="sh"&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="c1"&gt;# Caddy Deployment
&lt;/span&gt;        &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_manifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CaddyDeployment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apiVersion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apps/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deployment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replicas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;selector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;matchLabels&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;template&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;labels&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;annotations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configmap-reload-ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;containers&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy:2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ports&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;containerPort&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
                                        &lt;span class="p"&gt;}&lt;/span&gt;
                                    &lt;span class="p"&gt;],&lt;/span&gt;
                                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volumeMounts&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy-config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mountPath&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/caddy&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volumes&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy-config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configMap&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy-config&lt;/span&gt;&lt;span class="sh"&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="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Service to expose Caddy
&lt;/span&gt;        &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_manifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CaddyService&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apiVersion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Service&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy-service&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LoadBalancer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;selector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;caddy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ports&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;targetPort&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the last one, &lt;code&gt;app.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_cdk&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cdk&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;eks.eks_stack&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CdkEksStack&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;workloads.running_first_workloads_stack&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RunningFirstWorkloadsStack&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 1. Create EKS cluster stack
&lt;/span&gt;&lt;span class="n"&gt;eks_stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CdkEksStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EksStack&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Deploy workloads on top of the cluster
&lt;/span&gt;&lt;span class="nc"&gt;RunningFirstWorkloadsStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RunningFirstWorkloadsStack&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;eks_stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Listing Available Stacks
&lt;/h3&gt;

&lt;p&gt;Before deploying, it’s often useful to see which stacks are defined in the application. You can list them using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cdk list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fard4llmu8ku31ghrhkag.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%2Fard4llmu8ku31ghrhkag.png" alt="CDK List" width="399" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying All Stacks
&lt;/h3&gt;

&lt;p&gt;Since the workload stack depends on the EKS stack, the simplest approach is to deploy all stacks 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;&lt;span class="nv"&gt;$ &lt;/span&gt;cdk deploy &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F5t6jccchdv2urbi234ez.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%2F5t6jccchdv2urbi234ez.png" alt="CDK Deploy" width="800" height="290"&gt;&lt;/a&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%2Fku90shadmkai9oetdxi0.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%2Fku90shadmkai9oetdxi0.png" alt="CDK Deploy" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CDK will:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Deploy the EKS infrastructure first&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Wait for the cluster to become available&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Apply the Kubernetes manifests defined in the workload stack&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying a Single Stack
&lt;/h3&gt;

&lt;p&gt;If you want to deploy only a specific stack, you can target it by name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cdk deploy EksStack

or 

&lt;span class="nv"&gt;$ &lt;/span&gt;cdk deploy RunningFirstWorkloadsStack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verify Deployment
&lt;/h2&gt;

&lt;p&gt;Once the deployment is complete, let's see that the workload is running correctly using Kubernetes commands.&lt;/p&gt;

&lt;p&gt;First, let’s check the pods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fqffkuar5h25zua5k27q2.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%2Fqffkuar5h25zua5k27q2.png" alt="List pods" width="800" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both pods are in the Running state and ready, which means the Caddy containers started successfully and are using the configuration provided via the &lt;code&gt;ConfigMap&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, let’s check the Service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get svc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fnflin3eb1roqk0tmr6yb.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%2Fnflin3eb1roqk0tmr6yb.png" alt="Get services" width="800" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we can see that the &lt;code&gt;caddy-service&lt;/code&gt; has been assigned an external endpoint by AWS. This means the load balancer is ready and traffic can now reach the application from outside the cluster.&lt;/p&gt;

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

&lt;p&gt;Copy the value from the &lt;strong&gt;EXTERNAL-IP&lt;/strong&gt; column and open it in your browser, or use curl:&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%2Fr3oybgjabz7zjr5vgbhz.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%2Fr3oybgjabz7zjr5vgbhz.png" alt="Curl external url" width="800" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, the workload is fully deployed and accessible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make Changes
&lt;/h3&gt;

&lt;p&gt;To see how changes are applied, let’s update the Caddy configuration. We’ll modify the &lt;code&gt;response&lt;/code&gt; in the Caddyfile from v1 to v2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;respond &lt;span class="s2"&gt;"Hello from Caddy v1"&lt;/span&gt; -&amp;gt; respond &lt;span class="s2"&gt;"Hello from Caddy v2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, try deploying only the workload stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cdk deploy RunningFirstWorkloadsStack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, this looks like the correct approach—after all, only the workload code has changed. However, after the deployment completes, nothing appears to have changed:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; The running pods are still the same&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; No rolling update is triggered&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; The application response still returns Hello from Caddy v1&lt;/p&gt;

&lt;p&gt;In other words, from the cluster’s point of view, the workload has not been updated.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why Didn’t This Work?
&lt;/h4&gt;

&lt;p&gt;Although the &lt;code&gt;ConfigMap&lt;/code&gt; content changed in code, Kubernetes manifests defined using &lt;code&gt;cluster.add_manifest()&lt;/code&gt; are applied through a custom resource backed by the EKS stack. When deploying only the workload stack, CDK may determine that there are no CloudFormation-level changes that require reapplying the manifests.&lt;/p&gt;

&lt;p&gt;As a result, the Kubernetes provider is not re-invoked, and the updated configuration is never applied to the cluster.&lt;/p&gt;

&lt;p&gt;Next, we deploy all stacks together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cdk deploy &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time, the behavior is different:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; The EKS stack is re-evaluated&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; The Kubernetes provider is refreshed&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; The updated manifests are applied to the cluster&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; A rolling update is triggered on the Deployment&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; New pods are created with the updated configuration&lt;/p&gt;

&lt;p&gt;After the rollout completes, the application now responds with:&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%2Fho7x9phqjhj8mxjlcxx8.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%2Fho7x9phqjhj8mxjlcxx8.png" alt="Changes to v2" width="800" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s next
&lt;/h2&gt;

&lt;p&gt;At this point, we’ve successfully deployed a workload to EKS using AWS CDK. We defined Kubernetes resources in code, deployed them through CDK, exposed the application externally, and verified that updates can be rolled out predictably.&lt;/p&gt;

&lt;p&gt;This setup it’s still just the beginning.&lt;/p&gt;

&lt;p&gt;In the next posts, we’ll continue building on top of this foundation by exploring more production-oriented patterns, including:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; CDK for more&lt;br&gt;
&lt;strong&gt;-&lt;/strong&gt; Ingress&lt;br&gt;
&lt;strong&gt;-&lt;/strong&gt; Autoscaling with Karpenter&lt;/p&gt;

&lt;p&gt;Each of these topics will build directly on the cluster and workload we’ve created, moving the setup closer to a production-ready environment.&lt;/p&gt;

&lt;p&gt;Aight, thanks for taking the time read this post. Here is the full code of this post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/danielcristho/that-i-write/tree/main/eks-cdk/running-first-workloads" rel="noopener noreferrer"&gt;danielcristho/that-i-write/running-first-workloads&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>python</category>
      <category>kubernetes</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>AWS EKS: Create Your First Cluster Using AWS CDK</title>
      <dc:creator>Daniel Pepuho</dc:creator>
      <pubDate>Wed, 28 Jan 2026 08:33:53 +0000</pubDate>
      <link>https://dev.to/danielcristho/aws-eks-create-your-first-cluster-using-aws-cdk-fg2</link>
      <guid>https://dev.to/danielcristho/aws-eks-create-your-first-cluster-using-aws-cdk-fg2</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Create an EKS cluster using AWS CDK Python&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Provision infrastructure via AWS CloudFormation&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; The setup includes a VPC, EKS control plane, and a managed EC2 node group&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Configure IAM access to use &lt;code&gt;kubectl&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Verify the cluster and clean up resources when done&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;About 2 years ago, I’ve been using AWS CDK to manage infrastructure, mostly for smaller workloads like deploying APIs on AWS Lambda. You can see the repo here: &lt;a href="https://github.com/danielcristho/cdk-go-simple-restapi" rel="noopener noreferrer"&gt;cdk-go-simple-restapi&lt;/a&gt;. This year, I want to take it a step further by using AWS CDK to create and manage an AWS EKS cluster.&lt;/p&gt;

&lt;p&gt;I’ll be writing a short series of posts around this topic, starting with the basics. In this first post we’ll focus on building a minimal EKS cluster using AWS CDK as the foundation for the next parts of the series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AWS CDK for EKS?
&lt;/h2&gt;

&lt;p&gt;Amazon EKS already provides multiple ways to create and manage clusters, from the AWS Console to CloudFormation and Terraform. However, when infrastructure starts to grow in complexity, the way it’s defined and maintained becomes just as important as the resources themselves.&lt;/p&gt;

&lt;p&gt;CDK allows you to define infrastructure using familiar programming languages. Instead of managing large YAML or JSON templates, you work with code—loops, conditions, and abstractions included. This makes infrastructure easier to reason about, review, and evolve over time. According to the documentation, AWS CDK supports multiple languages such as Python, Go, TypeScript, JavaScript, and C#&lt;/p&gt;

&lt;p&gt;For EKS specifically, the kit provides higher-level constructs that abstract away a lot of the boilerplate required to get a cluster running. Networking, IAM roles, and node groups can be defined in a few lines of code while still allowing you to customize the parts that matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  How AWS CDK Works with Amazon EKS
&lt;/h2&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%2Ffnefzb4h0nso440i0ose.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%2Ffnefzb4h0nso440i0ose.png" alt="How CDK works with EKS" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The diagram above shows us how CDK interacts with AWS services when creating an Amazon EKS cluster.&lt;/p&gt;

&lt;p&gt;Everything starts from your CDK application (stack), where infrastructure is defined using a programming language such as Go, TypeScript, or Python. At this stage, no AWS resources are created yet. When you run &lt;code&gt;cdk synth&lt;/code&gt;, AWS CDK translates your code into a standard AWS CloudFormation template.&lt;/p&gt;

&lt;p&gt;This CloudFormation template is then deployed using &lt;code&gt;cdk deploy&lt;/code&gt;. At this point, AWS CloudFormation becomes responsible for provisioning the infrastructure. It creates all required AWS resources, including the VPC, IAM roles, the EKS control plane, and the managed node group. &lt;strong&gt;AWS CDK itself does not bypass CloudFormation, it simply acts as a higher-level abstraction on top of it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once CloudFormation finishes, the Amazon EKS cluster is fully provisioned and exposes a Kubernetes control plane backed by AWS-managed infrastructure. From this point forward, the cluster behaves like a standard Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;Optionally, the CDK can also interact with the Kubernetes API to manage workloads, such as applying Kubernetes manifests or deploying Helm charts. This step usually happens after the cluster is ready and is covered in later parts of this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;In this series, all AWS CDK examples will be written in Python.&lt;br&gt;
While AWS CDK supports multiple languages such as TypeScript and Go, the underlying concepts and constructs remain the same regardless of the language used.&lt;/p&gt;
&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AWS account and configured AWS CLI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An active AWS account with permissions to create EKS, VPC, IAM, and CloudFormation resources. Installed and configured with valid credentials (&lt;code&gt;aws configure&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Python 3.10+&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Used for writing the AWS CDK application in this series.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AWS CDK installed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Installed globally via npm:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; aws-cdk
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;kubectl installed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Used to interact with the EKS cluster once it is created.&lt;/p&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Notes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;This post focuses on creating the EKS cluster. No Kubernetes workloads are deployed yet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure your AWS credentials have sufficient permissions before running cdk deploy, as EKS provisioning may take several minutes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project Initialization
&lt;/h3&gt;

&lt;p&gt;We’ll start by creating a new AWS CDK project using Python. AWS CDK provides a project template that sets up the basic structure, dependencies, and configuration needed to get started.&lt;/p&gt;

&lt;p&gt;First, create a new directory and initialize a CDK app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;cdk-eks
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;cdk-eks
&lt;span class="nv"&gt;$ &lt;/span&gt;cdk init app &lt;span class="nt"&gt;--language&lt;/span&gt; python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command generates a basic project structure for a Python-based CDK application. The output after you run &lt;code&gt;cdk init&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Applying project template app &lt;span class="k"&gt;for &lt;/span&gt;python

&lt;span class="c"&gt;# Welcome to your CDK Python project!&lt;/span&gt;

This is a blank project &lt;span class="k"&gt;for &lt;/span&gt;CDK development with Python.

The &lt;span class="sb"&gt;`&lt;/span&gt;cdk.json&lt;span class="sb"&gt;`&lt;/span&gt; file tells the CDK Toolkit how to execute your app.

...

Enjoy!

Executing Creating virtualenv...
Executing Installing dependencies...
✅ All &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After initialization, you should see a structure similar to 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="nb"&gt;.&lt;/span&gt;
├── app.py
├── cdk_eks
│   ├── cdk_eks_stack.py
│   └── __init__.py
├── cdk.json
├── README.md
├── requirements-dev.txt
├── requirements.txt
├── source.bat
└── tests
    ├── __init__.py
    └── unit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;app.py&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The entry point of the CDK application. This is where the stack is instantiated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;cdk_eks_stack.py&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Contains the definition of the CDK stack where we’ll define the EKS cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;requirements.txt&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Python dependencies for the CDK app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;cdk.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;CDK configuration file, including the command used to run the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;tests/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A placeholder for unit tests, which we’ll use in a later post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Install Dependencies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before writing any code, activate the Python virtual environment and install the dependencies. BTW, you need add this one to your &lt;code&gt;requirements.txt&lt;/code&gt;:  &lt;code&gt;aws-cdk.lambda-layer-kubectl-v34&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
&lt;span class="nv"&gt;$ &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keeping dependencies isolated using a virtual environment helps avoid version conflicts and keeps the project reproducible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Run Bootsrap&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If this is your first time using AWS CDK in the selected AWS account and region, you’ll need to bootstrap the environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cdk bootstrap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command creates the necessary resources in your AWS account that CDK requires to deploy stacks.&lt;/p&gt;

&lt;p&gt;At this point, the project is ready, and we can start defining the EKS cluster using AWS CDK.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the EKS Cluster
&lt;/h3&gt;

&lt;p&gt;In this section, we’ll define a minimal EKS cluster using AWS CDK. The goal is not to build a production-ready cluster yet, but to create a foundation that we can extend in later posts.&lt;/p&gt;

&lt;p&gt;All changes will be made inside the CDK stack file&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Import Required Modules&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;cdk_eks/cdk_eks_stack.py&lt;/code&gt; and start by importing the required CDK modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;aws_eks&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;aws_ec2&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cdk.lambda_layer_kubectl_v32&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;KubectlV32Layer&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_iam&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;iam&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;constructs&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ec2&lt;/code&gt; for networking (VPC)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;eks&lt;/code&gt; for creating the EKS cluster and its node groups&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Define the VPC&lt;/p&gt;

&lt;p&gt;EKS requires a VPC. For simplicity, we’ll let CDK create one for us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CdkEksStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;construct_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;construct_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;kubectl_layer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KubectlV32Layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;KubectlLayer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;vpc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Vpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EksVpc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;max_azs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="c1"&gt;# AZ
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a basic VPC across two Availability Zones, which is sufficient for a minimal cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Create the EKS Cluster&lt;/p&gt;

&lt;p&gt;Next, we define the EKS cluster itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EksCluster&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KubernetesVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;V1_32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vpc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;default_capacity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;kubectl_layer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;kubectl_layer&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few important points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Kubernetes version is explicitly set to avoid unexpected upgrades.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;default_capacity=0&lt;/code&gt; disables the default node group so we can define our own.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; Add a Managed Node Group&lt;/p&gt;

&lt;p&gt;Now we add a managed node group to run workloads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_nodegroup_capacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ManagedNodeGroup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;desired_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;min_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instance_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InstanceType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t3.medium&lt;/span&gt;&lt;span class="sh"&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;This creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A managed EC2-based node group&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Autoscaling between 1 and 3 nodes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Instance type for testing&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5.&lt;/strong&gt; Add IAM&lt;/p&gt;

&lt;p&gt;By default, creating an EKS cluster with AWS CDK does not automatically grant Kubernetes access to the IAM identity used to deploy the stack. While the control plane and node group roles are configured, additional IAM-to-Kubernetes RBAC mapping is required to access the cluster using kubectl.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aws_auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_user_mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_user_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AdminUser&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;REPLACE_WITH_YOUR_IAM&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system:masters&lt;/span&gt;&lt;span class="sh"&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;Or using Role&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aws_auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_role_mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_role_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EksAdminRole&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;REPLACE_WITH_YOUR_ROLE&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system:masters&lt;/span&gt;&lt;span class="sh"&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;Here is the full code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;aws_eks&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;aws_ec2&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cdk.lambda_layer_kubectl_v32&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;KubectlV32Layer&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_iam&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;iam&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;constructs&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CdkEksStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;construct_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;construct_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;kubectl_layer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KubectlV32Layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;KubectlLayer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;vpc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Vpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EksVpc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;max_azs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EksCluster&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KubernetesVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;V1_32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;vpc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;default_capacity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;kubectl_layer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;kubectl_layer&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_nodegroup_capacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ManagedNodeGroup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;desired_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;min_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;max_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;instance_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InstanceType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t3.medium&lt;/span&gt;&lt;span class="sh"&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;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aws_auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_user_mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_user_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AdminUser&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;YOUR_IAM&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system:masters&lt;/span&gt;&lt;span class="sh"&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;At this point, our CDK stack defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A VPC&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An EKS control plane&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A managed node group for worker nodes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this is defined as code and will be provisioned via CloudFormation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5&lt;/strong&gt; Synthesize the Stack&lt;/p&gt;

&lt;p&gt;Before deploying, it’s a good idea to check what CDK will generate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cdk synth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cdk synth 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-50&lt;/span&gt;

Resources:
  KubectlLayer600207B5:
    Type: AWS::Lambda::LayerVersion
    Properties:
      Content:
        S3Bucket:
          Fn::Sub: cdk-hnb659fds-assets-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AWS&lt;/span&gt;::AccountId&lt;span class="k"&gt;}&lt;/span&gt;-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AWS&lt;/span&gt;::Region&lt;span class="k"&gt;}&lt;/span&gt;
        S3Key: cc5bb5a423d0f1ccbfa20b3016434049b477f393e38ad2be0e8cba029f2a2373.zip
      Description: /opt/kubectl/kubectl 1.32.3&lt;span class="p"&gt;;&lt;/span&gt; /opt/helm/helm 3.17.2
      LicenseInfo: Apache-2.0

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

&lt;/div&gt;



&lt;p&gt;This command outputs the CloudFormation template that will be used to create the EKS cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying the Stack
&lt;/h3&gt;

&lt;p&gt;With the stack definition in place, we can now deploy the EKS cluster using AWS CDK. This step will trigger AWS CloudFormation to provision all required resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Deploy the Stack&lt;/p&gt;

&lt;p&gt;Run the following command from the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cdk deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F4mbyz9t6es9bifdn1qxc.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%2F4mbyz9t6es9bifdn1qxc.png" alt="Deployment" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During deployment, CDK will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Package and upload assets (if any)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create or update the CloudFormation stack&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Provision AWS resources such as the VPC, IAM roles, EKS control plane, and managed node group&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’ll be prompted to confirm the deployment, as the stack creates IAM roles and other security-related resources. Review the changes and approve the deployment when prompted.&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%2Foblkxdb3um5ob3f7cat4.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%2Foblkxdb3um5ob3f7cat4.png" alt="CDK Deploy" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Verifying the Cluster
&lt;/h3&gt;

&lt;p&gt;After the deployment completes, the next step is to verify that the EKS cluster has been created successfully and is in a healthy state.&lt;/p&gt;

&lt;p&gt;At this point, you should see a CloudFormation stack created by AWS CDK in the AWS Console.&lt;br&gt;
This stack represents all resources defined in the CDK application, including the VPC, IAM roles, EKS control plane, and the managed node group.&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%2Fipncjwqhydm19t8em75s.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%2Fipncjwqhydm19t8em75s.png" alt="CDK CloudFormation stack" width="800" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The stack status should be &lt;strong&gt;CREATE_COMPLETE&lt;/strong&gt;, indicating that all resources were provisioned without errors. This also reinforces that AWS CDK relies on CloudFormation as the underlying provisioning engine.&lt;/p&gt;

&lt;p&gt;Next, navigate to the Amazon EKS console. You should see the newly created cluster listed and marked as Active.&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%2F0gpxqqay0u2ax8t1ej6f.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%2F0gpxqqay0u2ax8t1ej6f.png" alt="EKC Cluster" width="800" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Accessing the Cluster with kubectl&lt;/p&gt;

&lt;p&gt;To interact with the cluster, update your kubeconfig:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;aws eks update-kubeconfig &lt;span class="nt"&gt;--name&lt;/span&gt; &amp;lt;cluster-name&amp;gt; &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;region&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once configured, verify that the nodes are registered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fju5xt6lfw3ddwurc5tjy.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%2Fju5xt6lfw3ddwurc5tjy.png" alt="kubectl get nodes" width="800" height="146"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleaning Up Resources
&lt;/h3&gt;

&lt;p&gt;This command deletes the CloudFormation stack and all resources created by AWS CDK, including the EKS cluster, node groups, and networking components.&lt;br&gt;
Since EKS is not covered by the AWS free tier, it’s recommended to clean up resources when they are no longer needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cdk destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fjynt9073i5uk6sd88ysi.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%2Fjynt9073i5uk6sd88ysi.png" alt="Stack Destroy" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;At this point, we have a fully functional Amazon EKS cluster created and managed using AWS CDK. The cluster is accessible via kubectl, the node group is running, and IAM access has been configured correctly. This gives us a foundation, but so far, the cluster is still empty.&lt;/p&gt;

&lt;p&gt;In the next post, we’ll move beyond infrastructure and start running actual workloads on this cluster. Instead of applying raw Kubernetes YAML files manually, we’ll use AWS CDK to deploy workloads in a more structured and repeatable way.&lt;/p&gt;

&lt;p&gt;Aight, thanks for taking the time read this post. Here is the full code of this post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/danielcristho/that-i-write/tree/main/eks-cdk/create-kube-cluster" rel="noopener noreferrer"&gt;danielcristho/that-i-write/create-kube-cluster&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Exploring Logging in Caddy</title>
      <dc:creator>Daniel Pepuho</dc:creator>
      <pubDate>Wed, 17 Dec 2025 18:45:40 +0000</pubDate>
      <link>https://dev.to/danielcristho/exploring-logging-in-caddy-adb</link>
      <guid>https://dev.to/danielcristho/exploring-logging-in-caddy-adb</guid>
      <description>&lt;p&gt;At some point, I realized that &lt;strong&gt;&lt;em&gt;‘it works’&lt;/em&gt;&lt;/strong&gt; is not enough. I need to know &lt;strong&gt;&lt;em&gt;how it works&lt;/em&gt;&lt;/strong&gt; too.”&lt;/p&gt;

&lt;p&gt;When something slows down or fails, the question is no longer &lt;strong&gt;&lt;em&gt;“is the service running?”&lt;/em&gt;&lt;/strong&gt; but:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Which component handled this request?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; How long did it take?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Did the proxy retry or switch upstreams?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without clear answers, even a simple issue can turn into a long time debugging😆. This is where logging stops being a checkbox feature and starts becoming a core part of system design.&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%2Fevaxa1ig6lh41stjpxfw.jpg" 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%2Fevaxa1ig6lh41stjpxfw.jpg" alt="Logs Meme" width="600" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Default Logs Are Not Enough?
&lt;/h2&gt;

&lt;p&gt;By default, Caddy already gives you access logs. They show incoming requests, response status codes, and basic metadata. For simple setups, that might be sufficient.&lt;/p&gt;

&lt;p&gt;But once a system grows even slightly those logs start to fall short.&lt;/p&gt;

&lt;p&gt;The first issue is noise. Default logs tend to mix everything together: health checks, internal probes, real user traffic. Important signals get buried under routine requests.&lt;/p&gt;

&lt;p&gt;The second issue is missing context. When a request goes through a reverse proxy with load balancing, the most interesting part is often not the request itself, but what happens behind the scenes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; How long did the upstream take to respond?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Was there a retry or a fallback?&lt;/p&gt;

&lt;p&gt;Without this information, debugging becomes guesswork. A 502 response tells you that something failed, but not where or why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This post explores how to make Caddy logs actually useful especially in a Dockerized setup with reverse proxying and load-balanced Go APIs.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Caddy Logging Model
&lt;/h2&gt;

&lt;p&gt;Like most web servers, Caddy separates access logs from error logs. &lt;strong&gt;Access logs tell you what happened to a request&lt;/strong&gt;. &lt;strong&gt;Error logs tell you why something went wrong&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Access Logs&lt;/p&gt;

&lt;p&gt;Access logs describe the lifecycle of an incoming request:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; who made the request&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; what was requested&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; how the server responded&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; how long it took&lt;/p&gt;

&lt;p&gt;This is where most operational insights come from. When properly configured, access logs can tell you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; which upstream handled a request&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; how traffic is distributed&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; where latency is introduced&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Error Logs&lt;/p&gt;

&lt;p&gt;Error logs focus on failures and exceptional cases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; upstream connection errors&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; timeouts&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; TLS or protocol issues&lt;/p&gt;

&lt;p&gt;These logs are usually quieter, but they become critical when things break. They complement access logs by explaining why a request failed, not just that it failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling JSON Access Logs
&lt;/h2&gt;

&lt;p&gt;For this setup, I’m using a simple Go application as the backend worker. Each instance responds with its hostname, making it easy to see how requests are distributed by the load balancer.&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;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&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;ResponseWriter&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;http&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="n"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hostname&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello from %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hostname&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;blockquote&gt;
&lt;p&gt;The full load-balancing setup, Caddy in front of three Go workers running in Docker has already been covered in a previous post.&lt;br&gt;
If you need the full context, you can refer to here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://danielcristho.site/blog/go-caddy-load-balancer#the-project-setup" rel="noopener noreferrer"&gt;https://danielcristho.site/blog/go-caddy-load-balancer#the-project-setup&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Logging in a Docker-Based Load Balancer
&lt;/h3&gt;

&lt;p&gt;When running Caddy inside Docker, the most practical logging strategy is to write logs to &lt;code&gt;stdout&lt;/code&gt; and let Docker handle log collection.&lt;/p&gt;

&lt;p&gt;When you first run the stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt; 

&lt;span class="nv"&gt;$ &lt;/span&gt;docker logs load_balancer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll mostly see logs like:&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="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"info"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:1765883971.5864136,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"maxprocs: Leaving GOMAXPROCS=4: CPU quota undefined"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"info"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:1765883971.586685,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"GOMEMLIMIT is updated"&lt;/span&gt;,&lt;span class="s2"&gt;"package"&lt;/span&gt;:&lt;span class="s2"&gt;"github.com/KimMachineGun/automemlimit/memlimit"&lt;/span&gt;,&lt;span class="s2"&gt;"GOMEMLIMIT"&lt;/span&gt;:16911289958,&lt;span class="s2"&gt;"previous"&lt;/span&gt;:9223372036854775807&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"info"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:1765883971.586744,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"using config from file"&lt;/span&gt;,&lt;span class="s2"&gt;"file"&lt;/span&gt;:&lt;span class="s2"&gt;"/etc/caddy/Caddyfile"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"info"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:1765883971.5883265,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"adapted config to JSON"&lt;/span&gt;,&lt;span class="s2"&gt;"adapter"&lt;/span&gt;:&lt;span class="s2"&gt;"caddyfile"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"warn"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:1765883971.5883417,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies"&lt;/span&gt;,&lt;span class="s2"&gt;"adapter"&lt;/span&gt;:&lt;span class="s2"&gt;"caddyfile"&lt;/span&gt;,&lt;span class="s2"&gt;"file"&lt;/span&gt;:&lt;span class="s2"&gt;"/etc/caddy/Caddyfile"&lt;/span&gt;,&lt;span class="s2"&gt;"line"&lt;/span&gt;:2&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"info"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:1765883971.5897346,&lt;span class="s2"&gt;"logger"&lt;/span&gt;:&lt;span class="s2"&gt;"admin"&lt;/span&gt;,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"admin endpoint started"&lt;/span&gt;,&lt;span class="s2"&gt;"address"&lt;/span&gt;:&lt;span class="s2"&gt;"localhost:2019"&lt;/span&gt;,&lt;span class="s2"&gt;"enforce_origin"&lt;/span&gt;:false,&lt;span class="s2"&gt;"origins"&lt;/span&gt;:[&lt;span class="s2"&gt;"//localhost:2019"&lt;/span&gt;,&lt;span class="s2"&gt;"//[::1]:2019"&lt;/span&gt;,&lt;span class="s2"&gt;"//127.0.0.1:2019"&lt;/span&gt;&lt;span class="o"&gt;]}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"warn"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:1765883971.5899262,&lt;span class="s2"&gt;"logger"&lt;/span&gt;:&lt;span class="s2"&gt;"http.auto_https"&lt;/span&gt;,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server"&lt;/span&gt;,&lt;span class="s2"&gt;"server_name"&lt;/span&gt;:&lt;span class="s2"&gt;"srv0"&lt;/span&gt;,&lt;span class="s2"&gt;"http_port"&lt;/span&gt;:80&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"warn"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:1765883971.5901978,&lt;span class="s2"&gt;"logger"&lt;/span&gt;:&lt;span class="s2"&gt;"http"&lt;/span&gt;,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"HTTP/2 skipped because it requires TLS"&lt;/span&gt;,&lt;span class="s2"&gt;"network"&lt;/span&gt;:&lt;span class="s2"&gt;"tcp"&lt;/span&gt;,&lt;span class="s2"&gt;"addr"&lt;/span&gt;:&lt;span class="s2"&gt;":80"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"warn"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:1765883971.590206,&lt;span class="s2"&gt;"logger"&lt;/span&gt;:&lt;span class="s2"&gt;"http"&lt;/span&gt;,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"HTTP/3 skipped because it requires TLS"&lt;/span&gt;,&lt;span class="s2"&gt;"network"&lt;/span&gt;:&lt;span class="s2"&gt;"tcp"&lt;/span&gt;,&lt;span class="s2"&gt;"addr"&lt;/span&gt;:&lt;span class="s2"&gt;":80"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"info"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:1765883971.5902092,&lt;span class="s2"&gt;"logger"&lt;/span&gt;:&lt;span class="s2"&gt;"http.log"&lt;/span&gt;,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"server running"&lt;/span&gt;,&lt;span class="s2"&gt;"name"&lt;/span&gt;:&lt;span class="s2"&gt;"srv0"&lt;/span&gt;,&lt;span class="s2"&gt;"protocols"&lt;/span&gt;:[&lt;span class="s2"&gt;"h1"&lt;/span&gt;,&lt;span class="s2"&gt;"h2"&lt;/span&gt;,&lt;span class="s2"&gt;"h3"&lt;/span&gt;&lt;span class="o"&gt;]}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"info"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:1765883971.5905173,&lt;span class="s2"&gt;"logger"&lt;/span&gt;:&lt;span class="s2"&gt;"tls.cache.maintenance"&lt;/span&gt;,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"started background certificate maintenance"&lt;/span&gt;,&lt;span class="s2"&gt;"cache"&lt;/span&gt;:&lt;span class="s2"&gt;"0xc00070c200"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"info"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:1765883971.5906706,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"autosaved config (load with --resume flag)"&lt;/span&gt;,&lt;span class="s2"&gt;"file"&lt;/span&gt;:&lt;span class="s2"&gt;"/config/caddy/autosave.json"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"info"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:1765883971.591785,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"serving initial configuration"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"info"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:1765883971.5926607,&lt;span class="s2"&gt;"logger"&lt;/span&gt;:&lt;span class="s2"&gt;"tls"&lt;/span&gt;,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"cleaning storage unit"&lt;/span&gt;,&lt;span class="s2"&gt;"storage"&lt;/span&gt;:&lt;span class="s2"&gt;"FileStorage:/data/caddy"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"info"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:1765883971.5949264,&lt;span class="s2"&gt;"logger"&lt;/span&gt;:&lt;span class="s2"&gt;"tls"&lt;/span&gt;,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"finished cleaning storage units"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;These are startup and internal runtime logs, not HTTP access logs. Access logs only appear after real HTTP traffic flows through Caddy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To make access logs enable JSON logging globally in the Caddyfile you can put &lt;code&gt;output stdout&lt;/code&gt; like this.&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="err"&gt;log&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="err"&gt;level&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;INFO&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;json&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="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;80&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="err"&gt;log&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="err"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;stdout&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;json&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="err"&gt;reverse_proxy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;worker_&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8081&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;worker_&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8081&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;worker_&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8081&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="err"&gt;lb_policy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;random&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;health_uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;health_interval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;s&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;At this point:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; logs are emitted to stdout&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Docker captures them automatically&lt;/p&gt;

&lt;p&gt;Once the stack is running, send a few requests:&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:8082
curl http://localhost:8082
curl http://localhost:8082
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see responses like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Hello from worker_1
Hello from worker_2
Hello from worker_3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now check the logs again:&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1765884414.2170205&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"logger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"tls"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"cleaning storage unit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"storage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"FileStorage:/data/caddy"&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1765884414.2187994&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"logger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"tls"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"finished cleaning storage units"&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1765884423.2440372&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"logger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http.log.access.log0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"handled request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"remote_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"remote_port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"51346"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"client_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"proto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"HTTP/1.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"localhost:8082"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Fetch-Mode"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"navigate"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Gpc"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Connection"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"keep-alive"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Priority"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"u=0, i"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Accept"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Fetch-Dest"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"document"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Fetch-Site"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"User-Agent"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"Mozilla/5.0 (X11; Linux x86_64; rv:145.0) Gecko/20100101 Firefox/145.0"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Fetch-User"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"?1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Accept-Language"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"en-US,en;q=0.5"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Accept-Encoding"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"gzip, deflate, br, zstd"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Upgrade-Insecure-Requests"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;]}},&lt;/span&gt;&lt;span class="nl"&gt;"bytes_read"&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="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;0.000733253&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"resp_headers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"Content-Length"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"20"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Via"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"1.1 Caddy"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"text/plain; charset=utf-8"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"Tue, 16 Dec 2025 11:27:03 GMT"&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1765884423.7748682&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"logger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http.log.access.log0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"handled request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"remote_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"remote_port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"51346"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"client_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"proto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"HTTP/1.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"localhost:8082"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"User-Agent"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"Mozilla/5.0 (X11; Linux x86_64; rv:145.0) Gecko/20100101 Firefox/145.0"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Fetch-User"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"?1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Gpc"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Upgrade-Insecure-Requests"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Fetch-Dest"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"document"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Fetch-Site"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Accept-Language"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"en-US,en;q=0.5"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Accept-Encoding"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"gzip, deflate, br, zstd"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Priority"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"u=0, i"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Accept"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Connection"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"keep-alive"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Fetch-Mode"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"navigate"&lt;/span&gt;&lt;span class="p"&gt;]}},&lt;/span&gt;&lt;span class="nl"&gt;"bytes_read"&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="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;0.000783659&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"resp_headers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"Via"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"1.1 Caddy"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"Tue, 16 Dec 2025 11:27:03 GMT"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Content-Length"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"20"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"text/plain; charset=utf-8"&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1765884424.1060758&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"logger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http.log.access.log0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"handled request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"remote_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"remote_port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"51346"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"client_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"proto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"HTTP/1.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"localhost:8082"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"Upgrade-Insecure-Requests"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Fetch-Site"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Accept-Language"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"en-US,en;q=0.5"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Gpc"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Fetch-Dest"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"document"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Fetch-User"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"?1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"User-Agent"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"Mozilla/5.0 (X11; Linux x86_64; rv:145.0) Gecko/20100101 Firefox/145.0"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Accept"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Accept-Encoding"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"gzip, deflate, br, zstd"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Fetch-Mode"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"navigate"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Priority"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"u=0, i"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Connection"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"keep-alive"&lt;/span&gt;&lt;span class="p"&gt;]}},&lt;/span&gt;&lt;span class="nl"&gt;"bytes_read"&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="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;0.002117571&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"resp_headers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"Via"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"1.1 Caddy"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"Tue, 16 Dec 2025 11:27:04 GMT"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Content-Length"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"20"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"text/plain; charset=utf-8"&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1765884424.456112&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"logger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http.log.access.log0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"handled request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"remote_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"remote_port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"51346"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"client_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"proto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"HTTP/1.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"localhost:8082"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"Connection"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"keep-alive"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Upgrade-Insecure-Requests"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Fetch-Site"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Fetch-User"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"?1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Priority"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"u=0, i"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Accept-Language"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"en-US,en;q=0.5"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Fetch-Mode"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"navigate"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Accept"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Accept-Encoding"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"gzip, deflate, br, zstd"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Fetch-Dest"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"document"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"User-Agent"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"Mozilla/5.0 (X11; Linux x86_64; rv:145.0) Gecko/20100101 Firefox/145.0"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Sec-Gpc"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;]}},&lt;/span&gt;&lt;span class="nl"&gt;"bytes_read"&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="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;0.000959646&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"resp_headers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"text/plain; charset=utf-8"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Via"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"1.1 Caddy"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"Tue, 16 Dec 2025 11:27:04 GMT"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Content-Length"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"20"&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1765884440.3606126&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"logger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http.log.access.log0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"handled request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"remote_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"remote_port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"49074"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"client_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"proto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"HTTP/1.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"localhost:8082"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"User-Agent"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"curl/7.81.0"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Accept"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"*/*"&lt;/span&gt;&lt;span class="p"&gt;]}},&lt;/span&gt;&lt;span class="nl"&gt;"bytes_read"&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="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;0.000425884&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"resp_headers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"Tue, 16 Dec 2025 11:27:20 GMT"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Content-Length"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"20"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"text/plain; charset=utf-8"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Via"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"1.1 Caddy"&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1765884442.148407&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"logger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http.log.access.log0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"handled request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"remote_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"remote_port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"49090"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"client_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"proto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"HTTP/1.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"localhost:8082"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"Accept"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"*/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"User-Agent"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"curl/7.81.0"&lt;/span&gt;&lt;span class="p"&gt;]}},&lt;/span&gt;&lt;span class="nl"&gt;"bytes_read"&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="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;0.000592974&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"resp_headers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"Via"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"1.1 Caddy"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"Tue, 16 Dec 2025 11:27:22 GMT"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Content-Length"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"20"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"text/plain; charset=utf-8"&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1765884442.6677225&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"logger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http.log.access.log0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"handled request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"remote_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"remote_port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"49102"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"client_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"proto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"HTTP/1.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"localhost:8082"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"User-Agent"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"curl/7.81.0"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Accept"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"*/*"&lt;/span&gt;&lt;span class="p"&gt;]}},&lt;/span&gt;&lt;span class="nl"&gt;"bytes_read"&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="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;0.000432397&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"resp_headers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"Via"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"1.1 Caddy"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Content-Length"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"20"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"text/plain; charset=utf-8"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"Tue, 16 Dec 2025 11:27:22 GMT"&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1765884443.2411807&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"logger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http.log.access.log0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"handled request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"remote_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"remote_port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"49106"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"client_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"proto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"HTTP/1.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"localhost:8082"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"Accept"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"*/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"User-Agent"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"curl/7.81.0"&lt;/span&gt;&lt;span class="p"&gt;]}},&lt;/span&gt;&lt;span class="nl"&gt;"bytes_read"&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="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;0.000496529&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"resp_headers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"text/plain; charset=utf-8"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"Tue, 16 Dec 2025 11:27:23 GMT"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Via"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"1.1 Caddy"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"Content-Length"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"20"&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;To make the logs more readable you can use &lt;code&gt;jq&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; load_balancer 2&amp;gt;&amp;amp;1 | jq &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s1"&gt;'fromjson? | {level, logger, msg, request: {method: .request.method, uri: .request.uri}, status, duration}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"logger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tls"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"finished cleaning storage units"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request"&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="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&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="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"logger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http.log.access.log0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"handled request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request"&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="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.000733253&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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Which Upstream Handled This Request?
&lt;/h3&gt;

&lt;p&gt;Using Debug Logs to Expose Load Balancer Decisions&lt;/p&gt;

&lt;p&gt;Earlier, we saw that standard access logs don’t explicitly show which backend handled a request. Caddy can expose upstream selection details, just not in access logs. The information lives in debug-level logs, specifically inside the reverse proxy handler.&lt;/p&gt;

&lt;p&gt;By enabling debug mode, Caddy reveals how routing decisions are made internally.&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="err"&gt;log&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="err"&gt;level&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;DEBUG&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;json&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;In this mode, a single request produces logs like this, you can see which upstream that handled request &lt;code&gt;"dial":"worker_3:8081"&lt;/code&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="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"debug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"logger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http.handlers.reverse_proxy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"selected upstream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"dial"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"worker_3:8081"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"total_upstreams"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&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;h2&gt;
  
  
  Simulating Failure (Error Logs)
&lt;/h2&gt;

&lt;p&gt;So far, everything looks fine. Requests succees and access logs show no anomalies.&lt;/p&gt;

&lt;p&gt;Real systems rarely fail all at once. They fail partially one backend goes down, a health check starts failing, or a connection becomes unstable. In this setup, stopping one worker is enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker stop worker_2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try to hit the URL again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://localhost:8082
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Hello from worker_3
Hello from worker_1
Hello from worker_3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the client’s perspective, nothing breaks. Requests are still served, and responses keep coming back from the remaining healthy workers. This is exactly what we expect from a load balancer.&lt;/p&gt;

&lt;p&gt;Now if you see the logs again, there is an unhealthy upstream:&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="o"&gt;{&lt;/span&gt;: dial tcp: lookup worker_2 on 127.0.0.11:53: no such host&lt;span class="s2"&gt;"}
{"&lt;/span&gt;level&lt;span class="s2"&gt;":"&lt;/span&gt;info&lt;span class="s2"&gt;","&lt;/span&gt;ts&lt;span class="s2"&gt;":1765992098.8407533,"&lt;/span&gt;logger&lt;span class="s2"&gt;":"&lt;/span&gt;http.handlers.reverse_proxy.health_checker.active&lt;span class="s2"&gt;","&lt;/span&gt;msg&lt;span class="s2"&gt;":"&lt;/span&gt;HTTP request failed&lt;span class="s2"&gt;","&lt;/span&gt;host&lt;span class="s2"&gt;":"&lt;/span&gt;worker_2:8081&lt;span class="s2"&gt;","&lt;/span&gt;error&lt;span class="s2"&gt;":"&lt;/span&gt;Get &lt;span class="se"&gt;\"&lt;/span&gt;http://worker_2:8081/&lt;span class="se"&gt;\"&lt;/span&gt;: dial tcp: lookup worker_2 on 127.0.0.11:53: no such host&lt;span class="s2"&gt;"}
{"&lt;/span&gt;level&lt;span class="s2"&gt;":"&lt;/span&gt;info&lt;span class="s2"&gt;","&lt;/span&gt;ts&lt;span class="s2"&gt;":1765992101.6597724,"&lt;/span&gt;logger&lt;span class="s2"&gt;":"&lt;/span&gt;http.handlers.reverse_proxy.health_checker.active&lt;span class="s2"&gt;","&lt;/span&gt;msg&lt;span class="s2"&gt;":"&lt;/span&gt;HTTP request failed&lt;span class="s2"&gt;","&lt;/span&gt;host&lt;span class="s2"&gt;":"&lt;/span&gt;worker_2:8081&lt;span class="s2"&gt;","&lt;/span&gt;error&lt;span class="s2"&gt;":"&lt;/span&gt;Get &lt;span class="se"&gt;\"&lt;/span&gt;http://worker_2:8081/&lt;span class="se"&gt;\"&lt;/span&gt;: dial tcp: lookup worker_2 on 127.0.0.11:53: no such host&lt;span class="s2"&gt;"}
{"&lt;/span&gt;level&lt;span class="s2"&gt;":"&lt;/span&gt;info&lt;span class="s2"&gt;","&lt;/span&gt;ts&lt;span class="s2"&gt;":1765992104.640808,"&lt;/span&gt;logger&lt;span class="s2"&gt;":"&lt;/span&gt;http.handlers.reverse_proxy.health_checker.active&lt;span class="s2"&gt;","&lt;/span&gt;msg&lt;span class="s2"&gt;":"&lt;/span&gt;HTTP request failed&lt;span class="s2"&gt;","&lt;/span&gt;host&lt;span class="s2"&gt;":"&lt;/span&gt;worker_2:8081&lt;span class="s2"&gt;","&lt;/span&gt;error&lt;span class="s2"&gt;":"&lt;/span&gt;Get &lt;span class="se"&gt;\"&lt;/span&gt;http://worker_2:8081/&lt;span class="se"&gt;\"&lt;/span&gt;: dial tcp: lookup worker_2 on 127.0.0.11:53: no such host&lt;span class="s2"&gt;"}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This message is emitted by Caddy’s active health checker. It indicates that:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;worker_2&lt;/code&gt; is no longer reachable&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; the failure happens during health check probes&lt;/p&gt;

&lt;h2&gt;
  
  
  Customizing Log Output
&lt;/h2&gt;

&lt;p&gt;Up to this point, all examples use JSON access logs. They are structured, machine-friendly, and easy to filter.&lt;/p&gt;

&lt;p&gt;Caddy allows you to control how logs are encoded, not just what gets logged. This is useful when the goal shifts from processing logs to reading them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Console Logs
&lt;/h3&gt;

&lt;p&gt;Besides json, Caddy also provides a console encoder. It formats logs in a more friendly way while still preserving structure.&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="err"&gt;log&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="err"&gt;level&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;INFO&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;json&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="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;80&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="err"&gt;log&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="err"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;stdout&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;console&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="err"&gt;reverse_proxy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;worker_&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8081&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;worker_&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8081&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;worker_&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8081&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="err"&gt;lb_policy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;random&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;health_uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;health_interval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;s&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker restart load_balancer 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After restarting the container, access logs will 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;&lt;span class="nv"&gt;$ &lt;/span&gt;docker logs load_balancer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2025/12/17 17:31:44.788 INFO    http.log.access.log0    handled request &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"request"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"remote_ip"&lt;/span&gt;: &lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;, &lt;span class="s2"&gt;"remote_port"&lt;/span&gt;: &lt;span class="s2"&gt;"36598"&lt;/span&gt;, &lt;span class="s2"&gt;"client_ip"&lt;/span&gt;: &lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;, &lt;span class="s2"&gt;"proto"&lt;/span&gt;: &lt;span class="s2"&gt;"HTTP/1.1"&lt;/span&gt;, &lt;span class="s2"&gt;"method"&lt;/span&gt;: &lt;span class="s2"&gt;"GET"&lt;/span&gt;, &lt;span class="s2"&gt;"host"&lt;/span&gt;: &lt;span class="s2"&gt;"localhost:8082"&lt;/span&gt;, &lt;span class="s2"&gt;"uri"&lt;/span&gt;: &lt;span class="s2"&gt;"/"&lt;/span&gt;, &lt;span class="s2"&gt;"headers"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"User-Agent"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"curl/7.81.0"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, &lt;span class="s2"&gt;"Accept"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*/*"&lt;/span&gt;&lt;span class="o"&gt;]}}&lt;/span&gt;, &lt;span class="s2"&gt;"bytes_read"&lt;/span&gt;: 0, &lt;span class="s2"&gt;"user_id"&lt;/span&gt;: &lt;span class="s2"&gt;""&lt;/span&gt;, &lt;span class="s2"&gt;"duration"&lt;/span&gt;: 0.001321876, &lt;span class="s2"&gt;"size"&lt;/span&gt;: 20, &lt;span class="s2"&gt;"status"&lt;/span&gt;: 200, &lt;span class="s2"&gt;"resp_headers"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Date"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Wed, 17 Dec 2025 17:31:44 GMT"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, &lt;span class="s2"&gt;"Content-Length"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"20"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, &lt;span class="s2"&gt;"Content-Type"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"text/plain; charset=utf-8"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, &lt;span class="s2"&gt;"Via"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"1.1 Caddy"&lt;/span&gt;&lt;span class="o"&gt;]}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Choosing the Right Format
&lt;/h4&gt;

&lt;p&gt;There is no universally “correct” log format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;JSON&lt;/code&gt; works best when logs are consumed by tools or pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;Console&lt;/code&gt; works better when you are actively watching logs and debugging in real time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shaping Logs Without Changing Their Meaning
&lt;/h3&gt;

&lt;p&gt;Caddy also allows fine-grained control over how log fields are named and formatted. This is not about adding new information, but about making existing information easier to work with.&lt;/p&gt;

&lt;p&gt;For example, you can:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; rename common fields&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; control timestamp formats&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; standardize duration units&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; normalize log levels&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="err"&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="err"&gt;log&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="err"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;stdout&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;json&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="err"&gt;time_format&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="s2"&gt;"2006-01-02 15:04:05 MST"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;time_local&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;duration_format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ms"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;level_format&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"upper"&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;Here’s the updated access log after applying the custom JSON format:&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="o"&gt;{&lt;/span&gt;sp_headers&lt;span class="s2"&gt;":{"&lt;/span&gt;Via&lt;span class="s2"&gt;":["&lt;/span&gt;1.1 Caddy&lt;span class="s2"&gt;"],"&lt;/span&gt;Content-Type&lt;span class="s2"&gt;":["&lt;/span&gt;text/plain&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;utf-8&lt;span class="s2"&gt;"],"&lt;/span&gt;Date&lt;span class="s2"&gt;":["&lt;/span&gt;Wed, 17 Dec 2025 17:36:47 GMT&lt;span class="s2"&gt;"],"&lt;/span&gt;Content-Length&lt;span class="s2"&gt;":["&lt;/span&gt;20&lt;span class="s2"&gt;"]}}
{"&lt;/span&gt;level&lt;span class="s2"&gt;":"&lt;/span&gt;debug&lt;span class="s2"&gt;","&lt;/span&gt;ts&lt;span class="s2"&gt;":1765993007.632117,"&lt;/span&gt;logger&lt;span class="s2"&gt;":"&lt;/span&gt;http.handlers.reverse_proxy&lt;span class="s2"&gt;","&lt;/span&gt;msg&lt;span class="s2"&gt;":"&lt;/span&gt;selected upstream&lt;span class="s2"&gt;","&lt;/span&gt;dial&lt;span class="s2"&gt;":"&lt;/span&gt;worker_1:8081&lt;span class="s2"&gt;","&lt;/span&gt;total_upstreams&lt;span class="s2"&gt;":3}
{"&lt;/span&gt;level&lt;span class="s2"&gt;":"&lt;/span&gt;debug&lt;span class="s2"&gt;","&lt;/span&gt;ts&lt;span class="s2"&gt;":1765993007.6339025,"&lt;/span&gt;logger&lt;span class="s2"&gt;":"&lt;/span&gt;http.handlers.reverse_proxy&lt;span class="s2"&gt;","&lt;/span&gt;msg&lt;span class="s2"&gt;":"&lt;/span&gt;upstream roundtrip&lt;span class="s2"&gt;","&lt;/span&gt;upstream&lt;span class="s2"&gt;":"&lt;/span&gt;worker_1:8081&lt;span class="s2"&gt;","&lt;/span&gt;duration&lt;span class="s2"&gt;":0.001633852,"&lt;/span&gt;request&lt;span class="s2"&gt;":{"&lt;/span&gt;remote_ip&lt;span class="s2"&gt;":"&lt;/span&gt;172.21.0.1&lt;span class="s2"&gt;","&lt;/span&gt;remote_port&lt;span class="s2"&gt;":"&lt;/span&gt;36378&lt;span class="s2"&gt;","&lt;/span&gt;client_ip&lt;span class="s2"&gt;":"&lt;/span&gt;172.21.0.1&lt;span class="s2"&gt;","&lt;/span&gt;proto&lt;span class="s2"&gt;":"&lt;/span&gt;HTTP/1.1&lt;span class="s2"&gt;","&lt;/span&gt;method&lt;span class="s2"&gt;":"&lt;/span&gt;GET&lt;span class="s2"&gt;","&lt;/span&gt;host&lt;span class="s2"&gt;":"&lt;/span&gt;localhost:8082&lt;span class="s2"&gt;","&lt;/span&gt;uri&lt;span class="s2"&gt;":"&lt;/span&gt;/&lt;span class="s2"&gt;","&lt;/span&gt;headers&lt;span class="s2"&gt;":{"&lt;/span&gt;X-Forwarded-For&lt;span class="s2"&gt;":["&lt;/span&gt;172.21.0.1&lt;span class="s2"&gt;"],"&lt;/span&gt;X-Forwarded-Proto&lt;span class="s2"&gt;":["&lt;/span&gt;http&lt;span class="s2"&gt;"],"&lt;/span&gt;X-Forwarded-Host&lt;span class="s2"&gt;":["&lt;/span&gt;localhost:8082&lt;span class="s2"&gt;"],"&lt;/span&gt;Via&lt;span class="s2"&gt;":["&lt;/span&gt;1.1 Caddy&lt;span class="s2"&gt;"],"&lt;/span&gt;User-Agent&lt;span class="s2"&gt;":["&lt;/span&gt;curl/7.81.0&lt;span class="s2"&gt;"],"&lt;/span&gt;Accept&lt;span class="s2"&gt;":["&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s2"&gt;"]}},"&lt;/span&gt;headers&lt;span class="s2"&gt;":{"&lt;/span&gt;Date&lt;span class="s2"&gt;":["&lt;/span&gt;Wed, 17 Dec 2025 17:36:47 GMT&lt;span class="s2"&gt;"],"&lt;/span&gt;Content-Length&lt;span class="s2"&gt;":["&lt;/span&gt;20&lt;span class="s2"&gt;"],"&lt;/span&gt;Content-Type&lt;span class="s2"&gt;":["&lt;/span&gt;text/plain&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;utf-8&lt;span class="s2"&gt;"]},"&lt;/span&gt;status&lt;span class="s2"&gt;":200}
{"&lt;/span&gt;level&lt;span class="s2"&gt;":"&lt;/span&gt;INFO&lt;span class="s2"&gt;","&lt;/span&gt;ts&lt;span class="s2"&gt;":"&lt;/span&gt;2025-12-17 17:36:47 UTC&lt;span class="s2"&gt;","&lt;/span&gt;logger&lt;span class="s2"&gt;":"&lt;/span&gt;http.log.access.log0&lt;span class="s2"&gt;","&lt;/span&gt;msg&lt;span class="s2"&gt;":"&lt;/span&gt;handled request&lt;span class="s2"&gt;","&lt;/span&gt;request&lt;span class="s2"&gt;":{"&lt;/span&gt;remote_ip&lt;span class="s2"&gt;":"&lt;/span&gt;172.21.0.1&lt;span class="s2"&gt;","&lt;/span&gt;remote_port&lt;span class="s2"&gt;":"&lt;/span&gt;36378&lt;span class="s2"&gt;","&lt;/span&gt;client_ip&lt;span class="s2"&gt;":"&lt;/span&gt;172.21.0.1&lt;span class="s2"&gt;","&lt;/span&gt;proto&lt;span class="s2"&gt;":"&lt;/span&gt;HTTP/1.1&lt;span class="s2"&gt;","&lt;/span&gt;method&lt;span class="s2"&gt;":"&lt;/span&gt;GET&lt;span class="s2"&gt;","&lt;/span&gt;host&lt;span class="s2"&gt;":"&lt;/span&gt;localhost:8082&lt;span class="s2"&gt;","&lt;/span&gt;uri&lt;span class="s2"&gt;":"&lt;/span&gt;/&lt;span class="s2"&gt;","&lt;/span&gt;headers&lt;span class="s2"&gt;":{"&lt;/span&gt;User-Agent&lt;span class="s2"&gt;":["&lt;/span&gt;curl/7.81.0&lt;span class="s2"&gt;"],"&lt;/span&gt;Accept&lt;span class="s2"&gt;":["&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s2"&gt;"]}},"&lt;/span&gt;bytes_read&lt;span class="s2"&gt;":0,"&lt;/span&gt;user_id&lt;span class="s2"&gt;":"","&lt;/span&gt;duration&lt;span class="s2"&gt;":2,"&lt;/span&gt;size&lt;span class="s2"&gt;":20,"&lt;/span&gt;status&lt;span class="s2"&gt;":200,"&lt;/span&gt;resp_headers&lt;span class="s2"&gt;":{"&lt;/span&gt;Via&lt;span class="s2"&gt;":["&lt;/span&gt;1.1 Caddy&lt;span class="s2"&gt;"],"&lt;/span&gt;Date&lt;span class="s2"&gt;":["&lt;/span&gt;Wed, 17 Dec 2025 17:36:47 GMT&lt;span class="s2"&gt;"],"&lt;/span&gt;Content-Length&lt;span class="s2"&gt;":["&lt;/span&gt;20&lt;span class="s2"&gt;"],"&lt;/span&gt;Content-Type&lt;span class="s2"&gt;":["&lt;/span&gt;text/plain&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;utf-8&lt;span class="s2"&gt;"]}}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What Changed?&lt;/p&gt;

&lt;p&gt;The structure of the log is still JSON, but key fields are now normalized:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; level is uppercase (INFO)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; readable timestamps and localized&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; duration is expressed in milliseconds instead of fractional seconds&lt;/p&gt;

&lt;p&gt;This makes the log easier to read during debugging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reducing Noise and Risk in Logs
&lt;/h3&gt;

&lt;p&gt;Once logs are readable and structured, the next concern is volume and exposure. Not every field in an access log is equally useful, and some of them are better left out entirely.&lt;/p&gt;

&lt;p&gt;Caddy allows selective filtering at the log level, making it possible to reduce noise without changing how requests are handled.&lt;/p&gt;

&lt;h4&gt;
  
  
  Reducing Noise by Removing Fields
&lt;/h4&gt;

&lt;p&gt;Access logs often include fields that are technically correct but not relevant, like &lt;code&gt;request headers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If headers are not part of your debugging workflow, you can remove them:&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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;log&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="err"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;filter&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="err"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;delete&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;wrap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;json&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;With the request field removed entirely, access logs become significantly smaller:&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="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"info"&lt;/span&gt;,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"handled request"&lt;/span&gt;,&lt;span class="s2"&gt;"duration"&lt;/span&gt;:0.001026852,&lt;span class="s2"&gt;"size"&lt;/span&gt;:20,&lt;span class="s2"&gt;"status"&lt;/span&gt;:200&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other cases, it’s enough to remove only specific nested fields:&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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;log&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="err"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;filter&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="err"&gt;request&amp;gt;headers&amp;gt;Authorization&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;delete&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;resp_headers&amp;gt;Server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;delete&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;wrap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;json&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;Instead of deleting the entire request, selectively removing fields gives a better balance.&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="s2"&gt;"headers"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"User-Agent"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"curl/7.81.0"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, &lt;span class="s2"&gt;"Accept"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*/*"&lt;/span&gt;&lt;span class="o"&gt;]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Obscuring Sensitive Information
&lt;/h3&gt;

&lt;p&gt;Some fields should not be stored in plain text at all. Rather than relying on downstream redaction, Caddy can obscure sensitive data at the source:&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="err"&gt;log&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="err"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;filter&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="err"&gt;request&amp;gt;client_ip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ip_mask&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="err"&gt;ipv&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;ipv&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;56&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="err"&gt;wrap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;json&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;With IP masking enabled, the change is subtle but important:&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="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"level"&lt;/span&gt;:&lt;span class="s2"&gt;"info"&lt;/span&gt;,&lt;span class="s2"&gt;"ts"&lt;/span&gt;:1765994419.4105134,&lt;span class="s2"&gt;"logger"&lt;/span&gt;:&lt;span class="s2"&gt;"http.log.access.log0"&lt;/span&gt;,&lt;span class="s2"&gt;"msg"&lt;/span&gt;:&lt;span class="s2"&gt;"handled request"&lt;/span&gt;,&lt;span class="s2"&gt;"request"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"remote_ip"&lt;/span&gt;:&lt;span class="s2"&gt;"172.21.0.1"&lt;/span&gt;,&lt;span class="s2"&gt;"remote_port"&lt;/span&gt;:&lt;span class="s2"&gt;"58562"&lt;/span&gt;,&lt;span class="s2"&gt;"client_ip"&lt;/span&gt;:&lt;span class="s2"&gt;"172.21.0.0"&lt;/span&gt;,&lt;span class="s2"&gt;"proto"&lt;/span&gt;:&lt;span class="s2"&gt;"HTTP/1.1"&lt;/span&gt;,&lt;span class="s2"&gt;"method"&lt;/span&gt;:&lt;span class="s2"&gt;"GET"&lt;/span&gt;,&lt;span class="s2"&gt;"host"&lt;/span&gt;:&lt;span class="s2"&gt;"localhost:8082"&lt;/span&gt;,&lt;span class="s2"&gt;"uri"&lt;/span&gt;:&lt;span class="s2"&gt;"/"&lt;/span&gt;,&lt;span class="s2"&gt;"headers"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Accept"&lt;/span&gt;:[&lt;span class="s2"&gt;"*/*"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="s2"&gt;"User-Agent"&lt;/span&gt;:[&lt;span class="s2"&gt;"curl/7.81.0"&lt;/span&gt;&lt;span class="o"&gt;]}}&lt;/span&gt;,&lt;span class="s2"&gt;"bytes_read"&lt;/span&gt;:0,&lt;span class="s2"&gt;"user_id"&lt;/span&gt;:&lt;span class="s2"&gt;""&lt;/span&gt;,&lt;span class="s2"&gt;"duration"&lt;/span&gt;:0.00146917,&lt;span class="s2"&gt;"size"&lt;/span&gt;:20,&lt;span class="s2"&gt;"status"&lt;/span&gt;:200,&lt;span class="s2"&gt;"resp_headers"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Content-Type"&lt;/span&gt;:[&lt;span class="s2"&gt;"text/plain; charset=utf-8"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="s2"&gt;"Date"&lt;/span&gt;:[&lt;span class="s2"&gt;"Wed, 17 Dec 2025 18:00:19 GMT"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="s2"&gt;"Via"&lt;/span&gt;:[&lt;span class="s2"&gt;"1.1 Caddy"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,&lt;span class="s2"&gt;"Content-Length"&lt;/span&gt;:[&lt;span class="s2"&gt;"20"&lt;/span&gt;&lt;span class="o"&gt;]}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="s2"&gt;"client_ip"&lt;/span&gt;: &lt;span class="s2"&gt;"172.21.0.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request remains traceable across logs, but the exact client address is no longer exposed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Filtering is not about producing minimal logs. It’s about producing &lt;code&gt;intentional&lt;/code&gt; logs.&lt;/p&gt;

&lt;p&gt;I only apply filters after understanding:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; which fields I actually use&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; which ones are never read&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Enabling logs is easy. Making them useful takes effort.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Access logs show what happened to a request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Debug logs show how requests are routed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Error logs show what fails behind the scenes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Filtering and formatting reduce noise without hiding behavior.&lt;/p&gt;

&lt;p&gt;Aight, that's all. Thank you for taking your time to read this post.&lt;/p&gt;

&lt;p&gt;Feel free to give me feedback, tips, or a different perspective. I’d love to hear yours and continue the discussion.&lt;/p&gt;

&lt;p&gt;Happy logging!🧐&lt;/p&gt;

&lt;p&gt;References:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;a href="https://caddyserver.com/docs/caddyfile/directives/log#log" rel="noopener noreferrer"&gt;Caddy Documentation: log&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;a href="https://caddyserver.com/docs/logging" rel="noopener noreferrer"&gt;Caddy Documentation: How Logging Works&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>containers</category>
      <category>linux</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Docker Image Compression: gzip vs zstd</title>
      <dc:creator>Daniel Pepuho</dc:creator>
      <pubDate>Tue, 02 Dec 2025 15:15:35 +0000</pubDate>
      <link>https://dev.to/danielcristho/docker-image-compression-gzip-vs-zstd-4eod</link>
      <guid>https://dev.to/danielcristho/docker-image-compression-gzip-vs-zstd-4eod</guid>
      <description>&lt;p&gt;Docker images are already compressed when you push them to registries like &lt;a href="https://hub.docker.com" rel="noopener noreferrer"&gt;Docker Hub&lt;/a&gt;, &lt;a href="//ghcr.io"&gt;GHCR&lt;/a&gt;, &lt;a href="https://aws.amazon.com/ecr" rel="noopener noreferrer"&gt;AWS ECR&lt;/a&gt;, etc.&lt;/p&gt;

&lt;p&gt;So why would anyone compress an image again by using &lt;a href="https://www.gzip.org" rel="noopener noreferrer"&gt;gzip&lt;/a&gt; or &lt;a href="https://github.com/facebook/zstd" rel="noopener noreferrer"&gt;zstd&lt;/a&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%2F6uf3u6wgvy995kjhmtcv.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%2F6uf3u6wgvy995kjhmtcv.png" alt="why this happen meme" width="400" height="295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because in the real world, engineers often need to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; move images between servers (air-gapped networks),&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; migrates registries,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; backup image to object,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; load or save images repeatedly in Continuous Integration (CI) pipelines.&lt;/p&gt;

&lt;p&gt;In these cases, &lt;code&gt;docker save&lt;/code&gt; produces a raw &lt;code&gt;.tar&lt;/code&gt; file often huge.&lt;br&gt;
Compressing that tarball can cut transfer and storage time by 50-80%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But what’s the best compression tool? So we will test gzip vs zstd&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  🤔 When Do We Need Image Compression?
&lt;/h2&gt;

&lt;p&gt;Like I said before, you need to compress your image to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; transfer image between SSH or local network (LAN),&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; work with offline or air-gapped servers,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; backup images to object storage,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; migrate to new registry.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Skip compression when images are pushed directly to registry like DockerHub, registries are handled that&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Test Up
&lt;/h2&gt;

&lt;p&gt;To get a realistic number, we will test three images:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;a href="https://hub.docker.com/layers/library/alpine/latest/images/sha256-9eec16c5eada75150a82666ba0ad6df76b164a6f8582ba5cb964c0813fa56625" rel="noopener noreferrer"&gt;alpine:latest&lt;/a&gt; -&amp;gt; ~8MB&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;a href="https://hub.docker.com/layers/library/mariadb/10.6.18/images/sha256-23492dcccccd10285fd946f2d07956aed1dbd56f34d153d7de367840fc0afa88" rel="noopener noreferrer"&gt;maridb:10.6.18&lt;/a&gt; -&amp;gt; ~396MB&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;a href="https://hub.docker.com/repository/docker/danielcristh0/datascience-notebook/tags/python-3.10.11/sha256-7d1c9ee5ee5d2ad2051f5a2e56d0861e28ced6b8a76ce0928075426b3762b79d" rel="noopener noreferrer"&gt;Custom Jupyterlab image&lt;/a&gt; -&amp;gt; ~5.4GB&lt;/p&gt;
&lt;h3&gt;
  
  
  Environment
&lt;/h3&gt;
&lt;h4&gt;
  
  
  1. Local Computer
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; CPU: 4 cores / 8 threads&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Storage: NVMe SSD&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; OS: Ubuntu 22.04&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Docker Version: 27x&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Tools: &lt;code&gt;gzip&lt;/code&gt;, &lt;code&gt;zstd&lt;/code&gt;, &lt;code&gt;time&lt;/code&gt;, &lt;code&gt;scp&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  2. VPS
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; CPU: 4 cores / 8 threads&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Storage: VirtIO-backed SSD&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; OS: Ubuntu 22.04&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Docker Version: 27x&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Tools: &lt;code&gt;gzip&lt;/code&gt;, &lt;code&gt;zstd&lt;/code&gt;, &lt;code&gt;time&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Command used
&lt;/h3&gt;

&lt;p&gt;Below are the commands used to save, compress, transfer, decompress, and load the Docker images during testing.&lt;/p&gt;
&lt;h4&gt;
  
  
  1. Save the image (local machine)
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull &amp;lt;image_name&amp;gt;
docker save &amp;lt;image_name&amp;gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &amp;lt;image_name&amp;gt;.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker pull alpine:latest

latest: Pulling from library/alpine
Digest: sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker save alpine:latest &lt;span class="nt"&gt;-o&lt;/span&gt; alpine_latest.tar

&lt;span class="nv"&gt;$ &lt;/span&gt;docker save mariadb:10.6.18 &lt;span class="nt"&gt;-o&lt;/span&gt; mariadb_10.tar

&lt;span class="nv"&gt;$ &lt;/span&gt;docker save danielcristh0/datascience-notebook:python-3.10.11 &lt;span class="nt"&gt;-o&lt;/span&gt; jupyter_notebook.tar

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

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lh&lt;/span&gt;

total 5,6G
&lt;span class="nt"&gt;-rw-------&lt;/span&gt; 1 user group 8,3M Nov 28 19:51 alpine_latest.tar
&lt;span class="nt"&gt;-rw-------&lt;/span&gt; 1 user group 5,2G Nov 28 20:18 jupyter_notebook.tar
&lt;span class="nt"&gt;-rw-------&lt;/span&gt; 1 user group 384M Nov 28 20:14 mariadb_10.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  2. Compress the image (local machine)
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;gzip&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;time gzip&lt;/span&gt; &lt;span class="nt"&gt;-k&lt;/span&gt; &amp;lt;image&amp;gt;.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time gzip&lt;/span&gt; &lt;span class="nt"&gt;-k&lt;/span&gt; alpine_latest.tar

&lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="nt"&gt;-k&lt;/span&gt; alpine_latest.tar  0,44s user 0,01s system 99% cpu 0,452 total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time gzip&lt;/span&gt; &lt;span class="nt"&gt;-k&lt;/span&gt; mariadb_10.tar

&lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="nt"&gt;-k&lt;/span&gt; mariadb_10.tar  17,21s user 0,62s system 99% cpu 17,979 total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time gzip&lt;/span&gt; &lt;span class="nt"&gt;-k&lt;/span&gt; jupyter_notebook.tar

&lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="nt"&gt;-k&lt;/span&gt; jupyter_notebook.tar  238,83s user 3,56s system 99% cpu 4:03,16 total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;-k&lt;/code&gt; → keep original file&lt;/p&gt;

&lt;p&gt;gzip uses a single CPU thread at its default level (≈ level 6)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;zstd&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;time &lt;/span&gt;zstd &lt;span class="nt"&gt;-T0&lt;/span&gt; &lt;span class="nt"&gt;-19&lt;/span&gt; &amp;lt;image&amp;gt;.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt; &lt;span class="nb"&gt;time &lt;/span&gt;zstd &lt;span class="nt"&gt;-T0&lt;/span&gt; &lt;span class="nt"&gt;-19&lt;/span&gt; alpine_latest.tar

alpine_latest.tar    : 37.01%   &lt;span class="o"&gt;(&lt;/span&gt;8617984 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 3189867 bytes, alpine_latest.tar.zst&lt;span class="o"&gt;)&lt;/span&gt;
zstd &lt;span class="nt"&gt;-T0&lt;/span&gt; &lt;span class="nt"&gt;-19&lt;/span&gt; alpine_latest.tar  3,64s user 0,10s system 100% cpu 3,734 total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt; &lt;span class="nb"&gt;time &lt;/span&gt;zstd &lt;span class="nt"&gt;-T0&lt;/span&gt; &lt;span class="nt"&gt;-19&lt;/span&gt; mariadb_10.tar

mariadb_10.tar       : 16.95%   &lt;span class="o"&gt;(&lt;/span&gt;402636288 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 68258055 bytes, mariadb_10.tar.zst&lt;span class="o"&gt;)&lt;/span&gt;
zstd &lt;span class="nt"&gt;-T0&lt;/span&gt; &lt;span class="nt"&gt;-19&lt;/span&gt; mariadb_10.tar  172,89s user 0,66s system 191% cpu 1:30,81 total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt; &lt;span class="nb"&gt;time &lt;/span&gt;zstd &lt;span class="nt"&gt;-T0&lt;/span&gt; &lt;span class="nt"&gt;-22&lt;/span&gt; jupyter_notebook.tar
Warning : compression level higher than max, reduced to 19
zstd: jupyter_notebook.tar.zst already exists&lt;span class="p"&gt;;&lt;/span&gt; overwrite &lt;span class="o"&gt;(&lt;/span&gt;y/n&lt;span class="o"&gt;)&lt;/span&gt; ? y

jupyter_notebook.tar : 24.79%   &lt;span class="o"&gt;(&lt;/span&gt;5560227328 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 1378450873 bytes, jupyter_notebook.tar.zst&lt;span class="o"&gt;)&lt;/span&gt;
zstd &lt;span class="nt"&gt;-T0&lt;/span&gt; &lt;span class="nt"&gt;-22&lt;/span&gt; jupyter_notebook.tar  4759,54s user 19,32s system 188% cpu 42:11,68 total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;-T0&lt;/code&gt; → use all CPU threads&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-22&lt;/code&gt; → request maximum compression (automatically reduced to &lt;code&gt;-19&lt;/code&gt; by zstd)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  3. Transfer to VPS
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;gzip&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;time &lt;/span&gt;scp &amp;lt;image_name&amp;gt;.tar.gz user@vps:/tmp/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;scp alpine_latest.tar.gz onomi@myserver:/tmp
alpine_latest.tar.gz    100% 3588KB 174.8KB/s   00:20

scp alpine_latest.tar.gz onomi@myserver:/tmp  0,11s user 0,29s system 1% cpu 23,208 total


&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;scp mariadb_10.tar.gz onomi@myserver:/tmp/
mariadb_10.tar.gz   100%  114MB   2.2MB/s   00:50

scp mariadb_10.tar.gz onomi@myserver:/tmp/  0,46s user 0,84s system 2% cpu 52,457 total


&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;scp jupyter_notebook.tar.gz onomi@myserver:/tmp/
jupyter_notebook.tar.gz  100% 1765MB   3.4MB/s   08:35

scp jupyter_notebook.tar.gz onomi@myserver:/tmp/  5,03s user 10,42s system 2% cpu 8:38,50 total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;zstd&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;time &lt;/span&gt;scp &amp;lt;image_name&amp;gt;.tar.zst user@vps:/tmp/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;scp alpine_latest.tar.zst onomi@myserver:/tmp
alpine_latest.tar.zst   100% 3115KB 343.4KB/s   00:09

scp alpine_latest.tar.zst onomi@myserver:/tmp  0,10s user 0,18s system 1% cpu 22,728 total


&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;scp mariadb_10.tar.zst onomi@myserver:/tmp/
mariadb_10.tar.zst  100%   65MB   3.0MB/s   00:21

scp mariadb_10.tar.zst onomi@myserver:/tmp/  0,29s user 0,59s system 3% cpu 23,285 total


&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;scp jupyter_notebook.tar.zst onomi@myserver:/tmp/
jupyter_notebook.tar.zst    100% 1315MB   2.3MB/s   09:44

scp jupyter_notebook.tar.zst onomi@myserver:/tmp/  3,94s user 7,64s system 1% cpu 9:46,33 total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Load the Image on the Server (VPS)
&lt;/h4&gt;

&lt;p&gt;Now the compressed images are transferred to the VPS, the next step is to decompress them and load the Docker image into the remote server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;gzip&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;time gzip&lt;/span&gt; &lt;span class="nt"&gt;-dk&lt;/span&gt; &amp;lt;image&amp;gt;.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time gzip&lt;/span&gt; &lt;span class="nt"&gt;-dk&lt;/span&gt; alpine_latest.tar.gz

real    0m0.189s
user    0m0.116s
sys     0m0.039s


&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time gzip&lt;/span&gt; &lt;span class="nt"&gt;-dk&lt;/span&gt; mariadb_10.tar.gz


real    0m5.108s
user    0m3.813s
sys     0m1.129s

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time gzip&lt;/span&gt; &lt;span class="nt"&gt;-dk&lt;/span&gt; jupyter_notebook.tar.gz

real    1m8.344s
user    0m48.466s
sys     0m13.408s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;zstd&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;time &lt;/span&gt;zstd &lt;span class="nt"&gt;-d&lt;/span&gt; &amp;lt;image&amp;gt;.tar.zst
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;zstd &lt;span class="nt"&gt;-d&lt;/span&gt; alpine_latest.tar.zst
alpine_latest.tar.zst: 8617984 bytes

real    0m4.121s
user    0m0.041s
sys     0m0.043s

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;zstd &lt;span class="nt"&gt;-d&lt;/span&gt; mariadb_10.tar.zst
mariadb_10.tar.zst  : 402636288 bytes

real    0m3.455s
user    0m0.983s
sys     0m0.927s

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;zstd &lt;span class="nt"&gt;-d&lt;/span&gt; jupyter_notebook.tar.zst

jupyter_notebook.tar.zst: 5560227328 bytes

real    0m31.810s
user    0m14.599s
sys     0m13.600s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Decompression in &lt;code&gt;zstd&lt;/code&gt;is extremely fast, 5-10x faster than compression, even for large files.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  5. Loading the Image Into Docker
&lt;/h4&gt;

&lt;p&gt;Once decompressed, load the &lt;code&gt;.tar&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker load &lt;span class="nt"&gt;-i&lt;/span&gt; &amp;lt;image&amp;gt;.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker load &lt;span class="nt"&gt;-i&lt;/span&gt; jupyter_notebook.tar

Loaded image: danielcristh0/datascience-notebook:python-3.10.11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker images

IMAGE                                               ID             DISK USAGE   CONTENT SIZE   EXTRA
danielcristh0/datascience-notebook:python-3.10.11   9b38bf7c570f       11.4GB         5.56GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  6. Analysis: gzip vs zstd
&lt;/h4&gt;

&lt;p&gt;After running all compression, transfer, decompression, and loading tests across three different Docker images, let's compare gzip and zstd.&lt;/p&gt;

&lt;h5&gt;
  
  
  Size Comparison
&lt;/h5&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;zstd&lt;/code&gt; consistently produces much smaller output files than &lt;code&gt;gzip&lt;/code&gt;, especially on medium and large images.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Image&lt;/th&gt;
&lt;th&gt;Actual Size&lt;/th&gt;
&lt;th&gt;gzip Size&lt;/th&gt;
&lt;th&gt;zstd Size&lt;/th&gt;
&lt;th&gt;Reduction (gzip)&lt;/th&gt;
&lt;th&gt;Reduction (zstd)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;alpine:latest&lt;/td&gt;
&lt;td&gt;8.3 MB&lt;/td&gt;
&lt;td&gt;3.5 MB&lt;/td&gt;
&lt;td&gt;3.1 MB&lt;/td&gt;
&lt;td&gt;~57%&lt;/td&gt;
&lt;td&gt;~62%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mariadb:10.6.18&lt;/td&gt;
&lt;td&gt;384 MB&lt;/td&gt;
&lt;td&gt;114 MB&lt;/td&gt;
&lt;td&gt;65 MB&lt;/td&gt;
&lt;td&gt;~70%&lt;/td&gt;
&lt;td&gt;~83%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jupyter-notebook&lt;/td&gt;
&lt;td&gt;5.2 GB&lt;/td&gt;
&lt;td&gt;1.7 GB&lt;/td&gt;
&lt;td&gt;1.3 GB&lt;/td&gt;
&lt;td&gt;~67%&lt;/td&gt;
&lt;td&gt;~75%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;zstd&lt;/code&gt; gives around 20-50% better compression than gzip.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h5&gt;
  
  
  Speed
&lt;/h5&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;gzip&lt;/code&gt;is faster than &lt;code&gt;zstd&lt;/code&gt; at compression.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Image&lt;/th&gt;
&lt;th&gt;gzip (time)&lt;/th&gt;
&lt;th&gt;zstd (time)&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;alpine&lt;/td&gt;
&lt;td&gt;0.45 s&lt;/td&gt;
&lt;td&gt;3.7 s&lt;/td&gt;
&lt;td&gt;8x slower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mariadb&lt;/td&gt;
&lt;td&gt;17.9 s&lt;/td&gt;
&lt;td&gt;90.8 s&lt;/td&gt;
&lt;td&gt;5x slower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jupyter-notebook&lt;/td&gt;
&lt;td&gt;243 s&lt;/td&gt;
&lt;td&gt;42 minutes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10x slower&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;zstd&lt;/code&gt; gives better compression but requires significantly more CPU.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h5&gt;
  
  
  Transfer Speed (via SCP)
&lt;/h5&gt;

&lt;blockquote&gt;
&lt;p&gt;Because &lt;code&gt;zstd&lt;/code&gt; produces smaller files, transfer times are  2x faster. But on larger files, &lt;code&gt;zstd&lt;/code&gt; can still lose to &lt;code&gt;gzip&lt;/code&gt; depending on &lt;strong&gt;CPU and disk performance&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Image&lt;/th&gt;
&lt;th&gt;gzip Transfer&lt;/th&gt;
&lt;th&gt;zstd Transfer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;alpine&lt;/td&gt;
&lt;td&gt;20 s&lt;/td&gt;
&lt;td&gt;9 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mariadb&lt;/td&gt;
&lt;td&gt;50 s&lt;/td&gt;
&lt;td&gt;21 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jupyter-notebook&lt;/td&gt;
&lt;td&gt;8m 35s&lt;/td&gt;
&lt;td&gt;9m 44s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When Should You Use gzip or zstd?
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Use zstd when you want
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; The smallest compressed Docker images&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Fast decompression&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Faster transfers across networks&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Long-term backups&lt;/p&gt;

&lt;h4&gt;
  
  
  Use gzip when you want:
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Fast compression&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Low CPU usage&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Simple, predictable behavior&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Occasional small image transfers&lt;/p&gt;

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

&lt;p&gt;If you need to compress Docker images, here’s the quick answer:&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;zstd&lt;/code&gt; when you want:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Smaller archive sizes (around 20-50% smaller than &lt;code&gt;gzip&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Faster decompression&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Faster network transfers&lt;/p&gt;

&lt;p&gt;Use gzip when you want:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Fast compression&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Low CPU usage&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Simplicity&lt;/p&gt;

&lt;p&gt;Aight, that's all. Thank you for taking your time to read this post.&lt;/p&gt;

&lt;p&gt;Feel free to give me feedback, tips, or different benchmarks. I’d love to hear your feedback and continue the discussion.&lt;/p&gt;

&lt;p&gt;Happy containerization! 🐳&lt;/p&gt;

</description>
      <category>docker</category>
      <category>containers</category>
      <category>linux</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Running Firefox in Docker? Yes, with a GUI and noVNC!</title>
      <dc:creator>Daniel Pepuho</dc:creator>
      <pubDate>Fri, 21 Nov 2025 15:02:15 +0000</pubDate>
      <link>https://dev.to/danielcristho/running-firefox-in-docker-yes-with-a-gui-and-novnc-5fk</link>
      <guid>https://dev.to/danielcristho/running-firefox-in-docker-yes-with-a-gui-and-novnc-5fk</guid>
      <description>&lt;p&gt;Docker isn’t just for serve your code, appliactions. you can actually run a full desktop app inside it. In this project, I containerized Firefox with a virtual desktop and made it accessible through a browser using noVNC.&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%2F18da77maiv2f289gqnfc.jpg" 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%2F18da77maiv2f289gqnfc.jpg" alt="noVNC Docker meme" width="500" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What this project does?
&lt;/h2&gt;

&lt;p&gt;It creates a lightweight container that:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Runs a minimal desktop environment (&lt;code&gt;Fluxbox&lt;/code&gt;)&lt;br&gt;
&lt;strong&gt;-&lt;/strong&gt; Launches Firefox&lt;br&gt;
&lt;strong&gt;-&lt;/strong&gt; Serves a VNC display using &lt;code&gt;x11vnc&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;-&lt;/strong&gt; Exposes that desktop through &lt;code&gt;noVNC&lt;/code&gt; (so you can open it in your web browser)&lt;/p&gt;

&lt;p&gt;You can literally open Firefox running inside Docker, from your browser tab.&lt;br&gt;
All using a single &lt;code&gt;docker compose up&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  How it works?
&lt;/h2&gt;

&lt;p&gt;Here’s a quick breakdown of what happens inside the container:&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%2Fy45ijip0090ocsckbj4l.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%2Fy45ijip0090ocsckbj4l.png" alt="Breakddown" width="800" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything runs headlessly, there’s no physical display, but the combo of &lt;code&gt;Xvfb + Fluxbox&lt;/code&gt; gives Firefox a virtual desktop.&lt;/p&gt;
&lt;h2&gt;
  
  
  🐋 Dockerfile Overview
&lt;/h2&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="s"&gt; alpine:edge&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    faenza-icon-theme &lt;span class="se"&gt;\
&lt;/span&gt;    firefox &lt;span class="se"&gt;\
&lt;/span&gt;    fluxbox &lt;span class="se"&gt;\
&lt;/span&gt;    xfce4 &lt;span class="se"&gt;\
&lt;/span&gt;    xvfb &lt;span class="se"&gt;\
&lt;/span&gt;    x11vnc &lt;span class="se"&gt;\
&lt;/span&gt;    novnc &lt;span class="se"&gt;\
&lt;/span&gt;    supervisor &lt;span class="se"&gt;\
&lt;/span&gt;    bash &lt;span class="se"&gt;\
&lt;/span&gt;    net-tools

&lt;span class="c"&gt;# Install noVNC &amp;amp; websockify from source code&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; git python3 py3-pip &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /usr/share/novnc &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git clone https://github.com/novnc/noVNC.git /usr/share/novnc &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git clone https://github.com/novnc/websockify.git /usr/share/novnc/utils/websockify &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-sf&lt;/span&gt; /usr/share/novnc/vnc.html /usr/share/novnc/index.html

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; DISPLAY=:1&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; RESOLUTION=1920x1080x24&lt;/span&gt;

&lt;span class="c"&gt;# Set vnc password&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; VNC_PASS=dummypass&lt;/span&gt;

&lt;span class="c"&gt;# Create vnc password file&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /root/.vnc &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    x11vnc &lt;span class="nt"&gt;-storepasswd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VNC_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; /root/.vnc/passwd

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

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

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["supervisord", "-c", "/etc/supervisord.conf", "-n"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  📦 Docker Compose Setup
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&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;vnc_firefox&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="s"&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;vnc_firefox&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5901:5900"&lt;/span&gt; &lt;span class="c1"&gt;# VNC&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6080:6080"&lt;/span&gt; &lt;span class="c1"&gt;# noVNC web UI&lt;/span&gt;
    &lt;span class="err"&gt; &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-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;netstat&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-tln&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;grep&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-q&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;6080&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1"&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;1m30s&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;30s&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;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Under the Hood (process supervision)
&lt;/h2&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%2Fvs50q9kmku12resihs17.jpg" 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%2Fvs50q9kmku12resihs17.jpg" alt="Supervisor meme" width="735" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything is managed by &lt;code&gt;supervisord&lt;/code&gt;, which runs:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Xvfb&lt;/code&gt; - virtual framebuffer display&lt;/p&gt;

&lt;p&gt;&lt;code&gt;x11vnc&lt;/code&gt; - provides VNC access&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fluxbox&lt;/code&gt; - lightweight window manager (WM)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;firefox&lt;/code&gt; - your GUI browser&lt;/p&gt;

&lt;p&gt;&lt;code&gt;novnc_proxy&lt;/code&gt; - web socket bridge&lt;/p&gt;

&lt;p&gt;Example &lt;code&gt;supervisord.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Supervisor main config&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;supervisord]
&lt;span class="nv"&gt;nodaemon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;logfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log/supervisord.log
&lt;span class="nv"&gt;pidfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/run/supervisord.pid
&lt;span class="nv"&gt;childlogdir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log

&lt;span class="c"&gt;# XVirtual Framebuffer (Xvfb)&lt;/span&gt;
&lt;span class="c"&gt;# Creates a virtual display environment (:1)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;program:xvfb]
&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/Xvfb :1 &lt;span class="nt"&gt;-screen&lt;/span&gt; 0 1920x1080x24
&lt;span class="nv"&gt;autostart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;autorestart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;priority&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10

&lt;span class="c"&gt;# x11vnc, VNC server&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;program:x11vnc]
&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/x11vnc &lt;span class="nt"&gt;-display&lt;/span&gt; :1 &lt;span class="nt"&gt;-rfbauth&lt;/span&gt; /root/.vnc/passwd &lt;span class="nt"&gt;-forever&lt;/span&gt; &lt;span class="nt"&gt;-shared&lt;/span&gt; &lt;span class="nt"&gt;-rfbport&lt;/span&gt; 5900
&lt;span class="nv"&gt;autostart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;autorestart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;priority&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20

&lt;span class="c"&gt;# fluxbox, lightweight window manager&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;program:fluxbox]
&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/fluxbox
&lt;span class="nv"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;DISPLAY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;":1"&lt;/span&gt;
&lt;span class="nv"&gt;autostart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;autorestart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;priority&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;30

&lt;span class="c"&gt;# Runs firefox inside the Xvfb + Fluxbox environment&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;program:firefox]
&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/firefox
&lt;span class="nv"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;DISPLAY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;":1"&lt;/span&gt;
&lt;span class="nv"&gt;autostart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;autorestart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;priority&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;40

&lt;span class="c"&gt;# noVNC, WebSocket VNC Proxy&lt;/span&gt;
&lt;span class="c"&gt;# Bridges VNC (port 5900) to a web interface (port 6080)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;program:novnc]
&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/share/novnc/utils/novnc_proxy &lt;span class="nt"&gt;--vnc&lt;/span&gt; localhost:5900 &lt;span class="nt"&gt;--listen&lt;/span&gt; 6080
&lt;span class="nv"&gt;autostart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;autorestart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;priority&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our project structure should look likes 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="nb"&gt;.&lt;/span&gt;
├── docker-compose.yml
├── Dockerfile
└── supervisord.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to Run It?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Build &amp;amp; start the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fyz3dm9hawgosaa4ymcrc.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%2Fyz3dm9hawgosaa4ymcrc.gif" alt="Docker compose up" width="720" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2&lt;/strong&gt; Access it by using your browser or VNC client. Using &lt;code&gt;dummypass&lt;/code&gt; as password:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Using noVNC Web: &lt;code&gt;http://localhost:6080&lt;/code&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%2F9ivebvy7nku1luu3vh8n.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%2F9ivebvy7nku1luu3vh8n.gif" alt="noVNC Access" width="760" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Open any VNC viewer (e.g., RealVNC, TigerVNC, Remmina), then connect to: &lt;code&gt;localhost:5901&lt;/code&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%2Fpazgjw1zzwint87vnlwi.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%2Fpazgjw1zzwint87vnlwi.gif" alt="VNC viewwer" width="600" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;While this is mostly a fun experiment, it can be used for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Headless browser testing environments&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Remote browsing (isolated Firefox)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Demonstrating GUI automation setups&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NB&lt;/strong&gt;: 😎 This post is part of my &lt;strong&gt;“Container Stuffs”&lt;/strong&gt; open-source project on GitHub. Upcoming labs include:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; PostgreSQL master–replica setup&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Redis Sentinel cluster&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Multi-node Docker Swarm simulations&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; and more to come!&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%2Fn5g8udk80m4xd47d679g.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%2Fn5g8udk80m4xd47d679g.png" alt="Container Stuff" width="648" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/danielcristho/container-stuffs" rel="noopener noreferrer"&gt;&lt;strong&gt;danielcristho/container-stuffs&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Docker is more than backend services you can literally containerize &lt;code&gt;entire user experiences&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Projects like this prove that containers aren’t limited to APIs, databases, and background workers. With a bit of creativity, you can run full desktops, interactive UIs, browsers, automation toolchains, and even full development environments inside isolated, reproducible containers.&lt;/p&gt;

&lt;p&gt;Running Firefox inside Docker with noVNC shows how:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; system-level components (Xvfb, window managers, VNC)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; web technologies (WebSockets, noVNC)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; and container orchestration (Docker + Compose) can blend together to create something both useful and fun. &lt;/p&gt;

&lt;p&gt;Cheers, feel free to give me a feedback, and happy containerizing! 🐳&lt;/p&gt;

</description>
      <category>docker</category>
      <category>containers</category>
      <category>linux</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>My First Experience as a Speaker at AWS Community Day</title>
      <dc:creator>Daniel Pepuho</dc:creator>
      <pubDate>Tue, 04 Nov 2025 01:02:13 +0000</pubDate>
      <link>https://dev.to/danielcristho/my-first-experience-as-a-speaker-at-aws-community-day-2ki3</link>
      <guid>https://dev.to/danielcristho/my-first-experience-as-a-speaker-at-aws-community-day-2ki3</guid>
      <description>&lt;p&gt;Hello everyone! 👋&lt;/p&gt;

&lt;p&gt;Last weekend, I had the amazing opportunity to be a speaker at &lt;strong&gt;AWS Community Day Indonesia 2025&lt;/strong&gt;.&lt;br&gt;
It was one of the most rewarding and eye-opening experiences in my tech journey and in this post, I’d love to share what the day was like, what I learned, and why it meant so much to me. BTW, I shared about &lt;strong&gt;“Building a Multi-Tenant Machine Learning Platform on AWS EKS with Ray and JupyterHub”&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before the Event: Preparation
&lt;/h2&gt;

&lt;p&gt;A few days before the event, to be honest, I felt a mix of excitement and nervousness.&lt;br&gt;
Even though I’ve presented in smaller settings before, speaking at a community event like this felt special and a bit more serious!&lt;/p&gt;

&lt;p&gt;Here’s what I did to prepare:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; Finalized my presentation topic, making sure the content was practical and relevant for developers and cloud enthusiast&lt;br&gt;
&lt;strong&gt;-&lt;/strong&gt; Practiced my talk several times out loud to get comfortable with the flow&lt;br&gt;
&lt;strong&gt;-&lt;/strong&gt; Set my mindset: &lt;em&gt;I’m not just here to present. I’m here to connect, share, and learn from others&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That mindset shift really helped me calm down before stepping on stage&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%2F48rkqkvsdk5g98odacgi.jpg" 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%2F48rkqkvsdk5g98odacgi.jpg" alt="Pre Event" width="800" height="1013"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  During the Event: On Stage and Beyond
&lt;/h2&gt;

&lt;p&gt;When the day finally came, the atmosphere was incredible buzzing with conversations, excitement, and passion for AWS and cloud technologies.&lt;/p&gt;

&lt;p&gt;Some of my favorite moments were:&lt;br&gt;
&lt;strong&gt;-&lt;/strong&gt; Meeting so many people from the AWS community, many of whom I had only interacted with online before.&lt;br&gt;
&lt;strong&gt;-&lt;/strong&gt; Taking the stage to deliver my session “Building a Multi-Tenant Machine Learning Platform on AWS EKS with Ray and JupyterHub”.&lt;br&gt;
&lt;strong&gt;-&lt;/strong&gt; Even though I was nervous at first, once I started talking, everything clicked. It didn’t feel like a “presentation” anymore it felt like a conversation with friends who shared the same curiosity and enthusiasm.&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%2Fres.cloudinary.com%2Fdiunivf9n%2Fimage%2Fupload%2Fv1762141756%2FHLP00496_yl8br2.jpg" 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%2Fres.cloudinary.com%2Fdiunivf9n%2Fimage%2Fupload%2Fv1762141756%2FHLP00496_yl8br2.jpg" alt="Demo" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways &amp;amp; Reflections
&lt;/h2&gt;

&lt;p&gt;Looking back, here are a few lessons that really stuck with me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;strong&gt;Community is everything&lt;/strong&gt;. Behind every technology, it’s always about people learning, sharing, and growing together.&lt;br&gt;
&lt;strong&gt;-&lt;/strong&gt; &lt;strong&gt;Preparation builds confidence&lt;/strong&gt;. The more I practiced, the more natural it felt on stage.&lt;br&gt;
&lt;strong&gt;-&lt;/strong&gt; &lt;strong&gt;Sharing is a two-way process&lt;/strong&gt;. I came to give a talk, but I left with more insights from the audience than I expected&lt;br&gt;
&lt;strong&gt;-&lt;/strong&gt; &lt;strong&gt;Stay adaptable&lt;/strong&gt;. Every crowd is different. Reading the room and adjusting how you explain something is an important skill.&lt;br&gt;
&lt;strong&gt;-&lt;/strong&gt; &lt;strong&gt;Keep growing&lt;/strong&gt;. This experience reminded me &lt;em&gt;that growth doesn’t just happen behind the keyboard it happens when you step out, speak, and share what you’ve learned&lt;/em&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%2Fres.cloudinary.com%2Fdiunivf9n%2Fimage%2Fupload%2Fv1762242426%2FSTY07845_dkqweb.jpg" 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%2Fres.cloudinary.com%2Fdiunivf9n%2Fimage%2Fupload%2Fv1762242426%2FSTY07845_dkqweb.jpg" alt="Post Event" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;To be honest, there are still many aspects I haven’t fully covered in my presentation. After this event, I plan to expand this talk into a deeper technical series exploring the end-to-end ML lifecycle using Ray on AWS.&lt;/p&gt;

&lt;p&gt;While this session mainly focused on the infrastructure layer, there’s so much more to uncover about how Ray can power the entire machine learning pipeline from data preprocessing and distributed training with &lt;code&gt;Ray Train&lt;/code&gt;, to hyperparameter tuning with &lt;code&gt;Ray Tune&lt;/code&gt;, and serving models in production with &lt;code&gt;Ray Serve&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can see my deck here: &lt;a href="https://speakerdeck.com/danielcristho/building-a-multi-tenant-machine-learning-platform-on-aws-eks-with-ray-and-jupyterhub" rel="noopener noreferrer"&gt;Deck&lt;/a&gt;&lt;/p&gt;

</description>
      <category>todayilearned</category>
      <category>aws</category>
      <category>learning</category>
    </item>
    <item>
      <title>Exploring Load Balancing in Caddy Using Docker</title>
      <dc:creator>Daniel Pepuho</dc:creator>
      <pubDate>Wed, 27 Aug 2025 15:55:33 +0000</pubDate>
      <link>https://dev.to/danielcristho/exploring-load-balancing-in-caddy-using-docker-5gn6</link>
      <guid>https://dev.to/danielcristho/exploring-load-balancing-in-caddy-using-docker-5gn6</guid>
      <description>&lt;p&gt;Hello there! In this post, we'll dive into the world of Caddy, a modern and powerful web server. Built using Go, Caddy offers a range of built-in features, including &lt;strong&gt;reverse proxy&lt;/strong&gt; and &lt;strong&gt;load balancing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By the way, in this project, I'll use &lt;strong&gt;Caddy 2&lt;/strong&gt;, the latest version, which comes with a completely rewritten architecture and enhanced features. To make our setup and testing as smooth as possible, we'll leverage Docker. Docker allows us to create a reproducible environment with all the necessary components easily.&lt;/p&gt;

&lt;p&gt;The main focus of this post will be on understanding and experimenting with Caddy's &lt;strong&gt;load balancing algorithms&lt;/strong&gt;. We'll explore how Caddy intelligently distributes incoming traffic, the different strategies it offers (such as &lt;strong&gt;Round Robin&lt;/strong&gt;, &lt;strong&gt;Least Connection&lt;/strong&gt;, &lt;strong&gt;Random&lt;/strong&gt;, and more), and how you can configure the for various use cases.&lt;/p&gt;

&lt;p&gt;By using Docker, we'll quickly spin up multiple backend services (which are called "workers") and route requests through a single Caddy instance acting as our load balancer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Project Setup
&lt;/h2&gt;

&lt;p&gt;To get started, let's look at the project structure. Our setup is straightforward, allowing us to focus on the core concepts of load balancing.&lt;/p&gt;

&lt;p&gt;Here's the project structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;├── caddy
│   └── Caddyfile
├── docker-compose.yml
└── src
    ├── Dockerfile
    ├── go.mod
    └── main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our setup is composes of three main parts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; &lt;strong&gt;Backend Workers (&lt;code&gt;src&lt;/code&gt;)&lt;/strong&gt;: These are simple Go applications the will handle the requests. Each worker will simply return a "Hello form [hostname]" message, allowing us to sess which server is handling the request. This is the perfect way to visualize how Caddy distributes the load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; &lt;strong&gt;Caddy Load Balancer (&lt;code&gt;caddy&lt;/code&gt;)&lt;/strong&gt;: Our Caddy instance will act as the reverse proxy, forwarding requests to the worker services. We'll use the &lt;code&gt;Caddyfile&lt;/code&gt; to define our load balancing rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; &lt;strong&gt;Docker Compose (&lt;code&gt;docker.compose.yml&lt;/code&gt;)&lt;/strong&gt;: This file orchestrates everything, defining and running our multi-container application with a single-command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Components
&lt;/h2&gt;

&lt;p&gt;Let's break down the files in our project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src/main.go&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is a simple Go HTTP server. It listens on port &lt;code&gt;8081&lt;/code&gt; and responds to any request by printing its &lt;code&gt;hostname&lt;/code&gt;. This is our primary tool for observing the load balancing behavior.&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;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&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;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&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;ResponseWriter&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;http&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="n"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hostname&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello from %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hostname&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;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&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;ListenAndServe&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="no"&gt;nil&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;&lt;code&gt;src/Dockerfile&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This &lt;code&gt;Dockerfile&lt;/code&gt; builds our Go application into a lightweight, self-contained Docker image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.24&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; /go/src/app&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;RUN &lt;/span&gt;go mod download

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 go build &lt;span class="nt"&gt;-o&lt;/span&gt; /go/bin/app

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; golang:1.24-alpine&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /go/bin/app /&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8081&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT 8081&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This file ties everything together. We define a service for our Caddy load balancer and three worker services.&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;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.8"&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;load_balancer&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;caddy:2.10-alpine&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;load_balancer&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8082:80"&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;./caddy/Caddyfile:/etc/caddy/Caddyfile&lt;/span&gt;

  &lt;span class="na"&gt;worker_1&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="s"&gt;./src&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;worker_1&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;worker_1&lt;/span&gt;
    &lt;span class="na"&gt;expose&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;8081"&lt;/span&gt;

  &lt;span class="na"&gt;worker_2&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="s"&gt;./src&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;worker_2&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;worker_2&lt;/span&gt;
    &lt;span class="na"&gt;expose&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;8081"&lt;/span&gt;

  &lt;span class="na"&gt;worker_3&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="s"&gt;./src&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;worker_3&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;worker_3&lt;/span&gt;
    &lt;span class="na"&gt;expose&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;8081"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;caddy/Caddyfile&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is where the magic happens! The &lt;code&gt;Caddyfile&lt;/code&gt; is Caddy's configuration file. We'll define a &lt;strong&gt;reverse proxy&lt;/strong&gt; that routes to our workers. The &lt;code&gt;lb_policy&lt;/code&gt; directive is where we'll specify our load balancing algorithms.&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="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;reverse_proxy&lt;/span&gt; &lt;span class="n"&gt;worker_1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="n"&gt;worker_2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="n"&gt;worker_3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;lb_policy&lt;/span&gt; &lt;span class="n"&gt;round_robin&lt;/span&gt;
        &lt;span class="n"&gt;health_uri&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
        &lt;span class="n"&gt;health_interval&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="n"&gt;s&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;
  
  
  Experimenting with Load Balancing Algorithms
&lt;/h2&gt;

&lt;p&gt;Now that our project is set up, we can start experimenting. To run the project, simply execute docker-compose up in your terminal. You can then send requests to &lt;code&gt;http://127.0.0.1:8082&lt;/code&gt; and observe which worker responds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Round Robin (&lt;code&gt;lb_policy round_robin&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;This is the most common and simplest load balancing algorithm. Caddy distributes incoming requests to the backend servers in a sequential, rotating manner. It's a fair and predictable method, assuming all servers are equally capable of handling the load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Configure&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Modify your &lt;code&gt;caddy/Caddyfile&lt;/code&gt; to use the &lt;code&gt;round_robin&lt;/code&gt; policy.&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="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;reverse_proxy&lt;/span&gt; &lt;span class="n"&gt;worker_1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="n"&gt;worker_2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="n"&gt;worker_3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;lb_policy&lt;/span&gt; &lt;span class="n"&gt;round_robin&lt;/span&gt;
        &lt;span class="n"&gt;health_uri&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
        &lt;span class="n"&gt;health_interval&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="n"&gt;s&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;&lt;strong&gt;How to Test&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;After running &lt;code&gt;docker-compose up -d --build&lt;/code&gt;, open your terminal and send a few requests using &lt;code&gt;curl&lt;/code&gt;. You should see that Caddy distributes the traffic evenly among the three workers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:8082
&lt;span class="c"&gt;# Output: Hello from worker_1&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:8082
&lt;span class="c"&gt;# Output: Hello from worker_2&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:8082
&lt;span class="c"&gt;# Output: Hello from worker_3&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:8082
&lt;span class="c"&gt;# Output: Hello from worker_1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's test Caddy's fault tolerance. In a real-world scenario, a server might crash or become unresponsive. We'll simulate this by manually stopping one of the workers.&lt;/p&gt;

&lt;p&gt;Run the following command in your terminal to stop &lt;code&gt;worker_1&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker stop worker_1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a few moments (the health_interval you set, e.g., 3 seconds), Caddy will perform its next health check, detect that &lt;code&gt;worker_1&lt;/code&gt; is unresponsive, and automatically mark it as unhealthy.&lt;/p&gt;

&lt;p&gt;Now, send a few more requests. What do you expect to happen? With &lt;code&gt;worker_1&lt;/code&gt; down, Caddy should intelligently stop routing traffic to it and redirect all requests to the remaining healthy servers (&lt;code&gt;worker_2&lt;/code&gt; and &lt;code&gt;worker_3&lt;/code&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%2Fxgewmphcg5wdyfd9nqap.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%2Fxgewmphcg5wdyfd9nqap.gif" alt="Demo Round Robin" width="600" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Weighted Round Robin (&lt;code&gt;lb_policy weighted_round_robin&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;This algorithm is a more advanced version of Round Robin. It allows you to assign a "weight" to each backend server, which determines its share of the requests. &lt;strong&gt;Servers with a higher weight will receive more traffic than those with a lower weight&lt;/strong&gt;. &lt;strong&gt;This is ideal when you have servers with varying capacities, for example, a new, more powerful server and an older, less powerful one&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can also use this policy to gradually drain traffic from an old server or ramp up traffic to a new one during deployments, making it a very useful strategy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Configure&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;To use this policy, you need to add the weight to each server's address in the &lt;code&gt;Caddyfile&lt;/code&gt;. For our example, let's give &lt;code&gt;worker_1&lt;/code&gt; a higher weight of 3, while &lt;code&gt;worker_2&lt;/code&gt; and &lt;code&gt;worker_3&lt;/code&gt; each have a weight of 1. This means &lt;code&gt;worker_1&lt;/code&gt; should handle three out of every five requests.&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="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;reverse_proxy&lt;/span&gt;  &lt;span class="n"&gt;worker_1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="n"&gt;worker_2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="n"&gt;worker_3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;lb_policy&lt;/span&gt; &lt;span class="n"&gt;weighted_round_robin&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;health_uri&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
        &lt;span class="n"&gt;health_interval&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="n"&gt;s&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;After updating the &lt;code&gt;Caddyfile&lt;/code&gt;, make sure to reload or restart your Caddy container to apply the changes. You can do this with &lt;code&gt;docker-compose up -d --build&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Test&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Now, let's send a few requests to our load balancer and see how Caddy distributes the traffic according to the assigned weights. Send a few requests using curl and observe the responses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:8082
&lt;span class="c"&gt;# Output: Hello from worker_1&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:8082
&lt;span class="c"&gt;# Output: Hello from worker_1&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:8082
&lt;span class="c"&gt;# Output: Hello from worker_2&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:8082
&lt;span class="c"&gt;# Output: Hello from worker_3&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:8082
&lt;span class="c"&gt;# Output: Hello from worker_1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F8lf7qh9kycbisoq35l4n.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%2F8lf7qh9kycbisoq35l4n.gif" alt="Weighted Round Robin Demo" width="600" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Least Connection (&lt;code&gt;lb_policy ip_hash&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Aight, let's dive into Least Connection. Unlike Round Robin, which is a simple, sequential algorithm, Least Connection is a dynamic and more intelligent load balancing policy. It chooses the backend server with the fewest number of currently active requests. This policy is excellent for situations where your requests have a highly variable processing time.&lt;/p&gt;

&lt;p&gt;For example, if one of your servers gets a handful of complex, long-running requests while the others are handling many small, quick ones, this algorithm will automatically route new traffic to the servers that are less burdened, preventing a single server from becoming a bottleneck. If there's a tie, meaning two or more servers have the same lowest number of connections, Caddy will randomly choose one of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Configure&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Configuring this policy is simple. You just need to change the &lt;code&gt;lb_policy&lt;/code&gt; directive in your Caddyfile.&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="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;reverse_proxy&lt;/span&gt; &lt;span class="n"&gt;worker_1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="n"&gt;worker_2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="n"&gt;worker_3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;lb_policy&lt;/span&gt; &lt;span class="n"&gt;least_conn&lt;/span&gt;
        &lt;span class="n"&gt;health_uri&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
        &lt;span class="n"&gt;health_interval&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="n"&gt;s&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;After updating your &lt;code&gt;Caddyfile&lt;/code&gt;, make sure to restart your Caddy container with &lt;code&gt;docker-compose up -d --build&lt;/code&gt; to apply the changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Test&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;To demonstrate the Least Connection algorithm, you'll need to modify your Go code to simulate a long-running request. This will allow you to see how Caddy intelligently routes traffic away from the busy worker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;Update Your Go Code&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Open your &lt;code&gt;src/main.go&lt;/code&gt; file and add a new handler that will simulate a task with a significant delay. This will act as our "long-running request."&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;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&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;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&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;ResponseWriter&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;http&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="n"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hostname&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello from %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hostname&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;longHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&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;ResponseWriter&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;http&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="n"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hostname&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello! long running request finished from %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hostname&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;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&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;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/long"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;longHandler&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;ListenAndServe&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="no"&gt;nil&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;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;Rebuild and Run Docker Compose&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After updating your code, you must rebuild and run your containers to apply the changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;code&gt;Test the Scenario&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now, you can test the Least Connection algorithm using two separate terminals.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Terminal 1 (Long-Running Request)&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;Start a request to the /long endpoint. This will open a connection to one of the workers and hold it for 10 seconds. Caddy will detect that this worker has an active, ongoing connection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:8082/long
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Terminal 2 (Normal Requests)&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;Immediately after running the command in Terminal 1, switch to Terminal 2 and send several quick requests to the root endpoint (/).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:8082/
&lt;span class="c"&gt;# Output: Hello from worker_2&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:8082/
&lt;span class="c"&gt;# Output: Hello from worker_3&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:8082/
&lt;span class="c"&gt;# Output: Hello from worker_2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will observe that Caddy will not send requests to the worker that is currently busy with the long-running request. Instead, all new requests will be routed to the other two workers, demonstrating how the &lt;code&gt;least_conn&lt;/code&gt; algorithm effectively balances load dynamically. Once the long-running request is complete, that worker will once again be available to handle new requests.&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%2Ffmmaoembqb6yii7amg1n.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%2Ffmmaoembqb6yii7amg1n.gif" alt="Least Connection" width="720" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; IP Hash (&lt;code&gt;lb_policy ip_hash&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;The IP Hash load balancing algorithm is different from the previous ones because it's focused on session persistence. Instead of distributing requests based on a sequential or random order, it creates a hash from the client's IP address and uses that hash to consistently route all requests from that same client to the &lt;strong&gt;same backend server&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Configure&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Configuring the IP Hash policy is straightforward. You simply need to replace the lb_policy directive in your Caddyfile with &lt;code&gt;ip_hash&lt;/code&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="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;reverse_proxy&lt;/span&gt;  &lt;span class="n"&gt;worker_1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="n"&gt;worker_2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="n"&gt;worker_3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;lb_policy&lt;/span&gt; &lt;span class="n"&gt;ip_hash&lt;/span&gt;
        &lt;span class="n"&gt;health_uri&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
        &lt;span class="n"&gt;health_interval&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="n"&gt;s&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;After updating your Caddyfile, make sure to restart your Caddy container with &lt;code&gt;docker-compose up -d --build&lt;/code&gt; to apply the changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Test&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;To test this algorithm, you'll need to send requests from different "clients" (i.e., different IP addresses) and observe where they are routed. The easiest way to simulate this is by sending requests from your local machine and then using a proxy or a different network to see if the requests are routed to a different server.&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%2F88d0fgizt4w279r8c6d0.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%2F88d0fgizt4w279r8c6d0.gif" alt="IP Hash Demo" width="720" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No matter how many times you run &lt;code&gt;curl&lt;/code&gt; from the same machine, the requests will always be routed to the same worker. &lt;strong&gt;This is because Caddy is hashing your local IP address (127.0.0.1 or the container's internal IP) and consistently mapping it to that specific worker&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This demonstrates how IP Hash ensures &lt;strong&gt;session stickiness&lt;/strong&gt; without needing to share session data across all servers. It’s a powerful tool for maintaining a consistent user experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.&lt;/strong&gt; Random (&lt;code&gt;lb_policy random&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;The Random load balancing policy is the simplest and most unpredictable of all the algorithms. As its name suggests, it selects a backend server at random for each new request. There is no sequential pattern or special logic; every request has an equal chance of being routed to any of the available servers.&lt;/p&gt;

&lt;p&gt;While it may seem less sophisticated than other algorithms, the Random policy is surprisingly effective in many scenarios. It's fast, has a very low overhead, and can be a great choice for distributing traffic evenly across a large pool of homogenous servers. It naturally avoids the &lt;strong&gt;"thundering herd"&lt;/strong&gt; problem that can sometimes occur with Round Robin on &lt;strong&gt;first-come-first-served&lt;/strong&gt; requests, as it prevents all clients from hitting the same server at the same time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Configure&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Configuring this policy is the easiest. Simply replace the &lt;code&gt;lb_policy&lt;/code&gt; directive in your Caddyfile with &lt;code&gt;random&lt;/code&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="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;reverse_proxy&lt;/span&gt;  &lt;span class="n"&gt;worker_1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="n"&gt;worker_2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="n"&gt;worker_3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8081&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;lb_policy&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
        &lt;span class="n"&gt;health_uri&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
        &lt;span class="n"&gt;health_interval&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="n"&gt;s&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;After updating your Caddyfile, make sure to restart your Caddy container with &lt;code&gt;docker-compose up -d --build&lt;/code&gt; to apply the changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Test&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;To test the Random policy, send a series of quick requests and observe the output. Unlike the predictable pattern of Round Robin or the consistent output of IP Hash, the responses will come from different workers in an unpredictable order.&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://127.0.0.1:8082
&lt;span class="c"&gt;# Output: Hello from worker_3&lt;/span&gt;

curl http://127.0.0.1:8082
&lt;span class="c"&gt;# Output: Hello from worker_1&lt;/span&gt;

curl http://127.0.0.1:8082
&lt;span class="c"&gt;# Output: Hello from worker_2&lt;/span&gt;

curl http://127.0.0.1:8082
&lt;span class="c"&gt;# Output: Hello from worker_1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F8lug035saq9bhaue3y7r.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%2F8lug035saq9bhaue3y7r.gif" alt="Demo Random" width="720" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Throughout this post, we've explored the core capabilities of Caddy as a powerful web server and a flexible load balancer. Using a simple Docker setup, we were able to quickly demonstrate five different load balancing algorithms, each with its own unique advantages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;strong&gt;Round Robin&lt;/strong&gt;: The classic, simple approach for evenly distributing traffic in a predictable sequence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;strong&gt;Weighted Round Robin&lt;/strong&gt;: A smarter version of Round Robin that allows you to prioritize traffic to more powerful servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;strong&gt;Least Connection&lt;/strong&gt;: A dynamic algorithm that routes traffic based on real-time load, preventing a single server from becoming a bottleneck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;strong&gt;IP Hash&lt;/strong&gt;: The ideal choice for session stickiness, ensuring a consistent user experience by always routing a client to the same backend server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;strong&gt;Random&lt;/strong&gt;: A straightforward and fast algorithm for scattering traffic across servers, effective for a large pool of homogenous workers.&lt;/p&gt;

&lt;p&gt;This hands-on experience proved that Caddy is not just a simple web server but a robust tool for building scalable and reliable applications. Caddy's elegant syntax and powerful features make it an excellent choice for anyone looking to simplify their server configurations without sacrificing control or performance. Whether you're a seasoned developer or just starting, Caddy offers a smooth and intuitive experience that can handle everything from a single website to a complex, distributed application.&lt;/p&gt;

&lt;p&gt;References:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;a href="https://caddyserver.com/docs/caddyfile/directives/reverse_proxy" rel="noopener noreferrer"&gt;Caddy Documentation: Reverse Proxy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&lt;/strong&gt; &lt;a href="https://caddy.community" rel="noopener noreferrer"&gt;Caddy Community Wiki&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>go</category>
      <category>tutorial</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>Dockerizing Go API and Caddy</title>
      <dc:creator>Daniel Pepuho</dc:creator>
      <pubDate>Mon, 18 Aug 2025 02:24:50 +0000</pubDate>
      <link>https://dev.to/danielcristho/dockerizing-go-api-and-caddy-ge4</link>
      <guid>https://dev.to/danielcristho/dockerizing-go-api-and-caddy-ge4</guid>
      <description>&lt;p&gt;Hello! In this post I'll walk through how to use Caddy as a reverse proxy and Docker for containerization to deploy a simple Go API. This method offers a quick and modern to getting your Go API up and running.&lt;/p&gt;

&lt;p&gt;Before we dive into the deployment steps, let's briefly discuss why Docker and Caddy are an excellent combination.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt; is a containerization platform that packages your app and all its dependecies into an isolated unit. This guarantess that your app runs consistenly everywhere, eliminating the classic &lt;em&gt;"&lt;/em&gt;&lt;em&gt;it works on my machine&lt;/em&gt;&lt;em&gt;"&lt;/em&gt; problem.&lt;/li&gt;
&lt;/ul&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%2Fres.cloudinary.com%2Fdiunivf9n%2Fimage%2Fupload%2Fv1755401332%2Fdocker-meme_n1ca7c.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%2Fres.cloudinary.com%2Fdiunivf9n%2Fimage%2Fupload%2Fv1755401332%2Fdocker-meme_n1ca7c.png" alt="it works on my machine meme" width="676" height="930"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dockerile&lt;/strong&gt; is the blueprint that defines how your Docker image is built. It specifies the base image, the steps to compile your application, and how the container should run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker Compose&lt;/strong&gt; is a tool for defining and running multi-container Docker applications. Instead of starting each container manually, we can describe the entire stack in a single YAML file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caddy&lt;/strong&gt;  is a modern reverse proxy and web server built using Go. Caddy is renowned for its ease of use, especially its &lt;strong&gt;automatic HTTPS&lt;/strong&gt; feature. Its simple configuration makes it an ideal choice for serving our API.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;Before we start, you'll need to install Go, Docker and Docker Compose on your system.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Go
&lt;/h3&gt;

&lt;p&gt;Make sure Go is installed. You can check your version with:&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="err"&gt;$&lt;/span&gt; &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="n"&gt;go1&lt;/span&gt;&lt;span class="m"&gt;.24.0&lt;/span&gt; &lt;span class="n"&gt;linux&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;amd6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it's not installed, you can download it from &lt;a href="https://go.dev/dl" rel="noopener noreferrer"&gt;the official site&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Docker &amp;amp; Docker Compose
&lt;/h3&gt;

&lt;p&gt;Make sure Docker &amp;amp; Docker Compose are installed. You can check your docker version with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker &lt;span class="nt"&gt;--version&lt;/span&gt;

Docker version 27.5.1, build 9f9e405
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker compose version

Docker Compose version v2.3.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the official guides to install Docker and Docker Compose for your operating system if you haven't installed them before. &lt;a href="https://docs.docker.com/engine/install" rel="noopener noreferrer"&gt;The official site&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Project
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Create and Run a Simple Go API
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;First, create a new directory for the project and initialize a Go module:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;go-api &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;go-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="n"&gt;example&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="k"&gt;go&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This command creates a Go module (go.mod) named example.com/go-api, which helps manage dependencies and makes the project reproducible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Next, create a new file &lt;code&gt;main.go&lt;/code&gt; and define a simple HTTP server using Chi as the router.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server exposes two routes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Root path &lt;code&gt;/&lt;/code&gt; -&amp;gt; return an ASCII banner generated with the &lt;code&gt;go-figure&lt;/code&gt; package.
&lt;/li&gt;
&lt;/ul&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;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&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;ResponseWriter&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;http&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="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;asciiArt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewFigure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Go API - Caddy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asciiArt&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;ul&gt;
&lt;li&gt;
&lt;code&gt;/api/hello&lt;/code&gt; -&amp;gt; returns a simple JSON response.
&lt;/li&gt;
&lt;/ul&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;apiHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&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;ResponseWriter&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;http&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="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;`{"message":"Hello, World!"}`&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;ul&gt;
&lt;li&gt;In the main function, we:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Initialize the Chi router and register both routes.&lt;/li&gt;
&lt;li&gt;Configure an HTTP server to listen on port 8081.&lt;/li&gt;
&lt;li&gt;Run the server in a goroutine and listen for shutdown signals (SIGINT, SIGTERM).&lt;/li&gt;
&lt;li&gt;Gracefully shut down the server with a 5-second timeout when a termination signal is received.
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&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="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;chi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// API Route&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;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&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;"/api/hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;srv&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&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;Server&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;":8081"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGTERM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Server started at :8081"&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="n"&gt;srv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&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="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrServerClosed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Could not listen on port 8081: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Shutting down server..."&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;cancel&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;WithTimeout&lt;/span&gt;&lt;span class="p"&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="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&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;cancel&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="n"&gt;srv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shutdown&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;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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Server shutdown failed: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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;Here is the full code:&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;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"os/signal"&lt;/span&gt;
    &lt;span class="s"&gt;"syscall"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/common-nighthawk/go-figure"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/go-chi/chi/v5"&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;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&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;ResponseWriter&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;http&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="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;asciiArt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewFigure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Go API - Caddy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asciiArt&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;apiHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&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;ResponseWriter&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;http&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="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;`{"message":"Hello, World!"}`&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;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;chi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// API Route&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;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&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;"/api/hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;srv&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&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;Server&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;":8081"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGTERM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Server started at :8081"&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="n"&gt;srv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&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="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrServerClosed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Could not listen on port 8081: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Shutting down server..."&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;cancel&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;WithTimeout&lt;/span&gt;&lt;span class="p"&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="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&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;cancel&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="n"&gt;srv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shutdown&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;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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Server shutdown failed: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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;Then, try run the main.go using &lt;code&gt;go run&lt;/code&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="err"&gt;$&lt;/span&gt; &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, you should see this message in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server started at :8081
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, you can test the API using &lt;code&gt;curl&lt;/code&gt; or access from the browser on &lt;code&gt;http://127.0.0.1:8081&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl 127.0.0.1:8081

   ____                _      ____    ___                ____               _       _
  / ___|   ___        / &lt;span class="se"&gt;\ &lt;/span&gt;   |  _ &lt;span class="se"&gt;\ &lt;/span&gt; |_ _|              / ___|   __ _    __| |   __| |  _   _
 | |  _   / _ &lt;span class="se"&gt;\ &lt;/span&gt;     / _ &lt;span class="se"&gt;\ &lt;/span&gt;  | |_&lt;span class="o"&gt;)&lt;/span&gt; |  | |     _____    | |      / _&lt;span class="sb"&gt;`&lt;/span&gt; |  / _&lt;span class="sb"&gt;`&lt;/span&gt; |  / _&lt;span class="sb"&gt;`&lt;/span&gt; | | | | |
 | |_| | | &lt;span class="o"&gt;(&lt;/span&gt;_&lt;span class="o"&gt;)&lt;/span&gt; |    / ___ &lt;span class="se"&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="se"&gt;\_&lt;/span&gt;___|  &lt;span class="se"&gt;\_&lt;/span&gt;__/    /_/   &lt;span class="se"&gt;\_\ &lt;/span&gt;|_|     |___|              &lt;span class="se"&gt;\_&lt;/span&gt;___|  &lt;span class="se"&gt;\_&lt;/span&gt;_,_|  &lt;span class="se"&gt;\_&lt;/span&gt;_,_|  &lt;span class="se"&gt;\_&lt;/span&gt;_,_|  &lt;span class="se"&gt;\_&lt;/span&gt;_, |
                                                                                        |___/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl 127.0.0.1:8081/api/hello

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&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;h3&gt;
  
  
  Step 2: The Dockerfile
&lt;/h3&gt;

&lt;p&gt;We'll use a multi-stage build to create a minimal and secure Docker image. This process compiles our Go application in one stage and then copies only the final binary to a much smaller final image. This keeps our final image size low.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;Dockerfile&lt;/code&gt; in your project directory and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# First stage: builder&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;golang:1.24&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; /go/src/app&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;RUN &lt;/span&gt;go mod download

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 go build &lt;span class="nt"&gt;-o&lt;/span&gt; /go/bin/app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In the builder stage, we use the official Go image to compile our code.&lt;/li&gt;
&lt;li&gt;We copy the entire project into the container and run go mod download to fetch dependencies.&lt;/li&gt;
&lt;li&gt;Then we build the binary with CGO_ENABLED=0 to ensure it’s statically compiled and portable. The binary is placed in /go/bin/app.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Second stage: final image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; golang:1.24-alpine&lt;/span&gt;

&lt;span class="c"&gt;# Copy only the compiled binary from builder stage&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /go/bin/app /&lt;/span&gt;

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

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT 8081&lt;/span&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In the final stage, only the compiled binary is copied over from the builder stage. This keeps the image small because source code, dependencies, and build tools are excluded.&lt;/li&gt;
&lt;li&gt;We expose port 8081 so Docker knows which port the app listens on.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CMD ["/app"]&lt;/code&gt; runs the binary as the container’s main process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the full code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.24&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; /go/src/app&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;RUN &lt;/span&gt;go mod download

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 go build &lt;span class="nt"&gt;-o&lt;/span&gt; /go/bin/app

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; golang:1.24-alpine&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /go/bin/app /&lt;/span&gt;

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

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT 8081&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Configure Caddy
&lt;/h3&gt;

&lt;p&gt;Caddy will act as a reverse proxy that forwards incoming requests to the Go API container. This allows us to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tells Caddy to listen on port 80 (HTTP).&lt;/li&gt;
&lt;li&gt;Forwards requests to the Go API container, using the Docker service name go-api and port 8081.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a file named &lt;code&gt;Caddyfile&lt;/code&gt; in your project directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;:80 &lt;span class="o"&gt;{&lt;/span&gt;
    reverse_proxy go-api:8081
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 If you later have a domain (e.g., api.example.com), you can replace &lt;code&gt;:80&lt;/code&gt; with your domain&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;api.example.com &lt;span class="o"&gt;{&lt;/span&gt;
    reverse_proxy go-api:8081
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Configure Docker Compose
&lt;/h3&gt;

&lt;p&gt;Create a file named &lt;code&gt;docker-compose.yml&lt;/code&gt; in your project directory:&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;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.8"&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;go-api&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="s"&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;expose&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;8081"&lt;/span&gt;                 &lt;span class="c1"&gt;# Expose port internally (only visible to other services in this network)&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;caddy&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;caddy:2.10-alpine&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;caddy&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8082:80"&lt;/span&gt;              &lt;span class="c1"&gt;# Map host port 8082 -&amp;gt; container port 80&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;./Caddyfile:/etc/caddy/Caddyfile&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&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="s"&gt;go-api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Explanation
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;go-api&lt;/code&gt; service&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Built from your local Dockerfile.&lt;/li&gt;
&lt;li&gt;Uses expose instead of ports → this means the Go API is reachable inside the Docker network but not directly exposed to the host machine.&lt;/li&gt;
&lt;li&gt;The Go API will listen on :8081, but only Caddy can access it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;caddy&lt;/code&gt; service&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses the official lightweight &lt;a href="https://hub.docker.com/_/caddy" rel="noopener noreferrer"&gt;caddy:2.10-alpine&lt;/a&gt; image.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ports: "8082:80"&lt;/code&gt; -&amp;gt; exposes port 80 from the container to port 8082 on your host machine.
So when you open &lt;code&gt;http://127.0.0.1:8082&lt;/code&gt;, requests are routed through Caddy.&lt;/li&gt;
&lt;li&gt;The Caddyfile is mounted so you can configure reverse proxy behavior.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;depends_on&lt;/code&gt; ensures Caddy waits for the Go API container to start.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 5: Test the Setup
&lt;/h3&gt;

&lt;p&gt;Once all files are ready (&lt;code&gt;main.go&lt;/code&gt;, &lt;code&gt;Dockerfile&lt;/code&gt;, &lt;code&gt;Caddyfile&lt;/code&gt;, &lt;code&gt;docker-compose.yml&lt;/code&gt;), run the stack using the &lt;code&gt;docker compose up&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;&lt;span class="nb"&gt;.&lt;/span&gt;
├── Caddyfile
├── docker-compose.yml
├── Dockerfile
├── go.mod
├── go.sum
└── main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure the containers are running using &lt;code&gt;docker ps&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;&lt;span class="nv"&gt;$ &lt;/span&gt;docker ps

CONTAINER ID   IMAGE                          COMMAND                  CREATED         STATUS         PORTS
                                                      NAMES
6b6b35487d77   caddy:2.10-alpine              &lt;span class="s2"&gt;"caddy run --config …"&lt;/span&gt;   9 minutes ago   Up 9 minutes   443/tcp, 2019/tcp, 443/udp, 0.0.0.0:8082-&amp;gt;80/tcp, &lt;span class="o"&gt;[&lt;/span&gt;::]:8082-&amp;gt;80/tcp   caddy
20cbb2209fe7   docker-go-api-caddy_go-api     &lt;span class="s2"&gt;"/app"&lt;/span&gt;                   9 minutes ago   Up 9 minutes   0.0.0.0:32770-&amp;gt;8081/tcp, &lt;span class="o"&gt;[&lt;/span&gt;::]:32770-&amp;gt;8081/tcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, test the endpoints through Caddy (reverse proxy):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:8082
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output: the ASCII art rendered by &lt;code&gt;go-figure&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:8082/api/hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&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="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&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;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this guide, we successfully deployed a simple Go API containerized approach. By leveraging Docker, we created an isolated and reproducible environment for our application. We used a multi-stage build in our Dockerfile to keep the final image lightweight and secure, and Docker Compose to easily manage both our Go API and Caddy services with a single command.&lt;/p&gt;

&lt;p&gt;Feel free to explore further by adding more features to your API or by setting up a domain with Caddy’s automatic HTTPS. Happy deployment!🚀&lt;/p&gt;

&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com" rel="noopener noreferrer"&gt;Docker Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/compose" rel="noopener noreferrer"&gt;Docker Compose Documenation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caddyserver.com/docs" rel="noopener noreferrer"&gt;Caddy Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>go</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Deploying a Simple Flask API Using Gunicorn, Supervisor &amp; Nginx</title>
      <dc:creator>Daniel Pepuho</dc:creator>
      <pubDate>Wed, 30 Apr 2025 15:21:10 +0000</pubDate>
      <link>https://dev.to/danielcristho/deploying-a-simple-flask-api-using-gunicorn-supervisor-nginx-eki</link>
      <guid>https://dev.to/danielcristho/deploying-a-simple-flask-api-using-gunicorn-supervisor-nginx-eki</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Hi there! Flask is great for building APIs quickly. But turning your local project into a publicly accessible web service involves a few extra steps that aren’t always obvious.&lt;/p&gt;

&lt;p&gt;In this guide, I'll show you how to deploy a Flask API using Gunicorn as the WSGI server, Supervisor to manage the process, and Nginx as a reverse proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://flask.palletsprojects.com/en/stable" rel="noopener noreferrer"&gt;&lt;strong&gt;Flask&lt;/strong&gt;&lt;/a&gt;: The Python microframework we’ll use to build the API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://gunicorn.org" rel="noopener noreferrer"&gt;&lt;strong&gt;Gunicorn&lt;/strong&gt;&lt;/a&gt;: A Python WSGI HTTP server for running Flask in production.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://supervisord.org" rel="noopener noreferrer"&gt;&lt;strong&gt;Supervisor&lt;/strong&gt;&lt;/a&gt;: A process control system to ensure the Gunicorn server stays alive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://nginx.org" rel="noopener noreferrer"&gt;&lt;strong&gt;Nginx&lt;/strong&gt;&lt;/a&gt;: A reverse proxy to handle client requests and route them to Gunicorn.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Flask API Deployment Flow
&lt;/h3&gt;

&lt;p&gt;The diagram below illustrates the flow of a request and response when using Flask, Gunicorn, Supervisor, and Nginx.&lt;/p&gt;

&lt;p&gt;When a user sends an HTTP request, it first reaches the Nginx reverse proxy. Nginx forwards the request to Gunicorn, which serves the Flask application via the WSGI protocol. Supervisor ensures that Gunicorn keeps running and automatically restarts it if needed. The response follows the same path back to the user.&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%2Fres.cloudinary.com%2Fdiunivf9n%2Fimage%2Fupload%2Fv1746028839%2Fflask-api-flow_ouvbts.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%2Fres.cloudinary.com%2Fdiunivf9n%2Fimage%2Fupload%2Fv1746028839%2Fflask-api-flow_ouvbts.png" alt="Deployment Flow" width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;Before starting, make sure you have the following installed on your system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3 and Virtualenv&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check if Python is installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python3 &lt;span class="nt"&gt;--version&lt;/span&gt;

Python 3.10.14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If not installed, install it:&lt;/p&gt;

&lt;p&gt;Ubuntu/Debian:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;python3 python3-venv &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CentOS/RHEL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;python3 python3-venv &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Homebrew (macOS):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Nginx&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ubuntu/Debian:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;nginx &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CentOS/RHEL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;nginx &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Homebrew (macOS):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation, check if Nginx is running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it’s not running, start and enable it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start nginx
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Supervisor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ubuntu/Debian:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;supervisor &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CentOS/RHEL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;supervisor &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Homebrew (macOS):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;supervisor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation, check if Supervisor is running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status supervisor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it’s not running, start and enable it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start supervisor
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;supervisor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting Up the Flask Project
&lt;/h2&gt;

&lt;p&gt;First, create a project directory and set up a virtual environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;flask-api &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;flask-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the virtual environment activated, install Flask and Gunicorn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;flask gunicorn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can verify the installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;flask &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;gunicorn &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, you need to create a file called app.py inside your project directory. You can use any text editor you prefer, such as nano, vim, or others:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;vim app.py

from flask import Flask

app &lt;span class="o"&gt;=&lt;/span&gt; Flask&lt;span class="o"&gt;(&lt;/span&gt;__name__&lt;span class="o"&gt;)&lt;/span&gt;

@app.route&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/api/hello"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
def hello&lt;span class="o"&gt;()&lt;/span&gt;:
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;: &lt;span class="s2"&gt;"Hello from Flask API!"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, try to run your Flask app using &lt;code&gt;Gunicorn&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;&lt;span class="nv"&gt;$ &lt;/span&gt;gunicorn app:app

&lt;span class="o"&gt;[&lt;/span&gt;2025-04-30 20:37:49 +0700] &lt;span class="o"&gt;[&lt;/span&gt;1085004] &lt;span class="o"&gt;[&lt;/span&gt;INFO] Starting gunicorn 23.0.0
&lt;span class="o"&gt;[&lt;/span&gt;2025-04-30 20:37:49 +0700] &lt;span class="o"&gt;[&lt;/span&gt;1085004] &lt;span class="o"&gt;[&lt;/span&gt;INFO] Listening at: http://127.0.0.1:8000 &lt;span class="o"&gt;(&lt;/span&gt;1085004&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;2025-04-30 20:37:49 +0700] &lt;span class="o"&gt;[&lt;/span&gt;1085004] &lt;span class="o"&gt;[&lt;/span&gt;INFO] Using worker: &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;2025-04-30 20:37:49 +0700] &lt;span class="o"&gt;[&lt;/span&gt;1085005] &lt;span class="o"&gt;[&lt;/span&gt;INFO] Booting worker with pid: 1085005
&lt;span class="o"&gt;[&lt;/span&gt;2025-04-30 20:38:58 +0700] &lt;span class="o"&gt;[&lt;/span&gt;1085004] &lt;span class="o"&gt;[&lt;/span&gt;INFO] Handling signal: winch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To try your app, just open another terminal session or window. You can use a tool like &lt;code&gt;curl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:8000/api/hello

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;:&lt;span class="s2"&gt;"Hello from Flask API!"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running with Multiple Workers
&lt;/h3&gt;

&lt;p&gt;You can run Gunicorn with multiple worker processes using the -w option. For example, to run your app with 3 workers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gunicorn &lt;span class="nt"&gt;-w&lt;/span&gt; 3 app:app

&lt;span class="o"&gt;[&lt;/span&gt;2025-04-30 20:49:13 +0700] &lt;span class="o"&gt;[&lt;/span&gt;1085759] &lt;span class="o"&gt;[&lt;/span&gt;INFO] Starting gunicorn 23.0.0
&lt;span class="o"&gt;[&lt;/span&gt;2025-04-30 20:49:13 +0700] &lt;span class="o"&gt;[&lt;/span&gt;1085759] &lt;span class="o"&gt;[&lt;/span&gt;INFO] Listening at: http://127.0.0.1:8000 &lt;span class="o"&gt;(&lt;/span&gt;1085759&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;2025-04-30 20:49:13 +0700] &lt;span class="o"&gt;[&lt;/span&gt;1085759] &lt;span class="o"&gt;[&lt;/span&gt;INFO] Using worker: &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;2025-04-30 20:49:13 +0700] &lt;span class="o"&gt;[&lt;/span&gt;1085760] &lt;span class="o"&gt;[&lt;/span&gt;INFO] Booting worker with pid: 1085759
&lt;span class="o"&gt;[&lt;/span&gt;2025-04-30 20:49:13 +0700] &lt;span class="o"&gt;[&lt;/span&gt;1085761] &lt;span class="o"&gt;[&lt;/span&gt;INFO] Booting worker with pid: 1085760
&lt;span class="o"&gt;[&lt;/span&gt;2025-04-30 20:49:13 +0700] &lt;span class="o"&gt;[&lt;/span&gt;1085762] &lt;span class="o"&gt;[&lt;/span&gt;INFO] Booting worker with pid: 1085761
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To confirm that Gunicorn is running with multiple workers, you can use tools like top or htop.&lt;/p&gt;

&lt;p&gt;Install htop (optional but nicer to read):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;htop &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;htop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fli6icrynwizksqga238x.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%2Fli6icrynwizksqga238x.png" alt="Gunicorn Process" width="800" height="54"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To bind it to a different port (e.g., 8081) and listen on all interfaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gunicorn &lt;span class="nt"&gt;-b&lt;/span&gt; 0.0.0.0:8081 app:app

&lt;span class="o"&gt;[&lt;/span&gt;2025-04-30 21:14:29 +0700] &lt;span class="o"&gt;[&lt;/span&gt;1085847] &lt;span class="o"&gt;[&lt;/span&gt;INFO] Starting gunicorn 23.0.0
&lt;span class="o"&gt;[&lt;/span&gt;2025-04-30 21:14:29 +0700] &lt;span class="o"&gt;[&lt;/span&gt;1085847] &lt;span class="o"&gt;[&lt;/span&gt;INFO] Listening at: http://0.0.0.0:8081 &lt;span class="o"&gt;(&lt;/span&gt;1085847&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;2025-04-30 21:14:29 +0700] &lt;span class="o"&gt;[&lt;/span&gt;1085847] &lt;span class="o"&gt;[&lt;/span&gt;INFO] Using worker: &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;2025-04-30 21:14:29 +0700] &lt;span class="o"&gt;[&lt;/span&gt;1085848] &lt;span class="o"&gt;[&lt;/span&gt;INFO] Booting worker with pid: 1085848
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding Supervisor and Nginx Configuration
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Supervisor Setup
&lt;/h4&gt;

&lt;p&gt;To make sure Gunicorn runs in the background and restarts automatically if it crashes, you'll want to use Supervisor. Here's how to set it up:&lt;/p&gt;

&lt;p&gt;Create a configuration file for your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vim /etc/supervisor/conf.d/flask-api.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Insert this configuration (adjust paths as needed):&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="o"&gt;[&lt;/span&gt;program:flask-api]
&lt;span class="nv"&gt;directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/youruser/flask-api
&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/youruser/flask-api/venv/bin/gunicorn &lt;span class="nt"&gt;-w&lt;/span&gt; 3 &lt;span class="nt"&gt;-b&lt;/span&gt; 127.0.0.1:8000 app:app
&lt;span class="nv"&gt;autostart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;autorestart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;www-data
&lt;span class="nv"&gt;stdout_logfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log/flask-api.out.log
&lt;span class="nv"&gt;stderr_logfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log/flask-api.err.log
&lt;span class="nv"&gt;environment&lt;/span&gt;&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;"/home/youruser/flask-api/venv/bin"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;directory=/home/youruser/flask-api → The working directory where your Flask project is located.&lt;/p&gt;

&lt;p&gt;command=/home/youruser/flask-api/venv/bin/gunicorn -w 3 -b 127.0.0.1:8000 app:app → Runs the Gunicorn server with 3 workers, binding to localhost on port 8000.&lt;/p&gt;

&lt;p&gt;autostart=true → Automatically starts the app when Supervisor starts (e.g., on boot).&lt;/p&gt;

&lt;p&gt;autorestart=true → Restarts the app automatically if it crashes.&lt;/p&gt;

&lt;p&gt;user=www-data → Runs the process as the www-data user (you can change this to your own user if needed).&lt;/p&gt;

&lt;p&gt;stdout_logfile=/var/log/api/flask-api.out.log → File where standard output (including errors) is logged.&lt;/p&gt;

&lt;p&gt;stderr_logfile=/var/log/api/flask-api.err.log → (Optional if using redirect_stderr) File for capturing standard error output.&lt;/p&gt;

&lt;p&gt;environment=PATH="..." → Ensures Supervisor uses the correct Python virtual environment for Gunicorn.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then reload Supervisor to pick up the new config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;supervisorctl reread
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;supervisorctl update
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;supervisorctl status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the API is not running, check the logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /var/log/flask-api.out.log
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /var/log/flask-api.err.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use Supervisor’s built-in log viewer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; flask-api

&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Press Ctrl-C to &lt;span class="nb"&gt;exit&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;==&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Nginx Configuration
&lt;/h4&gt;

&lt;p&gt;Now that Gunicorn is running your Flask app on port 8000, you’ll want Nginx to act as a reverse proxy.&lt;/p&gt;

&lt;p&gt;Create a new Nginx config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vim /etc/nginx/sites-available/flask-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;server &lt;span class="o"&gt;{&lt;/span&gt;
    listen 80&lt;span class="p"&gt;;&lt;/span&gt;
    server_name _&lt;span class="p"&gt;;&lt;/span&gt;

    location / &lt;span class="o"&gt;{&lt;/span&gt;
        proxy_pass http://127.0.0.1:8000&lt;span class="p"&gt;;&lt;/span&gt;
        proxy_set_header Host &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        proxy_set_header X-Real-IP &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        proxy_set_header X-Forwarded-For &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        proxy_set_header X-Forwarded-Proto &lt;span class="nv"&gt;$scheme&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable the site and test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/flask-api /etc/nginx/sites-enabled
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, ff everything is set up correctly, you should now be able to access your Flask API at &lt;a href="http://YOUR_SERVER_IP/api/hello" rel="noopener noreferrer"&gt;http://YOUR_SERVER_IP/api/hello&lt;/a&gt; if you're using a public server or VPS.&lt;/p&gt;

&lt;h4&gt;
  
  
  Optional: Project Structure &amp;amp; Requirements
&lt;/h4&gt;

&lt;p&gt;To easily reinstall dependencies later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pip freeze &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, it’s better to specify only the packages you need manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vim requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flask
gunicorn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To install all requirements, just run &lt;code&gt;pip install&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your project structure 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;flask-api/
├── app.py
├── venv/
└── requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Common Issues
&lt;/h3&gt;

&lt;p&gt;Here are a few quick troubleshooting tips:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;502 Bad Gateway: Usually means Gunicorn isn't running or the Nginx config has the wrong port.&lt;/p&gt;

&lt;p&gt;Supervisor status shows STOPPED: Check your config file paths and the logs:&lt;br&gt;
&lt;code&gt;sudo tail -f /var/log/flask-api.err.log&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Permission errors: Ensure all paths used by Supervisor and Gunicorn are accessible by the appropriate user.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;In this guide, we deployed a Flask API using Gunicorn as the WSGI server, Supervisor to keep the app running reliably, and Nginx as a reverse proxy to handle incoming requests. With this setup, your Flask app is ready to serve real traffic efficiently and automatically recover from crashes. Thanks for reading — and good luck with your deployment! 🚀&lt;/p&gt;

&lt;p&gt;Project Reference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/ymedialabs-innovation/deploy-flask-app-with-nginx-using-gunicorn-and-supervisor-d7a93aa07c18" rel="noopener noreferrer"&gt;Rahul Nayak: Deploy flask app with nginx using gunicorn and supervisor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>linux</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Deploying a Simple Go API with Supervisor and Nginx</title>
      <dc:creator>Daniel Pepuho</dc:creator>
      <pubDate>Thu, 20 Mar 2025 06:02:30 +0000</pubDate>
      <link>https://dev.to/danielcristho/deploying-a-simple-go-api-with-supervisor-and-nginx-fh5</link>
      <guid>https://dev.to/danielcristho/deploying-a-simple-go-api-with-supervisor-and-nginx-fh5</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Hi! In this post, I'll show you how to deploy a simple Go API using Supervisor to manage the process and Nginx as a web server to serve it.&lt;/p&gt;

&lt;p&gt;Before we dive into the deployment steps, let's briefly discuss why we're using Supervisor and Nginx.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Supervisor is a process control system that helps manage and monitor applications running in the background. It ensures that your Go API stays up and automatically restarts it if it crashes. &lt;a href="https://supervisord.org/introduction.html" rel="noopener noreferrer"&gt;See the full documentation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Nginx is a high-performance web server that can also function as a reverse proxy, making it ideal for serving our Go API to the internet. &lt;a href="https://nginx.org/en" rel="noopener noreferrer"&gt;See the full documentation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🤔 Why Choose Supervisor Over Other Options?
&lt;/h3&gt;

&lt;p&gt;You might wonder why we use Supervisor instead of alternatives like &lt;a href="https://systemd.io" rel="noopener noreferrer"&gt;Systemd&lt;/a&gt;, &lt;a href="https://pm2.keymetrics.io" rel="noopener noreferrer"&gt;PM2&lt;/a&gt;, or containerized solutions like &lt;a href="https://www.docker.com/get-started" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;. Here’s a quick comparison:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tools&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Supervisor&lt;/td&gt;
&lt;td&gt;Simple setup, great for managing multiple processes, easy log management&lt;/td&gt;
&lt;td&gt;Requires manual config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Systemd&lt;/td&gt;
&lt;td&gt;Native to Linux, faster startup&lt;/td&gt;
&lt;td&gt;More complex setup, harder to debug&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PM2&lt;/td&gt;
&lt;td&gt;Built for Node.js, supports process monitoring&lt;/td&gt;
&lt;td&gt;Not ideal for Go applications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;Isolated environment, easy deployment, scalable&lt;/td&gt;
&lt;td&gt;More setup overhead, requires container knowledge&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  When Should You Use Supervisor?
&lt;/h4&gt;

&lt;p&gt;Use Supervisor when you want a simple, non-containerized way to manage a Go service, with features like auto-restart and log management, without dealing with systemd’s complexity or Docker’s extra overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup and Run a Simple Go API
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;p&gt;Before starting, make sure you have the following installed on your system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Go&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go version

go version go1.24.0 linux/amd64
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;If not installed, download it from &lt;a href="https://go.dev/dl" rel="noopener noreferrer"&gt;the official site&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Supervisor&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ubuntu/Debian&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;supervisor &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CentOS/RHEL&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;supervisor &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Homebrew (macOS)&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;supervisor
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;After installation, check if Supervisor is running:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status supervisor
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;If it’s not running, start and enable it:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start supervisor
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;supervisor
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Nginx&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ubuntu/Debian&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;nginx &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CentOS/RHEL&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;nginx &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Homebrew (macOS)&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;After installation, check if Nginx is running:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status nginx
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;If it’s not running, start and enable it:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start nginx
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Initialize a New Go Project
&lt;/h3&gt;

&lt;p&gt;First, create a new directory for the project and initialize a Go module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /var/www/
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;go-api &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;go-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go mod init example.com/go-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command creates a Go module named &lt;code&gt;example.com/go-api&lt;/code&gt;, which helps manage dependencies.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create a Simple API
&lt;/h4&gt;

&lt;p&gt;Now, create a new file &lt;code&gt;main.go&lt;/code&gt; and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;vim main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"fmt"&lt;/span&gt;
        &lt;span class="s"&gt;"net/http"&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;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&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;ResponseWriter&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;http&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="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Simple Go API"&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;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Server started at :8080"&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;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&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;Compile and run the Go server:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;If successful, you should see this message 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;Server started at :8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now test the API using &lt;code&gt;curl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl localhost:8080


Simple Go API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Create a Simple API with ASCII Text Response (Optional)
&lt;/h4&gt;

&lt;p&gt;First, install the go-figure package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go get github.com/common-nighthawk/go-figure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, modify &lt;code&gt;main.go&lt;/code&gt; to generate an ASCII text response dynamically:&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;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/common-nighthawk/go-figure"&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;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&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;ResponseWriter&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;http&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="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;asciiArt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewFigure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Simple Go API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asciiArt&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;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Server started at :8080"&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;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl localhost:8080

  ____    _                       _             ____                _      ____    ___
 / ___|  &lt;span class="o"&gt;(&lt;/span&gt;_&lt;span class="o"&gt;)&lt;/span&gt;  _ __ ___    _ __   | |   ___     / ___|   ___        / &lt;span class="se"&gt;\ &lt;/span&gt;   |  _ &lt;span class="se"&gt;\ &lt;/span&gt; |_ _|
 &lt;span class="se"&gt;\_&lt;/span&gt;__ &lt;span class="se"&gt;\ &lt;/span&gt; | | | &lt;span class="s1"&gt;'_ ` _ \  | '&lt;/span&gt;_ &lt;span class="se"&gt;\ &lt;/span&gt; | |  / _ &lt;span class="se"&gt;\ &lt;/span&gt;  | |  _   / _ &lt;span class="se"&gt;\ &lt;/span&gt;     / _ &lt;span class="se"&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="o"&gt;(&lt;/span&gt;_&lt;span class="o"&gt;)&lt;/span&gt; |    / ___ &lt;span class="se"&gt;\ &lt;/span&gt; |  __/   | |
 |____/  |_| |_| |_| |_| | .__/  |_|  &lt;span class="se"&gt;\_&lt;/span&gt;__|    &lt;span class="se"&gt;\_&lt;/span&gt;___|  &lt;span class="se"&gt;\_&lt;/span&gt;__/    /_/   &lt;span class="se"&gt;\_\ &lt;/span&gt;|_|     |___|
                         |_|
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running the API as a Background Service with Supervisor
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Create a Supervisor Configuration for the Go API
&lt;/h4&gt;

&lt;p&gt;Create a new Supervisor config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vim /etc/supervisor/conf.d/go-api.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following configuration:&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="o"&gt;[&lt;/span&gt;program:go-api]
&lt;span class="nv"&gt;process_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%&lt;span class="o"&gt;(&lt;/span&gt;program_name&lt;span class="o"&gt;)&lt;/span&gt;s_%&lt;span class="o"&gt;(&lt;/span&gt;process_num&lt;span class="o"&gt;)&lt;/span&gt;02d
&lt;span class="nv"&gt;directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/www/go-api
&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'cd /var/www/go-api &amp;amp;&amp;amp; ./main'&lt;/span&gt;
&lt;span class="nv"&gt;autostart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;autorestart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;www-data
&lt;span class="nv"&gt;redirect_stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;stderr_logfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log/go-api.err.log
&lt;span class="nv"&gt;stdout_logfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log/go-api.out.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;directory=/var/www/go-api&lt;/code&gt; → The working directory of the Go API.&lt;br&gt;
&lt;code&gt;command=bash -c 'cd /var/www/go-api &amp;amp;&amp;amp; ./main'&lt;/code&gt; → Runs the API.&lt;br&gt;
&lt;code&gt;autostart=true&lt;/code&gt; → Starts automatically on system boot.&lt;br&gt;
&lt;code&gt;autorestart=true&lt;/code&gt; → Restarts if the process crashes.&lt;br&gt;
&lt;code&gt;user=www-data&lt;/code&gt; → Runs as the www-data user (adjust as needed).&lt;br&gt;
&lt;code&gt;redirect_stderr=true&lt;/code&gt; → Redirects error logs to stdout.&lt;br&gt;
&lt;code&gt;stdout_logfile=/var/log/go-api.out.log&lt;/code&gt; → Standard output log file.&lt;br&gt;
&lt;code&gt;stderr_logfile=/var/log/go-api.err.log&lt;/code&gt; → Error log file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, we need build the Go API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go build &lt;span class="nt"&gt;-o&lt;/span&gt; main &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure the directory and binary have the correct permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; www-data:www-data /var/www/go-api
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;775 /var/www/go-api/main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Apply the Supervisor Configuration
&lt;/h4&gt;

&lt;p&gt;Reload Supervisor and start the service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;supervisorctl reread
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;supervisorctl update
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;supervisorctl start go-api:&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the service status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;supervisorctl avail

go-api:go-api_00                 &lt;span class="k"&gt;in &lt;/span&gt;use    auto      999:999
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;supervisorctl status go-api:&lt;span class="k"&gt;*&lt;/span&gt;

go-api:go-api_00                 RUNNING   pid 198867, &lt;span class="nb"&gt;uptime &lt;/span&gt;0:01:52
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Check Logs and Debugging
&lt;/h4&gt;

&lt;p&gt;If the API is not running, check the logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo cat&lt;/span&gt; /var/log/go-api.out.log
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo cat&lt;/span&gt; /var/log/go-api.err.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use Supervisor’s built-in log viewer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;supervisorctl &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; go-api:go-api_00

&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Press Ctrl-C to &lt;span class="nb"&gt;exit&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;==&lt;/span&gt;
Server started at :8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setting Up Nginx as a Reverse Proxy for the API
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Create a new configuration file:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vim /etc/nginx/sites-available/go-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;server &lt;span class="o"&gt;{&lt;/span&gt;
    server_name _&lt;span class="p"&gt;;&lt;/span&gt;

    location / &lt;span class="o"&gt;{&lt;/span&gt;
        proxy_pass http://127.0.0.1:8080&lt;span class="p"&gt;;&lt;/span&gt;
        proxy_http_version 1.1&lt;span class="p"&gt;;&lt;/span&gt;
        proxy_set_header Upgrade &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        proxy_set_header Connection &lt;span class="s1"&gt;'upgrade'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        proxy_set_header Host &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    error_log /var/log/nginx/go-api_error.log&lt;span class="p"&gt;;&lt;/span&gt;
    access_log /var/log/nginx/go-api_access.log&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a symbolic link to enable the site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/go-api /etc/nginx/sites-enabled/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf &lt;span class="nb"&gt;test &lt;/span&gt;is successful
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the test is successful, restart Nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you can access your Go API using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Localhost (if running locally)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://localhost

  ____    _                       _             ____                _      ____    ___
 / ___|  &lt;span class="o"&gt;(&lt;/span&gt;_&lt;span class="o"&gt;)&lt;/span&gt;  _ __ ___    _ __   | |   ___     / ___|   ___        / &lt;span class="se"&gt;\ &lt;/span&gt;   |  _ &lt;span class="se"&gt;\ &lt;/span&gt; |_ _|
 &lt;span class="se"&gt;\_&lt;/span&gt;__ &lt;span class="se"&gt;\ &lt;/span&gt; | | | &lt;span class="s1"&gt;'_ ` _ \  | '&lt;/span&gt;_ &lt;span class="se"&gt;\ &lt;/span&gt; | |  / _ &lt;span class="se"&gt;\ &lt;/span&gt;  | |  _   / _ &lt;span class="se"&gt;\ &lt;/span&gt;     / _ &lt;span class="se"&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="o"&gt;(&lt;/span&gt;_&lt;span class="o"&gt;)&lt;/span&gt; |    / ___ &lt;span class="se"&gt;\ &lt;/span&gt; |  __/   | |
 |____/  |_| |_| |_| |_| | .__/  |_|  &lt;span class="se"&gt;\_&lt;/span&gt;__|    &lt;span class="se"&gt;\_&lt;/span&gt;___|  &lt;span class="se"&gt;\_&lt;/span&gt;__/    /_/   &lt;span class="se"&gt;\_\ &lt;/span&gt;|_|     |___|
                         |_|
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Server’s Public IP (if running on a VPS or remote server)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://YOUR_SERVER_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: If you want to access your Go API using a custom domain instead of an IP address, you need to purchase a domain, configure its DNS to point to your server’s IP, and update your Nginx configuration accordingly. For better security, it's recommended to set up HTTPS using Let's Encrypt.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;In this guide, we deployed a simple Go API using Supervisor to manage the process ensuring automatic restarts and efficient request handling also Nginx as a reverse proxy. Thank you for reading, and good luck with your deployment! 🚀&lt;/p&gt;

</description>
      <category>go</category>
      <category>linux</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Kubernetes 105: Create Kubernetes Cluster</title>
      <dc:creator>Daniel Pepuho</dc:creator>
      <pubDate>Sat, 01 Feb 2025 14:21:01 +0000</pubDate>
      <link>https://dev.to/danielcristho/kubernetes-105-create-kubernetes-cluster-oha</link>
      <guid>https://dev.to/danielcristho/kubernetes-105-create-kubernetes-cluster-oha</guid>
      <description>&lt;p&gt;Let's start again. Now we will do some practices.&lt;/p&gt;

&lt;p&gt;In this part of the Kubernetes series, we will explore how to create a Kubernetes cluster in different environments. Whether you're running Kubernetes locally or in the cloud, understanding how to set up a cluster is fundamental to deploying and managing containerized applications efficiently.&lt;/p&gt;

&lt;p&gt;We will cover three different ways to create a Kubernetes cluster:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Kind (Kubernetes in Docker) - A lightweight way to run Kubernetes clusters locally for testing and development.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;K3D (K3S in Docker) - A more lightweight Kubernetes distribution, optimized for local development and CI/CD workflows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;EKS (Amazon Elastic Kubernetes Service) - A managed Kubernetes service provided by AWS for running Kubernetes workloads in the cloud.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each approach has its own use cases, advantages, and trade-offs. Let's dive into each one and see how to set up a cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up a Kubernetes Cluster with Kind
&lt;/h2&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%2F7x44sjqpd4v0ikbgpu2f.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%2F7x44sjqpd4v0ikbgpu2f.png" alt="Kind Logo" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kind (Kubernetes in Docker) is one of the simplest ways to spin up a Kubernetes cluster for local development and testing. It runs Kubernetes clusters inside Docker containers and is widely used for CI/CD and development workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Docker installed on your machine. (&lt;a href="https://docs.docker.com/engine/install" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;KIND CLI installed. (&lt;a href="https://kind.sigs.k8s.io/docs/user/quick-start/#installation" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Kubectl CLI installed. (&lt;a href="https://kubernetes.io/docs/tasks/tools" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create a Cluster with Kind
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create a new Kind cluster:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt; kind create cluster &lt;span class="nt"&gt;--name&lt;/span&gt; kind-cluster

Creating cluster &lt;span class="s2"&gt;"kind-cluster"&lt;/span&gt; ...
 ✓ Ensuring node image &lt;span class="o"&gt;(&lt;/span&gt;kindest/node:v1.31.0&lt;span class="o"&gt;)&lt;/span&gt; 🖼
 ✓ Preparing nodes 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
Set kubectl context to &lt;span class="s2"&gt;"kind-kind-cluster"&lt;/span&gt;
You can now use your cluster with:

kubectl cluster-info &lt;span class="nt"&gt;--context&lt;/span&gt; kind-kind-cluster

Thanks &lt;span class="k"&gt;for &lt;/span&gt;using kind! 😊
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Check the cluster:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;  kubectl cluster-info &lt;span class="nt"&gt;--context&lt;/span&gt; kind-kind-cluster

Kubernetes control plane is running at https://127.0.0.1:43417
CoreDNS is running at https://127.0.0.1:43417/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use &lt;span class="s1"&gt;'kubectl cluster-info dump'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;List available nodes:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get nodes

NAME                         STATUS   ROLES           AGE   VERSION
kind-cluster-control-plane   Ready    control-plane   75s   v1.31.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create Simple Deployment
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use the &lt;code&gt;kubectl create deployment&lt;/code&gt; command to define and start a deployment:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt; kubectl create deployment nginx &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx

deployment.apps/nginx created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Check deployment status using &lt;code&gt;kubectl get deployment&lt;/code&gt; command:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get deployment

NAME    READY   UP-TO-DATE   AVAILABLE   AGE
nginx   0/1     1            0           29s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Expose the deployment:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl expose deployment nginx &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80 &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;LoadBalancer

service/nginx exposed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Verify the Pod status and then try to access Nginx using your browser:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods

NAME                     READY   STATUS    RESTARTS   AGE
nginx-676b6c5bbc-wd87x   1/1     Running   0          12m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fxtltv33114mzja34rr1g.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%2Fxtltv33114mzja34rr1g.png" alt="Access Nginx" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete the cluster when no longer needed
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kind delete cluster &lt;span class="nt"&gt;--name&lt;/span&gt; kind-cluster

Deleting cluster &lt;span class="s2"&gt;"kind-cluster"&lt;/span&gt; ...
Deleted nodes: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"kind-cluster-control-plane"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting Up a Kubernetes Cluster with K3D
&lt;/h2&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%2Fad1hu2a7xvrvme945nla.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%2Fad1hu2a7xvrvme945nla.png" alt="K3D Logo" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;K3D is a tool that allows you to run lightweight Kubernetes clusters using K3S inside Docker. It is a great choice for fast, local Kubernetes development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Docker installed on your machine. (&lt;a href="https://docs.docker.com/engine/install" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;K3D CLI installed. (&lt;a href="https://k3d.io/stable/#installation" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Kubectl CLI installed. (&lt;a href="https://kubernetes.io/docs/tasks/tools" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create a Cluster with K3D
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create a new K3D cluster:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;k3d cluster create my-k3d-cluster

INFO[0000] Prep: Network
INFO[0000] Created network &lt;span class="s1"&gt;'k3d-my-k3d-cluster'&lt;/span&gt;
INFO[0000] Created image volume k3d-my-k3d-cluster-images
INFO[0000] Starting new tools node...
INFO[0000] Starting node &lt;span class="s1"&gt;'k3d-my-k3d-cluster-tools'&lt;/span&gt;
INFO[0001] Creating node &lt;span class="s1"&gt;'k3d-my-k3d-cluster-server-0'&lt;/span&gt;
INFO[0001] Creating LoadBalancer &lt;span class="s1"&gt;'k3d-my-k3d-cluster-serverlb'&lt;/span&gt;
INFO[0001] Using the k3d-tools node to gather environment information
INFO[0001] HostIP: using network gateway 172.20.0.1 address
INFO[0001] Starting cluster &lt;span class="s1"&gt;'my-k3d-cluster'&lt;/span&gt;
INFO[0001] Starting servers...
INFO[0001] Starting node &lt;span class="s1"&gt;'k3d-my-k3d-cluster-server-0'&lt;/span&gt;
INFO[0008] All agents already running.
INFO[0008] Starting helpers...
INFO[0008] Starting node &lt;span class="s1"&gt;'k3d-my-k3d-cluster-serverlb'&lt;/span&gt;
INFO[0016] Injecting records &lt;span class="k"&gt;for &lt;/span&gt;hostAliases &lt;span class="o"&gt;(&lt;/span&gt;incl. host.k3d.internal&lt;span class="o"&gt;)&lt;/span&gt; and &lt;span class="k"&gt;for &lt;/span&gt;2 network members into CoreDNS configma
p...
INFO[0018] Cluster &lt;span class="s1"&gt;'my-k3d-cluster'&lt;/span&gt; created successfully!
INFO[0018] You can now use it like this:
kubectl cluster-info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Check the cluster status:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl cluster-info

Kubernetes control plane is running at https://0.0.0.0:46503
CoreDNS is running at https://0.0.0.0:46503/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
Metrics-server is running at https://0.0.0.0:46503/api/v1/namespaces/kube-system/services/https:metrics-server:https/p
roxy

To further debug and diagnose cluster problems, use &lt;span class="s1"&gt;'kubectl cluster-info dump'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;List available nodes:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get nodes

NAME                          STATUS   ROLES                  AGE     VERSION
k3d-my-k3d-cluster-server-0   Ready    control-plane,master   2m33s   v1.30.4+k3s1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create Simple Deployment
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use the &lt;code&gt;kubectl create deployment&lt;/code&gt; command to define and start a deployment:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create deployment httpd &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;httpd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Check deployment status using &lt;code&gt;kubectl get deployment&lt;/code&gt;  command:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get deployment

NAME    READY   UP-TO-DATE   AVAILABLE   AGE
httpd   0/1     1            0           45s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Verify the Pod status:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt; kubectl get pods

NAME                     READY   STATUS    RESTARTS   AGE
httpd-56f946b8c8-84ww8   1/1     Running   0          9m11s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Expose the deployment:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl expose deployment httpd &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80 &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;LoadBalancer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Try to access using browser:&lt;/li&gt;
&lt;/ul&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%2Fhcz2h6s89xmovj5zxpv5.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%2Fhcz2h6s89xmovj5zxpv5.png" alt="Access HTTPD" width="794" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete the cluster when no longer needed:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;k3d cluster delete my-k3d-cluster

INFO[0000] Deleting cluster &lt;span class="s1"&gt;'my-k3d-cluster'&lt;/span&gt;
INFO[0001] Deleting cluster network &lt;span class="s1"&gt;'k3d-my-k3d-cluster'&lt;/span&gt;
INFO[0001] Deleting 1 attached volumes...
INFO[0001] Removing cluster details from default kubeconfig...
INFO[0001] Removing standalone kubeconfig file &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if &lt;/span&gt;there is one&lt;span class="o"&gt;)&lt;/span&gt;...
INFO[0001] Successfully deleted cluster my-k3d-cluster!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting Up a Kubernetes Cluster on AWS EKS
&lt;/h2&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%2F8stn3qycpy7381a03rj4.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%2F8stn3qycpy7381a03rj4.png" alt="AWS EKS Logo" width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Amazon Elastic Kubernetes Service (EKS) is a fully managed Kubernetes service on AWS, designed for running production workloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AWS CLI installed and configured. (&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;EKSCTL CLI installed. (&lt;a href="https://eksctl.io/installation" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Kubectl CLI installed. (&lt;a href="https://kubernetes.io/docs/tasks/tools" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create a cluster on EKS
&lt;/h3&gt;

&lt;p&gt;To create a Kubernetes cluster in AWS, you can use the AWS Console (dashboard) or the eksctl CLI. For this guide, we will use &lt;code&gt;eksctl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We will provisions an EKS cluster with two &lt;code&gt;t4g.small&lt;/code&gt; nodes in the &lt;code&gt;us-east-1&lt;/code&gt; region, making it ready for running Kubernetes workloads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;eksctl create cluster  &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; cluster-1  &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--node-type&lt;/span&gt; t4g.small &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--nodes&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--nodegroup-name&lt;/span&gt; node-group-1

2025-02-01 19:52:35 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]  eksctl version 0.202.0
2025-02-01 19:52:35 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]  using region us-east-1
2025-02-01 19:52:37 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]  setting availability zones to &lt;span class="o"&gt;[&lt;/span&gt;us-east-1c us-east-1f]

...

2025-02-01 20:02:04 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]  creating addon
2025-02-01 20:02:04 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]  successfully created addon
2025-02-01 20:02:05 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]  creating addon
2025-02-01 20:02:06 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]  successfully created addon
2025-02-01 20:02:07 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]  creating addon
2025-02-01 20:02:07 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]  successfully created addon
&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt; region is ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Access AWS console, navigate to the EKS service and you can see the cluster is successfully created.&lt;/li&gt;
&lt;/ul&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%2Fu6fea9eyql10rn2kvp9f.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%2Fu6fea9eyql10rn2kvp9f.png" alt="After cluster creation" width="800" height="90"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;List available nodes:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get nodes
NAME                             STATUS   ROLES    AGE   VERSION
ip-192-168-xx-yy.ec2.internal   Ready    &amp;lt;none&amp;gt;   17m   v1.30.8-eks-aeac579
ip-192-168-xx-yy.ec2.internal   Ready    &amp;lt;none&amp;gt;   17m   v1.30.8-eks-aeac57
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create Simple Deployment
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use the &lt;code&gt;kubectl create deployment&lt;/code&gt; command to define and start a deployment:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create deployment nginx &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx

deployment.apps/nginx create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Check deployment status using &lt;code&gt;kubectl get deployment&lt;/code&gt; command:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt; kubectl get deployment

NAME     READY   UP-TO-DATE   AVAILABLE   AGE
nginx    1/1     1            1           23s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Verify the Pod status:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods

NAME                     READY   STATUS         RESTARTS   AGE
nginx-bf5d5cf98-9dld5    1/1     Running        0          43s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Expose the service:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl expose deployment nginx &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;LoadBalancer &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80 &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Try to access using the browser:&lt;/li&gt;
&lt;/ul&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%2Fs3c2ul7c18ntg8x5tq4r.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%2Fs3c2ul7c18ntg8x5tq4r.png" alt="Access the service" width="800" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete the cluster when no longer needed:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt; eksctl delete cluster &lt;span class="nt"&gt;--name&lt;/span&gt; cluster-1 &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1

2025-02-01 20:51:59 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]  deleting EKS cluster &lt;span class="s2"&gt;"cluster-1"&lt;/span&gt;
2025-02-01 20:52:02 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]  will drain 0 unmanaged nodegroup&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;cluster &lt;span class="s2"&gt;"cluster-1"&lt;/span&gt;
2025-02-01 20:52:02 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]  starting parallel draining, max &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="nt"&gt;-flight&lt;/span&gt; of 1
2025-02-01 20:52:04 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]  deleted 0 Fargate profile&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
2025-02-01 20:52:13 &lt;span class="o"&gt;[&lt;/span&gt;✔]  kubeconfig has been updated
2025-02-01 20:52:13 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]  cleaning up AWS load balancers created by Kubernetes objects of Kind Service or Ingress
2025-02-01 20:52:56 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]
...

2025-02-01 21:02:00 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]  waiting &lt;span class="k"&gt;for &lt;/span&gt;CloudFormation stack &lt;span class="s2"&gt;"eksctl-cluster-1-nodegroup-node-group-1"&lt;/span&gt;
2025-02-01 21:02:01 &lt;span class="o"&gt;[&lt;/span&gt;ℹ]  will delete stack &lt;span class="s2"&gt;"eksctl-cluster-1-cluster"&lt;/span&gt;
2025-02-01 21:02:04 &lt;span class="o"&gt;[&lt;/span&gt;✔]  all cluster resources were deleted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Setting up a Kubernetes cluster is the first step in running containerized applications at scale. In this guide, we've explored three different ways to create a Kubernetes cluster and do a simple deployment: using Kind and K3D for local development and using EKS for cloud-based deployments. Each method has its own advantages depending on your use case.&lt;/p&gt;

&lt;p&gt;Thanks for reading this post. Stay tuned!&lt;/p&gt;

&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;KUBERNETES UNTUK PEMULA. &lt;a href="https://github.com/ngoprek-kubernetes/buku-kubernetes-pemula" rel="noopener noreferrer"&gt;https://github.com/ngoprek-kubernetes/buku-kubernetes-pemula&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How do I install AWS EKS CLI (eksctl)?. &lt;a href="https://learn.arm.com/install-guides/eksctl" rel="noopener noreferrer"&gt;https://learn.arm.com/install-guides/eksctl&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>beginners</category>
      <category>aws</category>
    </item>
    <item>
      <title>Kubernetes 104: Controllers</title>
      <dc:creator>Daniel Pepuho</dc:creator>
      <pubDate>Wed, 22 Jan 2025 09:13:36 +0000</pubDate>
      <link>https://dev.to/danielcristho/kubernetes-104-controller-2bna</link>
      <guid>https://dev.to/danielcristho/kubernetes-104-controller-2bna</guid>
      <description>&lt;p&gt;Let's start again. Now I'm going to talk about Controllers in Kubernetes. In Kubernetes, a Controller is like a cluster's brain, constantly working to ensure the system maintains its desired state. By monitoring objects such as Pods, Deployments, or DaemonSets, Controllers automate tasks and handle changes dynamically. Understanding Controllers is key to grasping how Kubernetes orchestrates and manages workloads seamlessly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Kubernetes Controllers
&lt;/h2&gt;

&lt;p&gt;Here are some of the most commonly used Controllers in Kubernetes:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Deployment
&lt;/h2&gt;

&lt;p&gt;Deployments &lt;strong&gt;manage updates to Pods and ReplicaSets declaratively by transitioning the current state to the desired state step-by-step.&lt;/strong&gt; They can create new ReplicaSets, adopt existing resources, or remove old Deployments.&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%2Frqs2kfd890pb086t69fj.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%2Frqs2kfd890pb086t69fj.png" alt="Deployment Diagram" width="682" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Common uses for Deployments include:&lt;/p&gt;

&lt;p&gt;a. Releasing a ReplicaSet and monitoring its status.&lt;br&gt;
b. Updating Pod specifications to declare a new desired state.&lt;br&gt;
c. Scaling up to handle increased load.&lt;br&gt;
d. Rolling back to a previous version if the current state is unstable.&lt;br&gt;
e. Cleaning up unused ReplicaSets.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. ReplicaSet
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ReplicaSets (RS) function as controllers in Kubernetes, responsible for maintaining a consistent number of running Pods for a specific workload&lt;/strong&gt;. Acting as the mechanism behind the scenes, the ReplicaSet controller continuously monitors the state of the Pods and ensures the desired replica count is maintained. If a Pod crashes, is evicted, or fails for any reason, the ReplicaSet controller swiftly creates new Pods to restore the desired state, ensuring resilience and uninterrupted service.&lt;/p&gt;

&lt;p&gt;In practical use, ReplicaSets are not typically managed directly by users. Instead, they are controlled through Deployments, which leverage the ReplicaSet controller while providing additional features such as rolling updates, rollbacks, and declarative workload management. This abstraction allows users to benefit from the reliability and scalability of ReplicaSet controllers without dealing with their complexities directly.&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%2F6kzhidqggm8mlepb6uwu.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%2F6kzhidqggm8mlepb6uwu.png" alt="ReplicaSet Diagram" width="678" height="715"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. DaemonSet
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;DaemonSet (DS) ensures that every or specific nodes in a cluster run a copy of a particular Pod&lt;/strong&gt;. When a new node is added, DaemonSet automatically creates a Pod on that node. Conversely, when a node is removed, the associated Pod is deleted by the &lt;code&gt;garbage collector&lt;/code&gt;. Deleting the DaemonSet removes all Pods it created.&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%2F03t2uvmtrhse6lovati2.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%2F03t2uvmtrhse6lovati2.png" alt="DaemonSet Diagram" width="678" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Common uses of DaemonSet:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Running storage daemons across nodes, such as Glusterd or Ceph.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Running log collection daemons across nodes, such as Fluentd or LogStash.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Running node monitoring daemons, such as Prometheus Node Exporter, Flowmill, or New Relic Agent.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;DaemonSet is ideal for tasks that require processes to run on every node, such as log collection, monitoring, or providing local volumes.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. StatefulSet
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A StatefulSet is a Kubernetes controller used for managing stateful applications. Unlike Deployments, which focus on stateless workloads, StatefulSet is designed for applications that require persistent identity and stable storage&lt;/strong&gt;. It ensures that each Pod it manages has a unique, stable network identity and maintains a strict order during creation, scaling, or deletion.&lt;/p&gt;

&lt;p&gt;Key Features of StatefulSet:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Stable Network Identity: Each Pod gets a unique and persistent DNS name (e.g., &lt;code&gt;pod-0&lt;/code&gt;, &lt;code&gt;pod-1&lt;/code&gt;), which remains consistent even after rescheduling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ordered Pod Deployment and Scaling: Pods are created and scaled in a sequential order. For example, &lt;code&gt;pod-0&lt;/code&gt; must be created before &lt;code&gt;pod-1&lt;/code&gt;, and the same applies during deletion.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Persistent Storage: StatefulSet works closely with PersistentVolumeClaim (PVC). Each Pod gets a dedicated PersistentVolume that remains intact even after the Pod is deleted or rescheduled.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2Fxc9ekn6rrwkg1akmw7a6.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%2Fxc9ekn6rrwkg1akmw7a6.png" alt="StatefulSet Diagram" width="679" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Common Use Cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Databases like MySQL, PostgreSQL, and MongoDB, where stable storage and network identity are critical.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Distributed Systems like Cassandra, Kafka, or ZooKeeper, where maintaining order and state is essential.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Caching Systems like Redis, requiring predictable storage and replication across nodes.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  5. Job
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A Job is a Kubernetes controller designed to manage tasks that run to completion&lt;/strong&gt;. Unlike Deployments or StatefulSets, which manage long-running or stateful applications, Jobs are used for short-lived workloads that need to be executed only once or a specific number of times.&lt;/p&gt;

&lt;p&gt;Key Features of a Job:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Ensures Completion: A Job creates one or more Pods to perform a task and ensures that the task is completed successfully. If a Pod fails, the Job controller automatically creates a replacement until the task succeeds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Parallelism: Jobs support parallel execution, allowing multiple Pods to run concurrently, controlled by the parallelism and completions parameters.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Retries: Jobs retry failed Pods until the task is successful or a specified backoff limit is reached.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Common Use Cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Batch Processing: Data transformation, ETL pipelines, or video encoding.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Database Operations: Running migrations, backups, or clean-up scripts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;One-Time Tasks: Performing diagnostics, generating reports, or initializing configurations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for reading this post.😀&lt;/p&gt;

&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;KUBERNETES UNTUK PEMULA. &lt;a href="https://github.com/ngoprek-kubernetes/buku-kubernetes-pemula" rel="noopener noreferrer"&gt;https://github.com/ngoprek-kubernetes/buku-kubernetes-pemula&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Kubernetes Documentation: Jobs. &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/job" rel="noopener noreferrer"&gt;https://kubernetes.io/docs/concepts/workloads/controllers/job&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Kubernetes Controllers. &lt;a href="https://www.uffizzi.com/kubernetes-multi-tenancy/kubernetes-controllers" rel="noopener noreferrer"&gt;https://www.uffizzi.com/kubernetes-multi-tenancy/kubernetes-controllers&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Kubernetes: DaemonSet. &lt;a href="https://opstree.com/blog/2021/12/07/kubernetes-daemonset" rel="noopener noreferrer"&gt;https://opstree.com/blog/2021/12/07/kubernetes-daemonset&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Kubernetes StatefulSet vs Kubernetes Deployment. &lt;a href="https://devtron.ai/blog/deployment-vs-statefulset" rel="noopener noreferrer"&gt;https://devtron.ai/blog/deployment-vs-statefulset&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>beginners</category>
      <category>devops</category>
      <category>kubernetes</category>
    </item>
  </channel>
</rss>
