<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Stashito blog</title>
        <link>https://stashito.com/blog/</link>
        <description>Notes on running a pull-through cache for Docker/OCI images, from the people building Stashito.</description>
        <lastBuildDate>Tue, 27 Oct 2026 21:49:47 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Stashito blog</title>
            <url>https://stashito.com/icon-512.png</url>
            <link>https://stashito.com/blog/</link>
        </image>
        <copyright>Ricardo Marques</copyright>
        <item>
            <title><![CDATA[why I built Stashito]]></title>
            <link>https://stashito.com/blog/posts/why-i-built-stashito</link>
            <guid isPermaLink="false">https://stashito.com/blog/posts/why-i-built-stashito</guid>
            <pubDate>Sun, 25 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[We went multi-cloud, Google egress and Docker Hub rate limits started to hurt, and every option I found was enterprise software. I wanted a small, fast cache that could grow. I also missed registry:2.]]></description>
            <content:encoded><![CDATA[
<p>At work we are mostly on Google Cloud, and Artifact Registry is the source of truth for our images. Then we went multi-cloud. The second provider is smaller and cheaper, and it has no artifact registry of its own, so every node there pulled straight from Google.</p>
<p>Three things hurt at once:</p>
<ul>
<li>Egress. Every pull of the same image from every node on the other cloud left Google and was billed on the way out.</li>
<li>Docker Hub rate limits. Base images came from Hub, and a fleet of nodes pulling anonymously hits the cap fast.</li>
<li>Latency. Pulling across clouds is slower than pulling across a rack, and image pulls are on the critical path of every deploy and every CI job.</li>
</ul>
<p>The fix is obvious: a pull-through cache on the second cloud. Pull each image once, serve it locally after that, one egress charge per image instead of one per node.</p>
<h2 id="the-search" tabindex="-1">the search <a class="header-anchor" href="#the-search" aria-label="Permalink to &quot;the search&quot;">&ZeroWidthSpace;</a></h2>
<p>I looked at what existed and everything I found was enterprise software. Platforms with a database, a cache layer, a UI, a licence tier and a JVM. I ended up on the free version of Artifactory, which worked and was the only decent option I could find at the time.</p>
<p>I also missed <code>registry:2</code> in proxy mode. The official Docker registry has had a pull-through cache mode for years, and it does most of what I needed. I did not find it, or did not recognise it for what it was, when I was looking. That is on me, and I am saying it here so nobody reads this post as &quot;there was nothing out there&quot;.</p>
<h2 id="what-i-wanted" tabindex="-1">what I wanted <a class="header-anchor" href="#what-i-wanted" aria-label="Permalink to &quot;what I wanted&quot;">&ZeroWidthSpace;</a></h2>
<p>A thing that is simple, fast and reliable. A single container that needs a volume and nothing else, with all of its configuration visible in the compose file, and not written in Java. The same thing should work on a node pool at work and on the dedicated servers I rent for my own projects. I have the same problem there on a smaller scale: my own images live in Amazon ECR, and every host pulling them directly from AWS costs money and waits on the network each time. Stashito sits in front of ECR there for the same two reasons, cost and latency.</p>
<p>And something that could grow. OCI images were the pain that week, but the same shape applies to Python packages, npm, apt, Go modules. The tools that cover all of those are the enterprise ones. I wanted the small version of that idea, starting with images.</p>
<h2 id="what-stashito-is" tabindex="-1">what Stashito is <a class="header-anchor" href="#what-stashito-is" aria-label="Permalink to &quot;what Stashito is&quot;">&ZeroWidthSpace;</a></h2>
<p>A single Go binary implementing the OCI Distribution Spec, shipped as <code>rcm7/stashito</code> for eight Linux platforms.</p>
<div class="language-yaml vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0" v-pre=""><code><span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">services</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  stashito</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    image</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">rcm7/stashito:0</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    environment</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      PORT</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"8080"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      STORAGE_PATH</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"/data/stashito"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      LOG_LEVEL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"info"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      LOG_FORMAT</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"text"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      TAG_TTL</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"60s"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">      UPSTREAM_DOCKERHUB_HOST</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"registry-1.docker.io"</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    volumes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">stashito_data:/data/stashito</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    ports</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      - </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"8080:8080"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">volumes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  stashito_data</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span></code></pre>
</div><p>Six env vars, all required, no hidden defaults. Add an <code>UPSTREAM_&lt;ALIAS&gt;_HOST</code> per registry and one instance fronts Docker Hub, GHCR, Quay, Artifact Registry, ACR and ECR at once, addressed as <code>localhost:8080/&lt;alias&gt;/&lt;image&gt;</code>. Set <code>MIRROR_UPSTREAM</code> and it works as a plain <code>registry-mirrors</code> entry, so existing <code>docker pull postgres</code> lines are served from the cache without changing anything.</p>
<p>In use: the first pull is a MISS from upstream, every later pull is a HIT from local disk. Digests and blobs are never revalidated; tags are, after <code>TAG_TTL</code>. If upstream is down, cached content is served stale. <code>MAX_STORAGE</code> caps the cache and evicts least recently pulled content inline, and a blob that will not fit on the filesystem is refused with <code>507</code> before any byte is written. ECR tokens are fetched and rotated for you. <code>/healthz</code> and optional Prometheus <code>/metrics</code>. The <a href="https://stashito.com/docs/" target="_blank" rel="noreferrer">docs</a> have the rest.</p>
<h2 id="stashito-and-registry-2" tabindex="-1">Stashito and registry:2 <a class="header-anchor" href="#stashito-and-registry-2" aria-label="Permalink to &quot;Stashito and registry:2&quot;">&ZeroWidthSpace;</a></h2>
<p>Same niche. If <code>registry:2</code> proxy mode does what you need, use it: it has TLS, auth, S3 and GCS backends and a decade of production behind it.</p>
<p>What Stashito does differently, as of October 2026: several upstreams in one instance instead of one remote per instance, env vars instead of a YAML file, a size cap with LRU eviction instead of a TTL plus manual garbage collection, stale serving when upstream is unreachable, and ECR handled natively. Both projects will move, so check the docs of each before deciding. For now the goal is that it is easy to get to and easy to run.</p>
<h2 id="what-it-does-not-do-yet" tabindex="-1">what it does not do yet <a class="header-anchor" href="#what-it-does-not-do-yet" aria-label="Permalink to &quot;what it does not do yet&quot;">&ZeroWidthSpace;</a></h2>
<p>As of October 2026:</p>
<ul>
<li>It does not accept pushes. It only caches what it pulls from upstream.</li>
<li>No authentication on the pull side and no TLS. Run it on a private network, or behind something that does both.</li>
<li>No web UI.</li>
<li>OCI images only.</li>
<li>Instances do not share state. Two Stashitos are two caches.</li>
<li>No scanning, no replication, no retention policies beyond the size cap.</li>
</ul>
<p>Any of these may change. None of them is a design decision, they are the things I have not needed myself so far.</p>
<h2 id="where-it-stands" tabindex="-1">where it stands <a class="header-anchor" href="#where-it-stands" aria-label="Permalink to &quot;where it stands&quot;">&ZeroWidthSpace;</a></h2>
<p>It is small, it is fast, and it does one thing. Code is AGPL-3.0 at <a href="https://github.com/RCM7/stashito" target="_blank" rel="noreferrer">github.com/RCM7/stashito</a>, and the <a href="https://stashito.com/docs/quickstart" target="_blank" rel="noreferrer">quickstart</a> takes a few minutes. If you run it and something is missing, open an issue.</p>
]]></content:encoded>
            <author>Ricardo</author>
        </item>
        <item>
            <title><![CDATA[hello from stashito.com]]></title>
            <link>https://stashito.com/blog/posts/hello</link>
            <guid isPermaLink="false">https://stashito.com/blog/posts/hello</guid>
            <pubDate>Sat, 24 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[There is a blog now. Posts on caching Docker images, release notes and what running Stashito looks like in practice.]]></description>
            <content:encoded><![CDATA[
<p>Stashito has a blog. Expect short posts: how the pull-through cache behaves under real CI load, what each release changes, and the trade-offs behind the design.</p>
<p>Subscribe with the <a href="https://stashito.com/blog/feed.xml">RSS feed</a>. The <a href="https://stashito.com/docs/" target="_blank" rel="noreferrer">docs</a> stay the place for how to run it.</p>
]]></content:encoded>
            <author>Ricardo</author>
        </item>
    </channel>
</rss>