<?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: jtodic</title>
    <description>The latest articles on DEV Community by jtodic (@jtodic).</description>
    <link>https://dev.to/jtodic</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%2F3646479%2Fb619f91f-a950-4ddf-a1f1-0e96968fe38b.png</url>
      <title>DEV Community: jtodic</title>
      <link>https://dev.to/jtodic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jtodic"/>
    <language>en</language>
    <item>
      <title>Analyzing Docker Images Without Downloading Them</title>
      <dc:creator>jtodic</dc:creator>
      <pubDate>Wed, 31 Dec 2025 08:33:03 +0000</pubDate>
      <link>https://dev.to/jtodic/analyzing-docker-images-without-downloading-them-5d3f</link>
      <guid>https://dev.to/jtodic/analyzing-docker-images-without-downloading-them-5d3f</guid>
      <description>&lt;p&gt;I built a tool that analyzes Docker image sizes directly from container registries — without pulling the images.&lt;/p&gt;

&lt;p&gt;I needed to compare 50 versions of a Docker image to find when it got bloated. Pulling all tags would be slow and use gigabytes of bandwidth. Instead, I got all the data in seconds using registry metadata APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Key Insight
&lt;/h2&gt;

&lt;p&gt;When you &lt;code&gt;docker pull&lt;/code&gt;, the client fetches a manifest, then a config, then downloads all layers. The manifest and config are tiny JSON files (~10KB total). The layers are gigabytes.&lt;/p&gt;

&lt;p&gt;But the manifest already contains layer sizes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"layers"&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="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;29536818&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:af6eca94..."&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;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1841029843&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:c46e201c..."&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;And the config contains the Dockerfile commands that created each layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"history"&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="nl"&gt;"created_by"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"apt-get install -y nginx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"empty_layer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;"created_by"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ENV PATH=/usr/local/bin:$PATH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"empty_layer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;Match them up (skipping &lt;code&gt;empty_layer: true&lt;/code&gt; entries like ENV and LABEL), and you get layer sizes with their Dockerfile commands. No layer downloads needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;p&gt;The tool reads &lt;code&gt;~/.docker/config.json&lt;/code&gt; — the same file Docker uses. For systems with credential helpers (macOS Keychain, Windows Credential Manager), it calls &lt;code&gt;docker-credential-&amp;lt;helper&amp;gt; get&lt;/code&gt; directly.&lt;/p&gt;

&lt;p&gt;You still need to run &lt;code&gt;docker login&lt;/code&gt; first for private registries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Architecture Images
&lt;/h2&gt;

&lt;p&gt;Modern images return a manifest list instead of a manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifests"&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="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:abc..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"platform"&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;"architecture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"amd64"&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;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:def..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"platform"&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;"architecture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arm64"&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;One extra request to fetch the right platform's manifest. Still under 10KB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Data transferred&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pull all images&lt;/td&gt;
&lt;td&gt;GBs (even with layer caching)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metadata only&lt;/td&gt;
&lt;td&gt;~500 KB (50 tags × ~10KB)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/jtodic/docker-time-machine.git
&lt;span class="nb"&gt;cd &lt;/span&gt;docker-time-machine
go mod download 
make build
make &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Public images&lt;/span&gt;
./dtm registry nginx &lt;span class="nt"&gt;--last&lt;/span&gt; 20

&lt;span class="c"&gt;# Private registries (after docker login)&lt;/span&gt;
./dtm registry your-registry.com/app &lt;span class="nt"&gt;--last&lt;/span&gt; 50 &lt;span class="nt"&gt;--format&lt;/span&gt; chart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>performance</category>
      <category>devops</category>
      <category>docker</category>
      <category>tooling</category>
    </item>
    <item>
      <title>🐳 I built a tool to find exactly which commit bloated your Docker image</title>
      <dc:creator>jtodic</dc:creator>
      <pubDate>Thu, 04 Dec 2025 13:57:29 +0000</pubDate>
      <link>https://dev.to/jtodic/i-built-a-tool-to-find-exactly-which-commit-bloated-your-docker-image-2h79</link>
      <guid>https://dev.to/jtodic/i-built-a-tool-to-find-exactly-which-commit-bloated-your-docker-image-2h79</guid>
      <description>&lt;p&gt;Ever wondered "why is my Docker image suddenly 500MB bigger?" and had to git bisect through builds manually? I made Docker Time Machine (DTM) - it walks through your git history, builds the image at each commit, and shows you exactly where the bloat happened.&lt;/p&gt;

&lt;p&gt;Example: &lt;br&gt;
dtm analyze --format chart&lt;/p&gt;

&lt;p&gt;Gives you interactive charts showing size trends, layer-by-layer comparisons, and highlights the exact commit that added the most weight (or optimized it).&lt;br&gt;
It's fast too - leverages Docker's layer cache so analyzing 20+ commits takes minutes, not hours.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/jtodic/docker-time-machine" rel="noopener noreferrer"&gt;https://github.com/jtodic/docker-time-machine&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Would love feedback from anyone who's been burned by mystery image bloat before 🔥&lt;/p&gt;

&lt;p&gt;Examples of reports:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcp8hkcnee627ifv0xav.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%2Fzcp8hkcnee627ifv0xav.png" alt=" " width="800" height="313"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq6si9esihysd6ioy79p8.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%2Fq6si9esihysd6ioy79p8.png" alt=" " width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>performance</category>
      <category>docker</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
