<?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: Vivek Sharma</title>
    <description>The latest articles on DEV Community by Vivek Sharma (@vviveksharma).</description>
    <link>https://dev.to/vviveksharma</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%2F3471861%2F4c01a70d-4d71-4c2d-b0e6-ab9d6edda1f6.png</url>
      <title>DEV Community: Vivek Sharma</title>
      <link>https://dev.to/vviveksharma</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vviveksharma"/>
    <language>en</language>
    <item>
      <title>Your Docker Builds Are Slow Because You're Doing It Wrong (And I Built a Tool to Prove It)</title>
      <dc:creator>Vivek Sharma</dc:creator>
      <pubDate>Sat, 23 May 2026 13:24:04 +0000</pubDate>
      <link>https://dev.to/vviveksharma/your-docker-builds-are-slow-because-youre-doing-it-wrong-and-i-built-a-tool-to-prove-it-5f5c</link>
      <guid>https://dev.to/vviveksharma/your-docker-builds-are-slow-because-youre-doing-it-wrong-and-i-built-a-tool-to-prove-it-5f5c</guid>
      <description>&lt;h2&gt;
  
  
  Stop waiting 10 minutes for CI to rebuild everything when you change one line of code. Here's what's actually breaking your Docker layer cache."
&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%2Fg4oh03tq9ttcgtj96mmj.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%2Fg4oh03tq9ttcgtj96mmj.png" alt="image showing the wait of developer until the docker build is completed" width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post is about Docker layer caching and why your builds probably take way longer than they should. If you've ever sat there watching Docker reinstall npm packages for the 10th time today after changing one line, yeah this is for you.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pain
&lt;/h2&gt;

&lt;p&gt;Okay so. You know that feeling, right? You changed literally ONE line of code. Fixed a typo in a comment or whatever. Push it up, CI kicks off, and now you're sitting there watching Docker reinstall 400 npm packages. Again. For the third time today.&lt;/p&gt;

&lt;p&gt;And you're like "why is this happening to me"&lt;/p&gt;

&lt;p&gt;Here's the thing - your build isn't slow because your code sucks or because AWS is being slow. It's slow because &lt;strong&gt;you wrote your Dockerfile wrong&lt;/strong&gt;. We all do it.&lt;/p&gt;

&lt;p&gt;I'm willing to bet you've got something like:&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="s"&gt; node:18&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;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yeah. That's the problem right there. Every code change = full npm install. Docker's layer cache? Gone. Destroyed. RIP.&lt;/p&gt;

&lt;p&gt;After explaining this to coworkers (and myself) like 50 times, I finally got fed up and built &lt;a href="https://github.com/vviveksharma/layerLint" rel="noopener noreferrer"&gt;LayerLint&lt;/a&gt;. It's basically a tool that looks at your Dockerfile and tells you when you messed up.&lt;/p&gt;

&lt;p&gt;(Also it was a good excuse to write some Go code. But mainly the other thing.)&lt;/p&gt;

&lt;h2&gt;
  
  
  How Docker Caching Actually Works (The 5-Minute Version)
&lt;/h2&gt;

&lt;p&gt;So nobody really explains this when you're learning Docker. They just tell you to write a Dockerfile and good luck. But here's what's actually happening: &lt;strong&gt;every line in your Dockerfile creates a layer&lt;/strong&gt;. These layers get cached. Which is great! Except the cache breaks super easily.&lt;/p&gt;

&lt;p&gt;Best way I can explain it - imagine a stack of pancakes. (I'm hungry, don't judge.) Each line (&lt;code&gt;FROM&lt;/code&gt;, &lt;code&gt;RUN&lt;/code&gt;, &lt;code&gt;COPY&lt;/code&gt;, whatever) is one pancake. Docker caches each pancake. So far so good.&lt;/p&gt;

&lt;p&gt;But then. Here's where it gets annoying: &lt;strong&gt;if you change one pancake, Docker throws away that pancake plus every single pancake above it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So like, if you change pancake 3, pancakes 3, 4, 5, 6, and 7 all get tossed. Only pancakes 1 and 2 stay cached.&lt;/p&gt;

&lt;p&gt;This whole thing is called the "invalidation chain" and it's literally why your builds take forever.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Cache-Killer Pattern
&lt;/h3&gt;

&lt;p&gt;Here's the classic mistake everyone makes (including me for like 2 years):&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="s"&gt; node:18&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .                    # Layer 1: Copy everything&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;             &lt;span class="c"&gt;# Layer 2: Install dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build           &lt;span class="c"&gt;# Layer 3: Build&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Okay so what happens when you change &lt;code&gt;src/index.js&lt;/code&gt;?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Layer 1 (&lt;code&gt;COPY . .&lt;/code&gt;) sees something changed ❌&lt;/li&gt;
&lt;li&gt;Cache = broken&lt;/li&gt;
&lt;li&gt;Layer 2 (&lt;code&gt;npm install&lt;/code&gt;) has to run again ❌&lt;/li&gt;
&lt;li&gt;Layer 3 (&lt;code&gt;npm run build&lt;/code&gt;) runs again too ❌&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You literally just reinstalled 400 packages because you changed one file. And Docker's sitting there like "yep, seems right".&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%2F1qwga860nmnsufdwyno2.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%2F1qwga860nmnsufdwyno2.png" alt="Image telling how the busted cache looks like" width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Alright here's the actual correct way:&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="s"&gt; node:18&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./   # Layer 1: Copy deps manifest&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;                           &lt;span class="c"&gt;# Layer 2: Install (cached!)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .                                  # Layer 3: Copy source&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build                         &lt;span class="c"&gt;# Layer 4: Build&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Now when you change &lt;code&gt;src/index.js&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Layer 1 (&lt;code&gt;package*.json&lt;/code&gt;) - didn't change ✅ (uses cache)&lt;/li&gt;
&lt;li&gt;Layer 2 (&lt;code&gt;npm install&lt;/code&gt;) - didn't change ✅ (uses cache)&lt;/li&gt;
&lt;li&gt;Layer 3 (&lt;code&gt;COPY . .&lt;/code&gt;) - okay this changed, rebuild&lt;/li&gt;
&lt;li&gt;Layer 4 (&lt;code&gt;npm run build&lt;/code&gt;) - gotta rebuild this too&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But look - layers 1 and 2 stayed cached! You just saved like 2-8 minutes every single build.&lt;/p&gt;

&lt;p&gt;This pattern works everywhere btw:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node → &lt;code&gt;package.json&lt;/code&gt; + &lt;code&gt;package-lock.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Go → &lt;code&gt;go.mod&lt;/code&gt; + &lt;code&gt;go.sum&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Python → &lt;code&gt;requirements.txt&lt;/code&gt; or &lt;code&gt;poetry.lock&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Rust → &lt;code&gt;Cargo.toml&lt;/code&gt; + &lt;code&gt;Cargo.lock&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same idea. Copy the dependency files first, install them, THEN copy your actual code.&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%2Figizc924539asbwj49k8.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%2Figizc924539asbwj49k8.png" alt="Image shows how the cached docker file looks like" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet LayerLint: The Dockerfile Linter You Didn't Know You Needed
&lt;/h2&gt;

&lt;p&gt;So I've seen this same mistake approximately 47 times. Someone (usually me) puts &lt;code&gt;COPY . .&lt;/code&gt; before &lt;code&gt;npm install&lt;/code&gt; and then wonders why builds take forever. Eventually I just got annoyed enough to build something about it.&lt;/p&gt;

&lt;p&gt;That's LayerLint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is it?&lt;/strong&gt;&lt;br&gt;
It's a static analysis tool I wrote in Go. You point it at your Dockerfile, it reads through it and goes "yo, this is gonna be slow". Doesn't even need to build the image. Just looks at the file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not use hadolint or whatever?&lt;/strong&gt;&lt;br&gt;
Hadolint is great for syntax stuff and general best practices. LayerLint is specifically focused on &lt;strong&gt;layer caching anti-patterns&lt;/strong&gt;. The stuff that makes your builds slow. Different problem.&lt;/p&gt;

&lt;p&gt;Think of it like having that one DevOps person who's always grumpy about Dockerfiles, except it runs instantly and won't send you passive-aggressive Slack messages.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Walkthrough: Let's Break Some Stuff
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Installation (Pick Your Poison)
&lt;/h3&gt;

&lt;p&gt;There's like 3 different ways to install it depending on how paranoid you are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lazy way (this is what I use):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://raw.githubusercontent.com/vviveksharma/layerLint/main/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The "I don't trust random install scripts" way (fair enough):&lt;/strong&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;# Grab the binary from releases&lt;/span&gt;
wget https://github.com/vviveksharma/layerLint/releases/latest/download/layerLint_Linux_x86_64.tar.gz
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; layerLint_Linux_x86_64.tar.gz
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x layerlint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The "I'm gonna build it from source" way (respect):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/vviveksharma/layerLint
&lt;span class="nb"&gt;cd &lt;/span&gt;layerLint
make generate-build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Scan
&lt;/h3&gt;

&lt;p&gt;Let me use that bad Dockerfile from before:&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="s"&gt; golang:1.22&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="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;go build &lt;span class="nt"&gt;-o&lt;/span&gt; server ./cmd/server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run LayerLint on it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./layerlint scan &lt;span class="nt"&gt;--dockerfile&lt;/span&gt; Dockerfile
&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%2Fl7gw2l5b25ymhzsulwvv.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%2Fl7gw2l5b25ymhzsulwvv.png" alt="layerlint response on the terminal" width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;╔══════════════════════════════════════════════════════════════════╗
║                        LayerLint Report                          ║
╚══════════════════════════════════════════════════════════════════╝

RuleID:     dockerfile/broad-copy-before-deps
Severity:   high
File:       Dockerfile
Line:       3
Title:      Dependency install runs after broad source copy
Message:    This dependency step runs after a broad COPY/ADD, so source 
            changes can invalidate the dependency cache.
Suggestion: Copy dependency manifests first (go.mod, go.sum), install 
            dependencies, then copy the rest of the source.

Example Fix:
  COPY go.mod go.sum ./
  RUN go mod download
  COPY . .

═══════════════════════════════════════════════════════════════════

Found 1 violation:
  - High:   1
  - Medium: 0
  - Low:    0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty nice right? It basically tells you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What you did wrong (broad copy before deps)&lt;/li&gt;
&lt;li&gt;Where exactly it is (line 3, can't miss it)&lt;/li&gt;
&lt;li&gt;Why this is bad (breaks the cache)&lt;/li&gt;
&lt;li&gt;How to actually fix it (copy manifests first)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No guessing. No googling "why is my docker build slow reddit". Just tells you straight up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Rules It Catches
&lt;/h3&gt;

&lt;p&gt;LayerLint checks for a bunch of other stuff too that'll eventually bite you:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;Why You Should Care&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unpinned-base-image-tag&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;:latest&lt;/code&gt; means your builds aren't reproducible (bad)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;copying-secrets-into-image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Secrets live forever in layer history even if you delete them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run-as-root&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Security issue, containers shouldn't run as root&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;missing-dockerignore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Slow builds + might leak secrets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;apt-update-without-install&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Package cache goes stale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;build-without-cache-mount&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Missing BuildKit cache mounts (free speed)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There's more. Check the &lt;a href="https://github.com/vviveksharma/layerLint/blob/main/docs/rules.md" rel="noopener noreferrer"&gt;full rules docs&lt;/a&gt; if you're curious.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Win: Automation with GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Okay so finding issues is one thing. But the real win? Making it so nobody (including yourself in 3 months when you forget all this) can merge a bad Dockerfile.&lt;/p&gt;

&lt;p&gt;Here's what you do. Add this to &lt;code&gt;.github/workflows/docker-lint.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint Dockerfile&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install LayerLint&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;curl -sSL https://raw.githubusercontent.com/vviveksharma/layerLint/main/install.sh | sh&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Scan Dockerfile&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./layerlint scan --dockerfile ./Dockerfile --fail-on-severity high&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And boom. Now every PR gets checked automatically. Someone tries to merge a slow Dockerfile? CI says no. PR blocked. They gotta fix it first.&lt;/p&gt;

&lt;p&gt;(Saved me from myself more times than I can count.)&lt;/p&gt;

&lt;p&gt;Oh and LayerLint can output different formats if you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--format json&lt;/code&gt; if you're doing scripting stuff&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--format sarif&lt;/code&gt; if you want it in GitHub's Security tab&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--format html&lt;/code&gt; for when you need to show management something pretty&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Real-World Example Workflows
&lt;/h3&gt;

&lt;p&gt;I put some example workflows in the repo that you can just copy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/vviveksharma/layerLint/blob/main/.github/workflows/examples/example-multi-dockerfile.yml" rel="noopener noreferrer"&gt;Scanning multiple Dockerfiles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vviveksharma/layerLint/blob/main/.github/workflows/examples/example-build-deploy.yml" rel="noopener noreferrer"&gt;Lint then build then deploy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vviveksharma/layerLint/blob/main/.github/workflows/examples/example-scheduled-audit.yml" rel="noopener noreferrer"&gt;Weekly audits that run automatically&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically just copy the yaml, change the paths to match your repo, commit it. Done.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Before &amp;amp; After: Show Me the Numbers
&lt;/h2&gt;

&lt;p&gt;Alright let me show you actual numbers from a real project I fixed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt; Node.js app, 342 npm packages (yeah it's a lot don't judge), changed one line in a component&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (the bad way):&lt;/strong&gt;&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="s"&gt; node:18&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;npm ci
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build time: &lt;strong&gt;8 minutes 32 seconds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every. Single. Time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After (fixed it):&lt;/strong&gt;&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="s"&gt; node:18&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build time: &lt;strong&gt;1 minute 12 seconds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's 7 minutes saved per build. I push like 10 times a day sometimes (don't we all?), so that's an hour saved. Every day. Just from fixing the Dockerfile.&lt;/p&gt;

&lt;p&gt;Do the yearly math on that and it's honestly kinda crazy. That's like... multiple work weeks just sitting there waiting for npm install.&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%2Fkmo951mouuihapfonqxw.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%2Fkmo951mouuihapfonqxw.png" alt="docker build comparison before and after using layerlint" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is actual time. Not theoretical. Real minutes you get back to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Actually write code&lt;/li&gt;
&lt;li&gt;Read docs (lol who am I kidding)&lt;/li&gt;
&lt;li&gt;Get coffee&lt;/li&gt;
&lt;li&gt;Scroll through Twitter/X or whatever we're calling it now&lt;/li&gt;
&lt;li&gt;Take a walk&lt;/li&gt;
&lt;li&gt;Pet your dog&lt;/li&gt;
&lt;li&gt;Literally anything that isn't watching a progress bar slowly fill up&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Other Platforms (Because Not Everyone Uses GitHub)
&lt;/h2&gt;

&lt;p&gt;LayerLint works on whatever CI system you're using:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitLab CI:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;docker-lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl -sSL https://raw.githubusercontent.com/vviveksharma/layerLint/main/install.sh | sh&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./layerlint scan --dockerfile Dockerfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;curl -sSL https://raw.githubusercontent.com/vviveksharma/layerLint/main/install.sh | sh&lt;/span&gt;
      &lt;span class="s"&gt;./layerlint scan --dockerfile Dockerfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'curl -sSL https://raw.githubusercontent.com/vviveksharma/layerLint/main/install.sh | sh'&lt;/span&gt;
&lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'./layerlint scan --dockerfile Dockerfile'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More platforms in the &lt;a href="https://github.com/vviveksharma/layerLint/blob/main/docs/ci-cd-integration.md" rel="noopener noreferrer"&gt;CI/CD guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pre-commit hook&lt;/strong&gt; (catch it before you even commit):&lt;/p&gt;

&lt;p&gt;Add this to &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;layerlint&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LayerLint&lt;/span&gt;
        &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;layerlint scan --dockerfile&lt;/span&gt;
        &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;system&lt;/span&gt;
        &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it runs every time you commit. Catches mistakes before they even get pushed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pro-Tips for Maximum Speed
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Use &lt;code&gt;.dockerignore&lt;/code&gt; (seriously please)
&lt;/h3&gt;

&lt;p&gt;I cannot stress this enough. Create a &lt;code&gt;.dockerignore&lt;/code&gt; file. Just do it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;node_modules&lt;/span&gt;/
.&lt;span class="n"&gt;git&lt;/span&gt;/
&lt;span class="n"&gt;dist&lt;/span&gt;/
&lt;span class="n"&gt;build&lt;/span&gt;/
*.&lt;span class="n"&gt;log&lt;/span&gt;
.&lt;span class="n"&gt;env&lt;/span&gt;*
*.&lt;span class="n"&gt;md&lt;/span&gt;
.&lt;span class="n"&gt;github&lt;/span&gt;/
&lt;span class="n"&gt;tests&lt;/span&gt;/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker won't send all that junk to the build context. Faster builds, smaller images, and you won't accidentally leak your &lt;code&gt;.env&lt;/code&gt; file into production.&lt;/p&gt;

&lt;p&gt;(I may or may not have done that once. Not fun. Learn from my mistakes.)&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Enable BuildKit Cache Mounts
&lt;/h3&gt;

&lt;p&gt;If you're not using BuildKit yet... start. And then use cache mounts:&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;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.npm &lt;span class="se"&gt;\
&lt;/span&gt;    npm ci

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/go/pkg/mod &lt;span class="se"&gt;\
&lt;/span&gt;    go mod download

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.cache/pip &lt;span class="se"&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;This caches package downloads &lt;strong&gt;between builds&lt;/strong&gt;. Not just within one build - between ALL of them. Absolute game changer.&lt;/p&gt;

&lt;p&gt;First time I set this up I thought something was broken because the build finished so fast. Nope, just working correctly for once.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Multi-Stage Builds for Production
&lt;/h3&gt;

&lt;p&gt;Also if you're shipping to prod, use multi-stage builds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18&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;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# Production stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your final image ends up way smaller, and deploys are faster. Win-win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contributing (Or: "I Found a Bug / Have an Idea")
&lt;/h2&gt;

&lt;p&gt;Code's pretty straightforward if you wanna contribute. Each rule is just its own file in &lt;code&gt;internal/rules/&lt;/code&gt;. Wanna add a new rule?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;internal/rules/your_rule.go&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Implement the &lt;code&gt;Rule&lt;/code&gt; interface:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Rule&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dockerfile&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Finding&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Register it in &lt;code&gt;internal/scanner/scanner.go&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add a test case in &lt;code&gt;testFiles/&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Look at &lt;a href="https://github.com/vviveksharma/layerLint/blob/main/internal/rules/broad_copy_before_deps.go" rel="noopener noreferrer"&gt;&lt;code&gt;broad_copy_before_deps.go&lt;/code&gt;&lt;/a&gt; to see how it's done.&lt;/p&gt;

&lt;p&gt;PRs welcome. If you find a caching anti-pattern that LayerLint misses, definitely add it. I'm sure there's stuff I haven't thought of. The Go parser stuff is pretty straightforward once you get into it.&lt;/p&gt;

&lt;p&gt;(First few times looking at the Dockerfile parser I was confused but it makes sense eventually.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;Honestly? I got tired of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sitting around waiting for builds (so much time wasted)&lt;/li&gt;
&lt;li&gt;Explaining the same Docker caching stuff over and over&lt;/li&gt;
&lt;li&gt;Forgetting these rules myself and having to relearn them every few months&lt;/li&gt;
&lt;li&gt;Watching our CI bill go up because of dumb mistakes (my manager wasn't happy about that one)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So yeah. Built a tool that explains it for me. Now when someone asks I just go "run layerlint" and we're good. Plus I can share the repo instead of typing the same explanation in Slack for the 100th time.&lt;/p&gt;

&lt;p&gt;If this saves you 5 minutes a day, cool. If it saves your whole team hours every week? Even better. That's the goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Builds take forever&lt;/li&gt;
&lt;li&gt;Burning through CI minutes (and money)&lt;/li&gt;
&lt;li&gt;Everyone's annoyed&lt;/li&gt;
&lt;li&gt;"Why is this so slow?" (nobody knows)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Builds are actually fast&lt;/li&gt;
&lt;li&gt;Cache works like it's supposed to&lt;/li&gt;
&lt;li&gt;CI catches bad Dockerfiles automatically&lt;/li&gt;
&lt;li&gt;You get your time back&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool's free, it's open source, and it literally takes 30 seconds to run. So like... just try it? Worst case you wasted 30 seconds. Best case you save hours.&lt;/p&gt;

&lt;p&gt;I mean you've read this far, might as well give it a shot right?&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%2Fx4je2zl6xrfkpwdnni0q.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%2Fx4je2zl6xrfkpwdnni0q.png" alt="Layerlint logo" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;Alright so if you wanna try 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="c"&gt;# Install it&lt;/span&gt;
curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://raw.githubusercontent.com/vviveksharma/layerLint/main/install.sh | sh

&lt;span class="c"&gt;# Point it at your Dockerfile&lt;/span&gt;
./layerlint scan &lt;span class="nt"&gt;--dockerfile&lt;/span&gt; Dockerfile

&lt;span class="c"&gt;# Fix whatever it complains about&lt;/span&gt;

&lt;span class="c"&gt;# Add it to your CI&lt;/span&gt;

&lt;span class="c"&gt;# Profit (aka faster builds)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/vviveksharma/layerLint" rel="noopener noreferrer"&gt;github.com/vviveksharma/layerLint&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All the rules:&lt;/strong&gt; &lt;a href="https://github.com/vviveksharma/layerLint/blob/main/docs/rules.md" rel="noopener noreferrer"&gt;docs/rules.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI setup guide:&lt;/strong&gt; &lt;a href="https://github.com/vviveksharma/layerLint/blob/main/docs/ci-cd-integration.md" rel="noopener noreferrer"&gt;docs/ci-cd-integration.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download page:&lt;/strong&gt; &lt;a href="https://github.com/vviveksharma/layerLint/releases" rel="noopener noreferrer"&gt;github.com/vviveksharma/layerLint/releases&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If it helps, star the repo maybe? If it doesn't help, open an issue and tell me what's broken.&lt;/p&gt;

&lt;p&gt;Anyway. Go fix your Dockerfiles. Future you will be grateful.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;MIT License. Built it because I got lazy and tired of explaining Docker caching.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Comments Section Starter
&lt;/h2&gt;

&lt;p&gt;What's your worst Dockerfile horror story? I wanna hear it. Drop it in the comments.&lt;/p&gt;

&lt;p&gt;Bonus points if it involved &lt;code&gt;npm install&lt;/code&gt; in production or a 2GB Docker image for a hello world app.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>performance</category>
      <category>tooling</category>
    </item>
    <item>
      <title>The Art of Distributed Locking: Implementing Redlock in Production</title>
      <dc:creator>Vivek Sharma</dc:creator>
      <pubDate>Thu, 21 May 2026 13:35:09 +0000</pubDate>
      <link>https://dev.to/vviveksharma/the-art-of-distributed-locking-implementing-redlock-in-production-139h</link>
      <guid>https://dev.to/vviveksharma/the-art-of-distributed-locking-implementing-redlock-in-production-139h</guid>
      <description>&lt;p&gt;So here's a fun story. I deployed a feature on a Friday evening (I know, I know—rookie mistake), grabbed dinner, and crashed. Saturday morning, I'm scrolling through my phone with coffee, and Sentry is absolutely &lt;em&gt;screaming&lt;/em&gt; at me. Red everywhere. Database alerts going nuclear.&lt;/p&gt;

&lt;p&gt;I'm thinking, "Wait, we have caching. What the hell?"&lt;/p&gt;

&lt;p&gt;Turns out? Cache stampede. And boy, did it hit hard.&lt;/p&gt;

&lt;p&gt;Here's the thing about cache stampede—it's sneaky. Your cache expires, and suddenly every request that was waiting decides to regenerate that cache entry at the exact same time. It's like a thousand threads all going "I'll fix it!" and then immediately DDoS'ing your own database. Not fun.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking Your Poison: Caching Strategies
&lt;/h2&gt;

&lt;p&gt;Before we dive into fixes, let's talk strategy. There's three main approaches people use and each and every has there own Pro's and Con's depending on the use-case and conditions of the archetecture we use any one of them&lt;/p&gt;

&lt;p&gt;I like to think about it with the librarian metaphor:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache-Aside&lt;/strong&gt; — The "I'll deal with it later" approach. Someone asks for a book? Fine, I'll walk down to the dusty basement (database), grab it, and keep it on my desk for next time. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Good stuff: Dead simple. Doesn't waste space on books nobody wants.&lt;/li&gt;
&lt;li&gt;The catch: That first person? Yeah, they're waiting while you trek to the basement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Write-Through&lt;/strong&gt; — The obsessive organizer. Every new book gets logged twice—one on the desk, one in the basement. No exceptions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Good stuff: Your desk is always current. No surprises.&lt;/li&gt;
&lt;li&gt;The catch: Everything takes forever because you're essentially doing double work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Write-Behind&lt;/strong&gt; — My personal favorite when I'm feeling risky. Stack books on the desk all day, then at 5 PM, haul everything to the basement in one trip.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Good stuff: Stupid fast writes. Users love it.&lt;/li&gt;
&lt;li&gt;The catch: If your desk catches fire before 5 PM (server crashes), you're toast. Hope you like data loss.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Actually Happens During a Cache Stampede
&lt;/h2&gt;

&lt;p&gt;Okay, back to my Saturday morning disaster.&lt;/p&gt;

&lt;p&gt;You know those sneaker drops? Picture the Travis Scott Jordan collab. Nike's got 1,000 pairs. There's 10,000 people outside losing their minds. The store opens, and everyone rushes the counter simultaneously. Security? Overwhelmed. Counter staff? Crying. Total chaos.&lt;/p&gt;

&lt;p&gt;That's cache stampede.&lt;/p&gt;

&lt;p&gt;Your cache key is the locked store doors. Your database is the overwhelmed counter staff. The second that cache expires, every single waiting request bulldozes through at once. No line, no rate limiting, just pure pandemonium hitting your poor database.&lt;/p&gt;

&lt;p&gt;And that's exactly what I woke up to that Saturday. Fun times.&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%2F12rn46jkrd0l9zf7hkte.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%2F12rn46jkrd0l9zf7hkte.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now comes the hero of our story: &lt;code&gt;Distributed Locking with Redlock&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  So What's Redlock Actually Doing?
&lt;/h2&gt;

&lt;p&gt;Think of it like a ticket system at a busy restaurant. &lt;/p&gt;

&lt;p&gt;Going back to our sneaker drop chaos—imagine if Nike smartened up. Instead of letting everyone mob the counter, they hand out numbered tickets at the door. "We've got 1,000 pairs, here's your ticket, we'll call you when it's your turn."&lt;/p&gt;

&lt;p&gt;Now people can grab coffee, browse around, whatever. The store staff aren't getting trampled. When your number's called, you walk up calmly and complete your purchase. Once all 1,000 tickets are gone, anyone else showing up just gets told "Sorry, sold out"—no point in hanging around.&lt;/p&gt;

&lt;p&gt;That's essentially what distributed locking does for your cache regeneration. Only one request gets the "ticket" (lock) to rebuild the cache. Everyone else? They either wait for that first request to finish, or they get served stale data while the rebuild happens in the background. No stampede, no database meltdown. &lt;/p&gt;

&lt;p&gt;Similarly in our system when the cache expires, the first request shouts, "I'm going to the DB! Here is my ID." It grabs a "Lock" (token) from Redis. Every other request sees that someone already has the token. They don't rush the DB; they just wait a few milliseconds and check the cache again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wait, But What Makes Redlock Special?
&lt;/h2&gt;

&lt;p&gt;Here's where I almost screwed up again. My first implementation just used a single Redis instance for locking. Worked great for about three weeks.&lt;/p&gt;

&lt;p&gt;Then our Redis instance hiccuped. Not even a full crash—just a brief restart during a deployment. And guess what happened? All my fancy locks disappeared. Cache stampede part two, electric boogaloo.&lt;/p&gt;

&lt;p&gt;So here's the thing about regular Redis locking: it's a single point of failure. If that one Redis node goes down, your entire locking mechanism vanishes. Every request suddenly thinks "oh, there's no lock, I'll go hit the database!" And we're back to Saturday morning hell.&lt;/p&gt;

&lt;p&gt;Enter Redlock. Salvatore Sanfilippo (the guy who created Redis) came up with this algorithm specifically to solve that problem. Instead of trusting just one Redis instance with your locks, Redlock spreads the responsibility across multiple independent Redis nodes—usually 5.&lt;/p&gt;

&lt;p&gt;Here's how it works: When you want to acquire a lock, you don't just ask one Redis node. You ask all 5 nodes, "Hey, can I get a lock on this key?" If you get a "yes" from the majority (at least 3 out of 5), you win the lock. If one Redis node crashes or gets disconnected? No big deal—you still have 4 others, and you only needed 3 to agree anyway.&lt;/p&gt;

&lt;p&gt;It's like needing 3 out of 5 signatures to authorize a bank transaction. One person's on vacation? Doesn't matter. Two people? Okay, now we have a problem, but at that point, you've got bigger issues than cache stampedes.&lt;/p&gt;

&lt;p&gt;The catch? You need to run 5 independent Redis instances. More infrastructure, more complexity. But honestly? After that second incident, I stopped complaining about the extra nodes.&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%2F4gmm2epb611lwuhhi823.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%2F4gmm2epb611lwuhhi823.png" alt=" " width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Show Me the Code
&lt;/h2&gt;

&lt;p&gt;Alright, enough theory. Here's how I actually implemented this using Go's &lt;code&gt;redsync&lt;/code&gt; library:&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;"time"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/go-redsync/redsync/v4"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/go-redsync/redsync/v4/redis/goredis/v9"&lt;/span&gt;
    &lt;span class="n"&gt;goredislib&lt;/span&gt; &lt;span class="s"&gt;"github.com/redis/go-redis/v9"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;GetUserProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UserProfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="o"&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;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user:profile:%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Try getting from cache first&lt;/span&gt;
    &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;redisClient&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="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Cache hit! We're good&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;deserializeProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached&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="c"&gt;// Cache miss - we need to hit the DB&lt;/span&gt;
    &lt;span class="c"&gt;// But first, let's get a lock so we don't all do it at once&lt;/span&gt;
    &lt;span class="n"&gt;lockKey&lt;/span&gt; &lt;span class="o"&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;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"lock:%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mutex&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;redSync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewMutex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;redsync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithExpiry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&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;redsync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTries&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="c"&gt;// Don't retry, fail fast&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Try to acquire the lock&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;mutex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&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="c"&gt;// Someone else got the lock, let them do the work&lt;/span&gt;
        &lt;span class="c"&gt;// We'll just wait a bit and check cache again&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;100&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;Millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;redisClient&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="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;deserializeProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached&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="c"&gt;// Still not there? Return stale data or error gracefully&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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;mutex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Double-check cache (someone might've filled it while we waited)&lt;/span&gt;
    &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redisClient&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="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;deserializeProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached&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="c"&gt;// Okay, we actually need to hit the DB&lt;/span&gt;
    &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetUserProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&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="c"&gt;// Cache it for next time&lt;/span&gt;
    &lt;span class="n"&gt;redisClient&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="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&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;Minute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;profile&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic happens in those few lines where we try to grab the lock. If we get it, we're the chosen one who hits the database. If we don't? We back off, wait a tiny bit, and check if whoever got the lock already populated the cache. Simple, but it saved my Saturday morning.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>caching</category>
      <category>systemdesign</category>
      <category>programming</category>
    </item>
    <item>
      <title>Stop Wasting Hours on "Project Setup": How I Automated Production-Ready Go APIs</title>
      <dc:creator>Vivek Sharma</dc:creator>
      <pubDate>Wed, 15 Apr 2026 18:30:52 +0000</pubDate>
      <link>https://dev.to/vviveksharma/stop-wasting-hours-on-project-setup-how-i-automated-production-ready-go-apis-3l45</link>
      <guid>https://dev.to/vviveksharma/stop-wasting-hours-on-project-setup-how-i-automated-production-ready-go-apis-3l45</guid>
      <description>&lt;h3&gt;
  
  
  Why I built a CLI to bridge the gap between "Hello World" and a production-ready microservice in 60 seconds.
&lt;/h3&gt;




&lt;h2&gt;
  
  
  The "Boilerplate Tax"
&lt;/h2&gt;

&lt;p&gt;As a backend engineer, I noticed a recurring pattern. Every time I started a new service, I spent the first few hours doing manual labor that had nothing to do with business logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting up &lt;strong&gt;Structured Logging&lt;/strong&gt; (Zap) with request correlation IDs.&lt;/li&gt;
&lt;li&gt;Configuring &lt;strong&gt;Prometheus Metrics&lt;/strong&gt; and health-check probes.&lt;/li&gt;
&lt;li&gt;Hardening &lt;strong&gt;Security Headers&lt;/strong&gt; (HSTS, CSP, XSS protection).&lt;/li&gt;
&lt;li&gt;Writing &lt;strong&gt;Dockerfiles&lt;/strong&gt; with multi-stage builds and non-root users.&lt;/li&gt;
&lt;li&gt;Setting up &lt;strong&gt;Database Migration&lt;/strong&gt; runners.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what I call the "Boilerplate Tax." It kills momentum and, worse, it leads to inconsistencies across teams. I wanted a tool that didn't just give me folders, but a &lt;strong&gt;production-ready foundation.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing Goforge 🚀
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Goforge&lt;/strong&gt; is a CLI tool built for Gophers who value speed without compromising on engineering standards. It scaffolds a complete API based on &lt;strong&gt;Clean Architecture&lt;/strong&gt; principles.&lt;/p&gt;

&lt;h3&gt;
  
  
  🎯 Key Features:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework Choice:&lt;/strong&gt; Toggle between &lt;strong&gt;Fiber&lt;/strong&gt; (for high performance) and &lt;strong&gt;Gin&lt;/strong&gt; (for a massive ecosystem) using simple flags.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security First:&lt;/strong&gt; Integrated middleware for security headers and non-root Docker builds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability:&lt;/strong&gt; Built-in Prometheus &lt;code&gt;/metrics&lt;/code&gt; and Zap JSON logging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ops Ready:&lt;/strong&gt; Pre-configured PostgreSQL, Redis, and &lt;code&gt;golang-migrate&lt;/code&gt; support.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Goforge? (The Architectural Perspective)
&lt;/h2&gt;

&lt;p&gt;In the Go community, there is a constant debate between "Standard Library" purists and "Framework" users. While the standard library is powerful, modern cloud-native development requires significant "plumbing."&lt;/p&gt;

&lt;p&gt;I designed Goforge to be &lt;strong&gt;opinionated where it matters&lt;/strong&gt; (Security and Observability) but &lt;strong&gt;flexible where you need it&lt;/strong&gt; (Business Logic).&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Observability is Not Optional
&lt;/h3&gt;

&lt;p&gt;In a distributed system, you can’t manage what you can’t measure. Goforge includes Kubernetes-ready &lt;code&gt;/health/live&lt;/code&gt; and &lt;code&gt;/health/ready&lt;/code&gt; endpoints out of the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Multi-Framework Paradox
&lt;/h3&gt;

&lt;p&gt;One of the hardest decisions is picking a framework. Goforge allows you to choose your engine while keeping the internal structure (Handlers -&amp;gt; Services -&amp;gt; Repositories) identical.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Developer Experience (DX)
&lt;/h3&gt;

&lt;p&gt;I’ve included a comprehensive &lt;code&gt;Makefile&lt;/code&gt; to ensure a "zero-config" local development experience:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make up  &lt;span class="c"&gt;# Spins up API, PostgreSQL, and Redis in Docker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How to Get Started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option 1: Go Install (Recommended)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/viveksharma/goforge@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option 2: Download Binary
&lt;/h3&gt;

&lt;p&gt;Download pre-built binaries from the &lt;a href="https://github.com/viveksharma/goforge/releases" rel="noopener noreferrer"&gt;Releases&lt;/a&gt; page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 3: Install from Latest Main
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/vviveksharma/Goforge-CLI.git
&lt;span class="nb"&gt;cd &lt;/span&gt;goforge
make &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs the latest development version to &lt;code&gt;$GOPATH/bin&lt;/code&gt; (usually &lt;code&gt;~/go/bin&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 4: Build from Source
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/vviveksharma/Goforge-CLI.git
&lt;span class="nb"&gt;cd &lt;/span&gt;goforge
go build &lt;span class="nt"&gt;-o&lt;/span&gt; goforge ./cmd/goforge
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;goforge /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, forge your first production 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="c"&gt;# Create a Fiber-based API&lt;/span&gt;
goforge create payment-service &lt;span class="nt"&gt;--server&lt;/span&gt; fiber

&lt;span class="nb"&gt;cd &lt;/span&gt;payment-service
make up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Goforge is open-source and MIT licensed. I’d love for you to try it out, break it, and help me build the ultimate starting point for Go developers.&lt;/p&gt;

&lt;p&gt;Check out the repo and give it a star! ⭐&lt;br&gt;
👉 &lt;a href="https://github.com/vviveksharma/Goforge-CLI" rel="noopener noreferrer"&gt;https://github.com/vviveksharma/Goforge-CLI&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
