<?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 Westgaard</title>
    <description>The latest articles on DEV Community by Daniel Westgaard (@danielwe).</description>
    <link>https://dev.to/danielwe</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%2F3876610%2F1d0996da-2e1c-4cac-979f-f2a9d33d8b15.jpg</url>
      <title>DEV Community: Daniel Westgaard</title>
      <link>https://dev.to/danielwe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danielwe"/>
    <language>en</language>
    <item>
      <title>How to Find Every Consumer of Your Go Module</title>
      <dc:creator>Daniel Westgaard</dc:creator>
      <pubDate>Mon, 20 Apr 2026 22:00:00 +0000</pubDate>
      <link>https://dev.to/danielwe/how-to-find-every-consumer-of-your-go-module-5cbk</link>
      <guid>https://dev.to/danielwe/how-to-find-every-consumer-of-your-go-module-5cbk</guid>
      <description>&lt;p&gt;&lt;em&gt;You maintain a shared Go module. A breaking API change is coming. Which repos across your org import it — and at which version?&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;You own &lt;code&gt;github.com/company/platform-lib&lt;/code&gt;. Maybe it's your internal observability library that every service imports for structured logging and trace propagation. Maybe it's a shared HTTP middleware package that standardises authentication, rate limiting, and request IDs across all your APIs. Maybe it's a configuration loader that services use to pull settings from your config management system at startup.&lt;/p&gt;

&lt;p&gt;It started as a few shared utilities to avoid copy-pasting. Twenty services adopted it. Then more. Some import it directly and call into it heavily. Some pull it in transitively because another shared library depends on it. Some are on the latest version. Some are three majors behind.&lt;/p&gt;

&lt;p&gt;Now you need to change it. Maybe you're removing a deprecated function that's been generating noise in the godoc for two years. Maybe you're bumping to &lt;code&gt;v2&lt;/code&gt; because you need to restructure the package API in a backwards-incompatible way. Maybe you're changing the signature of a middleware constructor to accept an options struct instead of positional arguments.&lt;/p&gt;

&lt;p&gt;The question is the same one that runs through &lt;a href="https://riftmap.dev/blog/series/find-every-consumer/" rel="noopener noreferrer"&gt;every post in this series&lt;/a&gt;: &lt;strong&gt;which repos across the org import this module, and at which version?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario
&lt;/h2&gt;

&lt;p&gt;Here is what Go module consumption looks like in practice. Your platform team publishes a module, and other repos declare it in their &lt;code&gt;go.mod&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="c"&gt;// service-a/go.mod&lt;/span&gt;
&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;

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

&lt;span class="n"&gt;require&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="m"&gt;.8.3&lt;/span&gt;
    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;middleware&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="m"&gt;.1.0&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some repos import the module directly and use it throughout their code. Others pull it in as an indirect dependency — because a library they depend on imports your module — and Go records it with an &lt;code&gt;// indirect&lt;/code&gt; comment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;require&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="m"&gt;.8.3&lt;/span&gt;
    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;some&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="m"&gt;.4.1&lt;/span&gt; &lt;span class="c"&gt;// indirect&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some repos are pinned to a specific release tag. Some reference a pseudo-version pointing to a specific commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;github.com/company/platform-lib v0.0.0-20260301153042-a1b2c3d4e5f6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some have overridden the module source entirely with a &lt;code&gt;replace&lt;/code&gt; directive — pointing to a local checkout or a patched fork:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;replace&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;../&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of these represent consumption of your module. None of them look the same. And right now, they live in different repos, owned by different teams, that you probably can't enumerate off the top of your head.&lt;/p&gt;

&lt;h2&gt;
  
  
  What existing tools give you (and where they stop)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  pkg.go.dev
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;pkg.go.dev&lt;/code&gt; is the official Go package documentation and discovery site. For public modules, it indexes which packages import a given package and shows an importer list. This is the closest existing tool to "who uses this?"&lt;/p&gt;

&lt;p&gt;The limitation is sharp: &lt;code&gt;pkg.go.dev&lt;/code&gt; only indexes public modules fetched through the public Go module proxy at &lt;code&gt;proxy.golang.org&lt;/code&gt;. If your module is hosted on an internal GitLab or GitHub instance, protected by authentication, and routed through a private &lt;code&gt;GOPROXY&lt;/code&gt;, it does not appear in &lt;code&gt;pkg.go.dev&lt;/code&gt; at all. Every consumer in your org is invisible to it.&lt;/p&gt;

&lt;p&gt;For most platform teams managing internal shared libraries, this makes &lt;code&gt;pkg.go.dev&lt;/code&gt; entirely useless for the question at hand. Even for public modules, the importer list only covers packages that &lt;code&gt;pkg.go.dev&lt;/code&gt; has itself indexed — not a comprehensive count of every organisation that imports the module.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;go mod graph&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;go mod graph&lt;/code&gt; command prints the full module dependency graph for the current module — a recursive list of every direct and transitive dependency. It's useful for understanding what a single module pulls in.&lt;/p&gt;

&lt;p&gt;But it only works outward from the current module. There is no &lt;code&gt;go mod reverse-graph&lt;/code&gt;. You'd have to clone every repo in the org, run &lt;code&gt;go mod graph&lt;/code&gt; in each one, collect the output, and invert it yourself — which is exactly the manual trawl the question is trying to avoid. And the moment you finish, the results are stale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Renovate and Dependabot
&lt;/h3&gt;

&lt;p&gt;Both Renovate and Dependabot support Go modules as a first-class ecosystem. They detect &lt;code&gt;require&lt;/code&gt; entries in &lt;code&gt;go.mod&lt;/code&gt; and open pull requests when newer versions are published — which means they implicitly know which repos consume which modules.&lt;/p&gt;

&lt;p&gt;But they don't expose that knowledge as a queryable view. You cannot ask Renovate or Dependabot "show me every repo in the org that imports &lt;code&gt;platform-lib&lt;/code&gt;, and what version each is on." They react to new versions being published. The reverse — who's consuming the current version before you publish a breaking one — is not something they surface.&lt;/p&gt;

&lt;p&gt;Both tools also only cover repos where they've been configured. A team that hasn't set up Go module support in their config, or doesn't use either tool at all, is invisible.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub code search
&lt;/h3&gt;

&lt;p&gt;You can search for your module path across all repos in an org:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;org:my-org "github.com/company/platform-lib"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This finds &lt;code&gt;go.mod&lt;/code&gt; files that contain the module path and gives you a list of repos. For a one-off audit, it's a workable starting point.&lt;/p&gt;

&lt;p&gt;The familiar problems apply: results don't include version numbers without opening each file individually, the search index lags behind recent commits, and it doesn't account for &lt;code&gt;replace&lt;/code&gt; directives. If a repo has replaced your module with a fork or local path, the &lt;code&gt;require&lt;/code&gt; entry still appears — but what's actually being compiled is different. Code search can't tell you that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Athens (GOPROXY)
&lt;/h3&gt;

&lt;p&gt;Athens is a widely-used open-source Go module proxy that organisations run internally for private modules and caching. It serves module downloads and maintains a local cache.&lt;/p&gt;

&lt;p&gt;Athens tracks which modules were fetched and at which version. What it doesn't track is which &lt;em&gt;source repo&lt;/em&gt; triggered the download. A build server fetching &lt;code&gt;platform-lib v1.8.3&lt;/code&gt; is recorded as a download event, but not which team's CI pipeline it was, which service repo it was building, or which &lt;code&gt;go.mod&lt;/code&gt; declared the dependency. Athens is a supply-chain cache, not a consumption graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is harder than it looks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Major version suffixes create distinct module identities.&lt;/strong&gt; Go's module system treats &lt;code&gt;github.com/company/platform-lib&lt;/code&gt; and &lt;code&gt;github.com/company/platform-lib/v2&lt;/code&gt; as completely separate modules. A repo that imports &lt;code&gt;platform-lib/v2&lt;/code&gt; has a different dependency than one that imports &lt;code&gt;platform-lib&lt;/code&gt;. Grepping for &lt;code&gt;platform-lib&lt;/code&gt; finds both — but a tool that tracks module identities needs to recognise that these are different versions of the same thing, and that consumers may be on any combination of &lt;code&gt;v1&lt;/code&gt;, &lt;code&gt;v2&lt;/code&gt;, &lt;code&gt;v3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This also means publishing a new major version doesn't automatically affect v1 consumers. They continue importing the old module path until they explicitly update both the &lt;code&gt;go.mod&lt;/code&gt; entry and all import paths in their source code. Understanding who's still on v1 requires knowing who's importing which module path — and treating those as a unified dependency family.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;replace&lt;/code&gt; directives obscure the real source.&lt;/strong&gt; A &lt;code&gt;replace&lt;/code&gt; directive can substitute your module with a different source — a local path, a fork, or a different module entirely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;replace&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="m"&gt;.8.3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;hotfix&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the outside, the &lt;code&gt;require&lt;/code&gt; entry still references &lt;code&gt;github.com/company/platform-lib&lt;/code&gt;. A scanner that reads only &lt;code&gt;require&lt;/code&gt; entries sees this as a consumer on v1.8.3. But the code being compiled is coming from a patched fork. The apparent consumer is not actually running your module — and a tool that reports it as one gives you a misleading picture of who's affected by a change to the canonical source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Indirect dependencies inflate the consumer list.&lt;/strong&gt; Go's &lt;code&gt;go.mod&lt;/code&gt; includes both direct and indirect dependencies, with &lt;code&gt;// indirect&lt;/code&gt; annotations on the latter. If your module is a transitive dependency — imported by a library that's imported by a service — it appears in that service's &lt;code&gt;go.mod&lt;/code&gt; even though the service's code never directly calls into yours.&lt;/p&gt;

&lt;p&gt;This matters when reasoning about impact. A service that directly imports your module and calls its functions will break if you change the API. A service that has it only as an indirect dependency may not be affected at all, depending on how much of your interface the intermediate library exposes. Treating all &lt;code&gt;go.mod&lt;/code&gt; entries as equivalent overstates the blast radius; ignoring indirect entries understates it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;go.work&lt;/code&gt; workspaces cross module boundaries.&lt;/strong&gt; Go 1.18 introduced workspace mode, where a &lt;code&gt;go.work&lt;/code&gt; file at the root of a checkout declares multiple local modules as part of a unified workspace. When workspace mode is active, dependencies between modules in the &lt;code&gt;go.work&lt;/code&gt; are resolved locally rather than through &lt;code&gt;go.mod&lt;/code&gt; &lt;code&gt;require&lt;/code&gt; declarations.&lt;/p&gt;

&lt;p&gt;Some consumers of your module may be developing against it in a shared workspace, with no explicit &lt;code&gt;require&lt;/code&gt; entry in their &lt;code&gt;go.mod&lt;/code&gt; during development. A scanner that only reads &lt;code&gt;go.mod&lt;/code&gt; files misses these entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vendor directories shadow the dependency graph.&lt;/strong&gt; Repos that vendor their dependencies with &lt;code&gt;go mod vendor&lt;/code&gt; keep a copy of all transitive dependencies under &lt;code&gt;vendor/&lt;/code&gt;, with canonical version information in &lt;code&gt;vendor/modules.txt&lt;/code&gt;. Some repos modify vendored code directly — technically discouraged, but not uncommon in practice. These modifications break the assumption that a version string in &lt;code&gt;go.mod&lt;/code&gt; corresponds exactly to the canonical upstream source. A consumer may appear to be on v1.8.3 while actually running a locally modified version of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the full answer requires
&lt;/h2&gt;

&lt;p&gt;To reliably answer "who imports this module," you need a system that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scans every repo in the org&lt;/strong&gt;, parsing &lt;code&gt;go.mod&lt;/code&gt; files — without requiring opt-in or registration from each team&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handles all reference forms&lt;/strong&gt; — tagged versions, pseudo-versions, and &lt;code&gt;replace&lt;/code&gt; directives — and records what module source is actually being resolved, not just what appears in &lt;code&gt;require&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tracks all major version suffixes&lt;/strong&gt; as distinct but related module identities, so you see the full picture across &lt;code&gt;v1&lt;/code&gt;, &lt;code&gt;v2&lt;/code&gt;, &lt;code&gt;v3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distinguishes direct from indirect dependencies&lt;/strong&gt;, so you know which consumers call into your module directly and which pull it in transitively through another library&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follows transitive edges&lt;/strong&gt; to show second-order consumers: repos that depend on your module through an intermediate shared library&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeps results current&lt;/strong&gt; through regular rescans rather than a one-time snapshot&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is one of the specific problems &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt; is built to solve. It connects to your GitHub or GitLab organisation, scans every repo, and parses &lt;code&gt;go.mod&lt;/code&gt; files to extract module dependencies — including all &lt;code&gt;require&lt;/code&gt; entries, &lt;code&gt;replace&lt;/code&gt; directives, and version strings. It resolves module paths across major version suffixes and builds a cross-repo dependency graph you can query by module.&lt;/p&gt;

&lt;p&gt;The result is the same kind of view described throughout this series: before you remove a function, bump a major version, or change a constructor signature, you open the graph, click the module, and see every repo that imports it — their version, whether they're a direct or indirect consumer, and which teams own them. You know who'll break. You know who to notify. You know who's still on v1 and will need a migration path before v2 ships.&lt;/p&gt;

&lt;p&gt;No manual &lt;code&gt;go mod graph&lt;/code&gt; inversions across dozens of repos. No org-wide code search with manual version lookups. No waiting to see whose CI goes red after you merge.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How is your team solving this today?&lt;/strong&gt; I'd genuinely like to know — drop a comment or find me at &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;riftmap.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>modules</category>
      <category>dependencymanagement</category>
    </item>
    <item>
      <title>How to Find Every Consumer of Your Helm Chart</title>
      <dc:creator>Daniel Westgaard</dc:creator>
      <pubDate>Sat, 18 Apr 2026 22:00:00 +0000</pubDate>
      <link>https://dev.to/danielwe/how-to-find-every-consumer-of-your-helm-chart-347c</link>
      <guid>https://dev.to/danielwe/how-to-find-every-consumer-of-your-helm-chart-347c</guid>
      <description>&lt;p&gt;&lt;em&gt;You maintain a shared Helm chart. A breaking change is coming. Which teams across the org are running it — and at which version?&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;You own a shared Helm chart. Maybe it's a &lt;code&gt;platform-services&lt;/code&gt; chart that bundles your observability stack: Prometheus, Alertmanager, and a custom metrics exporter your platform team maintains. Maybe it's an &lt;code&gt;ingress-gateway&lt;/code&gt; chart that every product team deploys at the edge of their namespace. Maybe it's a company-wide chart for spinning up a standard service with your logging sidecar, network policies, and PodDisruptionBudget pre-configured.&lt;/p&gt;

&lt;p&gt;It started as a convenience. Teams stopped reinventing the same Kubernetes manifests. A dozen clusters adopted it. Then more. Some teams deploy it via ArgoCD. Some use Flux. Some have &lt;code&gt;helm install&lt;/code&gt; calls baked into their CI pipelines. Some consume it as a subchart inside their own umbrella chart. Some use a plain &lt;code&gt;Chart.yaml&lt;/code&gt; with a &lt;code&gt;dependencies:&lt;/code&gt; block and run &lt;code&gt;helm dependency update&lt;/code&gt; before deploy.&lt;/p&gt;

&lt;p&gt;Now you need to change it. Maybe a value key is being renamed — &lt;code&gt;replicaCount&lt;/code&gt; is becoming &lt;code&gt;replicas&lt;/code&gt; to align with upstream conventions. Maybe you're dropping support for a Kubernetes API version that was removed in 1.32. Maybe you're restructuring the chart into subcharts to make individual components opt-in rather than all-or-nothing.&lt;/p&gt;

&lt;p&gt;The question is the same one it always is: &lt;strong&gt;which repos across the org consume this chart, and at which version?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario
&lt;/h2&gt;

&lt;p&gt;Here is what Helm chart consumption looks like in practice. Your platform team publishes a chart to an internal chart repository or OCI registry. Consumers reference it in a few different ways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;As a standalone &lt;code&gt;helm install&lt;/code&gt;:&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;helm &lt;span class="nb"&gt;install &lt;/span&gt;platform-services oci://registry.company.com/helm-charts/platform-services &lt;span class="nt"&gt;--version&lt;/span&gt; 3.2.1 &lt;span class="nt"&gt;-n&lt;/span&gt; monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;As a dependency in another chart's &lt;code&gt;Chart.yaml&lt;/code&gt;:&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="c1"&gt;# some-service/chart/Chart.yaml&lt;/span&gt;
&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform-services&lt;/span&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.2.0"&lt;/span&gt;
    &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://charts.company.com"&lt;/span&gt;
    &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;As an ArgoCD &lt;code&gt;Application&lt;/code&gt; manifest:&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="c1"&gt;# gitops/apps/some-team/monitoring.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Application&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform-services&lt;/span&gt;
    &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://charts.company.com&lt;/span&gt;
    &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.2.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;As a Flux &lt;code&gt;HelmRelease&lt;/code&gt; CRD:&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="c1"&gt;# gitops/releases/monitoring.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helm.toolkit.fluxcd.io/v2&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HelmRelease&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform-services&lt;/span&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;&amp;gt;=3.0.0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;4.0.0"&lt;/span&gt;
      &lt;span class="na"&gt;sourceRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HelmRepository&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;company-charts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All four patterns express the same dependency. None of them look the same in source code. And right now, they probably live in different repos, written by different teams, using different GitOps tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  What existing tools give you (and where they stop)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Helm itself
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;helm&lt;/code&gt; CLI knows about releases deployed to a cluster. &lt;code&gt;helm list -A&lt;/code&gt; shows every release across all namespaces in a given cluster, including which chart and version is installed. &lt;code&gt;helm history &amp;lt;release&amp;gt;&lt;/code&gt; shows the version history for a specific release.&lt;/p&gt;

&lt;p&gt;This is useful once you have access to the cluster. The limitation is that it's cluster-scoped and live-state-only. You can see what's running right now, but it tells you nothing about &lt;em&gt;source repos&lt;/em&gt; — which repo contains the &lt;code&gt;values.yaml&lt;/code&gt; driving the release, which team owns it, or whether the same chart is deployed to a different cluster you don't have access to. Most companies have at least three clusters (dev, staging, prod), and many have dozens. You'd need access to all of them, and even then you'd have a list of running releases, not a map of source-code consumers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Artifact Hub and Chart Museums
&lt;/h3&gt;

&lt;p&gt;Artifact Hub is the public index for Helm charts. It tracks download counts and shows chart metadata, but it's a discovery tool for chart &lt;em&gt;users&lt;/em&gt;, not a graph of chart &lt;em&gt;consumers&lt;/em&gt;. If your chart is published internally — to a private chart repository or an OCI registry — it doesn't appear in Artifact Hub at all. And even for public charts, neither Artifact Hub nor any chart museum stores information about which orgs or repos have installed a particular chart version.&lt;/p&gt;

&lt;h3&gt;
  
  
  ArgoCD and Flux
&lt;/h3&gt;

&lt;p&gt;If your org has committed fully to GitOps and every Helm release is declared as an ArgoCD &lt;code&gt;Application&lt;/code&gt; or a Flux &lt;code&gt;HelmRelease&lt;/code&gt; manifest in a Git repo, then in theory all your consumers are visible in those manifests. The problem is "in theory." In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not every team has migrated to GitOps. Some still run &lt;code&gt;helm upgrade&lt;/code&gt; in CI pipelines or from engineer laptops.&lt;/li&gt;
&lt;li&gt;The GitOps manifests may live in many different repos — a central gitops repo, per-team repos, per-environment repos — rather than a single queryable location.&lt;/li&gt;
&lt;li&gt;ArgoCD's UI shows you the apps it's managing, but querying "show me every app that references chart &lt;code&gt;platform-services&lt;/code&gt;" is not a built-in view. You'd need to iterate through the Application CRDs or query the Argo CD API.&lt;/li&gt;
&lt;li&gt;Flux doesn't have a central query surface for "which &lt;code&gt;HelmRelease&lt;/code&gt; objects across all clusters reference chart X at version Y" — that information is spread across &lt;code&gt;HelmRelease&lt;/code&gt; YAML files in whatever repos your GitOps setup uses.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Renovate
&lt;/h3&gt;

&lt;p&gt;Renovate supports Helm chart version bumps as a first-class feature. Configured in a repo, it detects chart references in &lt;code&gt;Chart.yaml&lt;/code&gt; dependencies, ArgoCD Application manifests, and Flux HelmRelease CRDs, and opens pull requests when new chart versions are published.&lt;/p&gt;

&lt;p&gt;As with Terraform and GitHub Actions: Renovate &lt;em&gt;must&lt;/em&gt; know who consumes what in order to open those PRs. But it doesn't expose that knowledge as a queryable view. You can't ask Renovate "which repos reference my chart and at which version?" It reacts after a new version is published. The consumer list before you publish a breaking change is not something it surfaces.&lt;/p&gt;

&lt;p&gt;Renovate also only covers repos where it has been configured. A team that hasn't added Helm to their Renovate config — or doesn't use Renovate at all — is invisible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grep
&lt;/h3&gt;

&lt;p&gt;Clone every repo. Search for the chart 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="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"platform-services"&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*.yaml"&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*.yml"&lt;/span&gt; ~/repos/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This finds direct name references in YAML files. It doesn't distinguish between chart references and random YAML comments, doesn't extract version constraints, misses OCI-format references where the chart name is embedded in a registry URL, and ignores any &lt;code&gt;helm install&lt;/code&gt; calls in shell scripts or Makefile targets. And as always: the results are stale by the next commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is harder than it looks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reference formats are highly inconsistent.&lt;/strong&gt; A Helm chart can be referenced via a traditional HTTP chart repository, an OCI registry, or a local path. These look nothing alike:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Traditional repository (in Chart.yaml dependencies)&lt;/span&gt;
&lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://charts.company.com"&lt;/span&gt;

&lt;span class="c1"&gt;# OCI registry (in Chart.yaml dependencies, Helm 3.8+)&lt;/span&gt;
&lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oci://registry.company.com/helm-charts"&lt;/span&gt;

&lt;span class="c1"&gt;# OCI in a Flux HelmRepository&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oci&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oci://registry.company.com/helm-charts&lt;/span&gt;

&lt;span class="c1"&gt;# Inline OCI reference in a helm CLI call&lt;/span&gt;
&lt;span class="s"&gt;helm install platform-services oci://registry.company.com/helm-charts/platform-services --version 3.2.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resolving all of these back to the same chart identity requires knowing how your registry namespaces charts and how your chart repositories map to OCI paths — out-of-band information that no grep can recover.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version constraints vary widely.&lt;/strong&gt; Unlike Docker image &lt;code&gt;FROM&lt;/code&gt; statements (which usually pin to a specific tag) or Terraform module &lt;code&gt;ref=&lt;/code&gt; pins, Helm consumers regularly use semver constraints:&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.2.0"&lt;/span&gt;      &lt;span class="c1"&gt;# patch-compatible only: &amp;gt;=3.2.0 &amp;lt;3.3.0&lt;/span&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.0.0"&lt;/span&gt;      &lt;span class="c1"&gt;# minor-compatible: &amp;gt;=3.0.0 &amp;lt;4.0.0&lt;/span&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;&amp;gt;=3.0.0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;4"&lt;/span&gt;  &lt;span class="c1"&gt;# explicit range&lt;/span&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;*"&lt;/span&gt;           &lt;span class="c1"&gt;# any version (dangerous)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A consumer pinned to &lt;code&gt;^3.0.0&lt;/code&gt; will automatically receive your &lt;code&gt;3.3.0&lt;/code&gt; release — they don't have to explicitly upgrade. A consumer on &lt;code&gt;~3.2.0&lt;/code&gt; won't. Understanding who will be affected by a minor version bump versus a major bump requires evaluating constraints, not just comparing version strings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Umbrella charts create transitive consumers.&lt;/strong&gt; An umbrella chart is a Helm chart whose primary purpose is to bundle multiple subcharts together. If &lt;code&gt;team-a&lt;/code&gt; publishes an umbrella chart &lt;code&gt;team-a-stack&lt;/code&gt; that includes your &lt;code&gt;platform-services&lt;/code&gt; chart as a dependency, and &lt;code&gt;team-b&lt;/code&gt; deploys &lt;code&gt;team-a-stack&lt;/code&gt; — then &lt;code&gt;team-b&lt;/code&gt; is a transitive consumer of &lt;code&gt;platform-services&lt;/code&gt; through &lt;code&gt;team-a-stack&lt;/code&gt;. A breaking change to &lt;code&gt;platform-services&lt;/code&gt; affects &lt;code&gt;team-b&lt;/code&gt;, but a search for &lt;code&gt;platform-services&lt;/code&gt; in their repos finds nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitOps and imperative installs coexist.&lt;/strong&gt; In most organisations, some teams manage Helm releases declaratively (ArgoCD, Flux) and others do it imperatively (&lt;code&gt;helm install&lt;/code&gt; or &lt;code&gt;helm upgrade&lt;/code&gt; in CI pipelines, shell scripts, or Makefiles). The declarative installs leave traces in Git repos as YAML manifests. The imperative installs may leave no trace in source code at all — just a live release visible in &lt;code&gt;helm list&lt;/code&gt;. A complete consumer map needs to combine both sources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chart aliases break name matching.&lt;/strong&gt; A &lt;code&gt;Chart.yaml&lt;/code&gt; dependency can declare an &lt;code&gt;alias&lt;/code&gt;, which changes the name used to refer to the subchart in values overrides:&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;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform-services&lt;/span&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.2.0"&lt;/span&gt;
    &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://charts.company.com"&lt;/span&gt;
    &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;   &lt;span class="c1"&gt;# the chart is installed as "monitoring", not "platform-services"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A naive search for &lt;code&gt;platform-services&lt;/code&gt; in values files will find nothing, because the values are nested under &lt;code&gt;monitoring:&lt;/code&gt;. Tools that rely on name matching alone miss aliased consumers entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the full answer requires
&lt;/h2&gt;

&lt;p&gt;To reliably answer "who consumes this chart," you need a system that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scans every repo in the org&lt;/strong&gt;, including GitOps repos, service repos, and platform repos — without requiring registration or manual catalog entries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parses all reference formats&lt;/strong&gt; — &lt;code&gt;Chart.yaml&lt;/code&gt; dependencies, ArgoCD Application manifests, Flux HelmRelease CRDs, and &lt;code&gt;helm install&lt;/code&gt; calls in CI configs and shell scripts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolves OCI and HTTP registry references&lt;/strong&gt; back to a canonical chart identity, so consumers using different registry URL formats for the same chart are recognised as the same dependency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluates version constraints&lt;/strong&gt; rather than just matching exact version strings, so you know which consumers are within range of your next release&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follows umbrella chart transitive dependencies&lt;/strong&gt;, so second-order consumers are visible, not just direct chart references&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeps results current&lt;/strong&gt; through regular rescans, not a one-time manual trawl&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is one of the specific problems &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt; is built to solve. It connects to your GitHub or GitLab organisation, scans every repo, and extracts chart references from &lt;code&gt;Chart.yaml&lt;/code&gt; dependency blocks, ArgoCD Application and ApplicationSet manifests, Flux HelmRelease CRDs, and &lt;code&gt;helm&lt;/code&gt; calls in CI configs. It resolves OCI and HTTP registry URLs to a unified chart identity and builds a cross-repo dependency graph you can query by chart name and version.&lt;/p&gt;

&lt;p&gt;The result is the same kind of view described in the earlier posts in this series: before you rename a value key or drop a Kubernetes API version, you open the graph, click the chart, and see every consumer — their version constraint, the repo that owns the deployment declaration, and which constraint ranges include your next release. You know who'll automatically receive the change, who's pinned to an older range, and who you need to contact before shipping.&lt;/p&gt;

&lt;p&gt;No cluster access required. No cross-org Slack ping. No grep marathon across forty repos.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How is your team solving this today?&lt;/strong&gt; I'd genuinely like to know — drop a comment or find me at &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;riftmap.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>helm</category>
      <category>kubernetes</category>
      <category>gitops</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Find Every Consumer of Your Reusable GitHub Actions Workflow</title>
      <dc:creator>Daniel Westgaard</dc:creator>
      <pubDate>Fri, 17 Apr 2026 06:38:52 +0000</pubDate>
      <link>https://dev.to/danielwe/how-to-find-every-consumer-of-your-reusable-github-actions-workflow-2cpn</link>
      <guid>https://dev.to/danielwe/how-to-find-every-consumer-of-your-reusable-github-actions-workflow-2cpn</guid>
      <description>&lt;p&gt;&lt;em&gt;You maintain a shared GitHub Actions workflow. You need to rename an input, drop a step, or change a required secret. Which repos across your org call it? Here's why the answer is harder than it should be.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;You maintain a reusable GitHub Actions workflow. Maybe it's &lt;code&gt;deploy-to-eks.yml&lt;/code&gt; in your platform team's &lt;code&gt;.github&lt;/code&gt; repo, or a &lt;code&gt;ci-lint-test.yml&lt;/code&gt; that standardises how every service in the org runs its CI pipeline. It started as a way to stop every team from copy-pasting the same 80-line workflow file. It worked. Twenty repos adopted it. Then forty. Then you lost count.&lt;/p&gt;

&lt;p&gt;Now you need to change it. Maybe you're replacing a deprecated action (&lt;code&gt;actions/setup-node@v4&lt;/code&gt; is being forced to Node 24 and you need to bump to &lt;code&gt;v6&lt;/code&gt;). Maybe you're renaming an input from &lt;code&gt;node-version&lt;/code&gt; to &lt;code&gt;runtime-version&lt;/code&gt; because the workflow now supports Bun and Deno too. Maybe you're removing a step that uploaded artifacts to a bucket that no longer exists.&lt;/p&gt;

&lt;p&gt;The question is the same one that comes up for every shared infrastructure artifact: &lt;strong&gt;which repos across our org call this workflow, and at which ref?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario
&lt;/h2&gt;

&lt;p&gt;Here's what a typical reusable workflow consumption looks like. Your platform team publishes a workflow in a central repository, and other repos call it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# In some-service/.github/workflows/ci.yml&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;test&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;my-org/platform-workflows/.github/workflows/ci-lint-test.yml@v2.1.0&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;node-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;20"&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inherit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some repos pin to a tag. Some pin to a branch like &lt;code&gt;main&lt;/code&gt;. Some pin to a full commit SHA for supply-chain security. Some don't pin at all and reference &lt;code&gt;@main&lt;/code&gt;, which means they're calling whatever version is on HEAD right now.&lt;/p&gt;

&lt;p&gt;You need to find all of them, understand which version each one uses, and figure out which will break when you rename that input. Right now, most teams do this by searching in GitHub, asking in Slack, or just pushing the change and waiting for workflows to fail. None of these are a real answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What existing tools give you (and where they stop)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub code search
&lt;/h3&gt;

&lt;p&gt;GitHub's code search is the most natural place to look. You can search across all repos in an org:&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="s"&gt;org:my-org "uses&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-org/platform-workflows/.github/workflows/ci-lint-test.yml"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works for finding direct string matches. For a one-off audit, it's often good enough. But it has real limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It doesn't extract the ref. You can see that a repo calls the workflow, but the &lt;code&gt;@v2.1.0&lt;/code&gt; or &lt;code&gt;@main&lt;/code&gt; part requires manually opening each result and reading the YAML.&lt;/li&gt;
&lt;li&gt;Results are point-in-time. If you're planning a breaking change, you need to know who's calling the workflow &lt;em&gt;now&lt;/em&gt; — not when the search index was last updated.&lt;/li&gt;
&lt;li&gt;The search index can lag. GitHub's code search is eventually consistent. Repos that were recently updated might not show current results immediately.&lt;/li&gt;
&lt;li&gt;It doesn't handle indirection. If a repo has a "wrapper" workflow that calls your reusable workflow, and other workflows call the wrapper, GitHub code search only finds the direct callers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dependabot
&lt;/h3&gt;

&lt;p&gt;Dependabot supports GitHub Actions as a first-class ecosystem. In a repo's &lt;code&gt;dependabot.yml&lt;/code&gt;, you can configure it to watch for action version updates:&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;updates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;package-ecosystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;github-actions"&lt;/span&gt;
    &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/"&lt;/span&gt;
    &lt;span class="na"&gt;schedule&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weekly"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dependabot will then open pull requests when actions referenced in the repo's workflows have new versions available. This includes reusable workflows referenced with &lt;code&gt;uses:&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The same pattern as Renovate for Terraform: Dependabot &lt;em&gt;implicitly&lt;/em&gt; knows who consumes what, because it's configured per-repo. But it doesn't expose this as a queryable view. You can't ask Dependabot "which repos in my org call &lt;code&gt;platform-workflows/ci-lint-test.yml&lt;/code&gt;?" It's a version updater, not a dependency mapper.&lt;/p&gt;

&lt;p&gt;There's an additional limitation: Dependabot only monitors repos where it's been explicitly enabled. If a team hasn't added &lt;code&gt;github-actions&lt;/code&gt; to their &lt;code&gt;dependabot.yml&lt;/code&gt; — or doesn't have a &lt;code&gt;dependabot.yml&lt;/code&gt; at all — their workflow dependencies are invisible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Renovate
&lt;/h3&gt;

&lt;p&gt;Renovate also supports GitHub Actions workflows, including reusable workflow references. It can detect &lt;code&gt;uses:&lt;/code&gt; directives in workflow files and open PRs when new versions are available.&lt;/p&gt;

&lt;p&gt;Renovate has a slight edge over Dependabot here: it can be configured org-wide via a shared preset, so you don't need every repo to opt in individually. But the core limitation is identical — it's a dependency &lt;em&gt;updater&lt;/em&gt;, not a dependency &lt;em&gt;mapper&lt;/em&gt;. It reacts when a new version exists. It doesn't give you the blast radius before you publish the change.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub API
&lt;/h3&gt;

&lt;p&gt;The GitHub API lets you list workflows and workflow runs across repositories. You can enumerate all repos in an org, fetch their workflow files, and parse the YAML yourself.&lt;/p&gt;

&lt;p&gt;This is essentially building a custom scanner. The API gives you the raw data, but assembling it into an answer requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Paginating through every repo in the org&lt;/li&gt;
&lt;li&gt;Fetching the contents of every &lt;code&gt;.github/workflows/*.yml&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Parsing YAML to extract &lt;code&gt;uses:&lt;/code&gt; directives&lt;/li&gt;
&lt;li&gt;Filtering for your specific reusable workflow&lt;/li&gt;
&lt;li&gt;Extracting the ref from each reference&lt;/li&gt;
&lt;li&gt;Handling rate limits (the GitHub API is aggressive about rate limiting for large orgs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Teams that go this route end up building a script, running it periodically, and storing the results somewhere. It works. It's also a project you now have to maintain, and the results are stale between runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grep
&lt;/h3&gt;

&lt;p&gt;Clone everything, grep for the workflow reference:&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;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"platform-workflows/.github/workflows/ci-lint-test.yml"&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*.yml"&lt;/span&gt; ~/repos/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same trade-offs as always: works once, stale immediately, doesn't extract versions, doesn't catch transitive calls, and at 100+ repos it's slow enough that nobody does it proactively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is harder than it looks
&lt;/h2&gt;

&lt;p&gt;Reusable workflow references look simpler than Terraform module sources or Docker image references — there's really only one syntax. But the complexity is in what surrounds that syntax.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ref types vary widely.&lt;/strong&gt; The &lt;code&gt;@ref&lt;/code&gt; portion of a workflow call can be a tag, a branch, or a full SHA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Tag — the common case&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;my-org/platform-workflows/.github/workflows/deploy.yml@v2.1.0&lt;/span&gt;

&lt;span class="c1"&gt;# Branch — consuming HEAD, inherently unstable&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;my-org/platform-workflows/.github/workflows/deploy.yml@main&lt;/span&gt;

&lt;span class="c1"&gt;# SHA — supply-chain hardened, but opaque&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;my-org/platform-workflows/.github/workflows/deploy.yml@a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A tag tells you the version. A branch tells you almost nothing — the consumer is on whatever commit &lt;code&gt;main&lt;/code&gt; happened to point to when they last ran. A SHA is precise but requires resolving the commit to a tag or branch to understand which &lt;em&gt;version&lt;/em&gt; the consumer is actually using. Answering "who's still on v1.x?" requires understanding all three forms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reusable workflows vs. composite actions.&lt;/strong&gt; GitHub Actions has two mechanisms for sharing CI logic: reusable workflows (called with &lt;code&gt;uses:&lt;/code&gt; at the &lt;em&gt;job&lt;/em&gt; level) and composite actions (called with &lt;code&gt;uses:&lt;/code&gt; at the &lt;em&gt;step&lt;/em&gt; level). They look similar in YAML but behave differently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Reusable workflow — job level&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;deploy&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;my-org/platform-workflows/.github/workflows/deploy.yml@v2&lt;/span&gt;

&lt;span class="c1"&gt;# Composite action — step level&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;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;my-org/platform-actions/setup-toolchain@v3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are dependencies on shared code in other repos. Both break when the interface changes. A consumer tracking system needs to find both, because platform teams often maintain a mix of reusable workflows and composite actions — and the same "who's using this?" question applies to both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;.github&lt;/code&gt; repository.&lt;/strong&gt; GitHub has a special convention: a repo named &lt;code&gt;.github&lt;/code&gt; in an org can contain workflow templates that appear in every repo's "Actions" tab. If your platform team publishes starter workflows through the &lt;code&gt;.github&lt;/code&gt; repo, those templates get copied (not referenced) into consuming repos. Once copied, they're no longer linked to the source — there's no &lt;code&gt;uses:&lt;/code&gt; directive pointing back, just a disconnected copy that may or may not have been modified since.&lt;/p&gt;

&lt;p&gt;This means some of your "consumers" are invisible. They took the template, modified it, and now maintain their own fork. You can't find them by searching for &lt;code&gt;uses:&lt;/code&gt; directives because there aren't any.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inputs and secrets are the breaking surface.&lt;/strong&gt; Unlike Terraform modules, where the interface is defined by variables and outputs with explicit types, reusable workflow inputs are loosely typed. A workflow declares its inputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_call&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DEPLOY_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you rename &lt;code&gt;node-version&lt;/code&gt; to &lt;code&gt;runtime-version&lt;/code&gt;, every caller that passes &lt;code&gt;node-version&lt;/code&gt; will silently fail — the input just won't be received, and the workflow will use its default or break downstream. There's no compile-time error. The failure shows up at runtime, in the middle of a CI run, and the error message often doesn't clearly point to the renamed input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow call chains.&lt;/strong&gt; Reusable workflows can call other reusable workflows, up to ten levels deep and fifty total workflow calls per run (GitHub increased these limits in late 2025). If workflow A calls workflow B, which calls workflow C, and you change C's interface, both A and B are affected — but only B directly references C. Finding A requires resolving the full call chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the full answer requires
&lt;/h2&gt;

&lt;p&gt;To reliably answer "who consumes this reusable workflow," you need a system that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scans every repo in the org&lt;/strong&gt;, parsing all &lt;code&gt;.github/workflows/*.yml&lt;/code&gt; files — not just repos that have opted into Dependabot or Renovate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extracts &lt;code&gt;uses:&lt;/code&gt; directives&lt;/strong&gt; at both the job level (reusable workflows) and step level (composite actions), because platform teams typically maintain both&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolves ref types&lt;/strong&gt; — tags, branches, and SHAs — to a normalised version, so you can answer "who's still on v1.x?" regardless of how the ref was specified&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follows workflow call chains&lt;/strong&gt; so you see transitive consumers, not just direct callers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeps the graph current&lt;/strong&gt; through regular rescans, not one-off audits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Makes the result queryable&lt;/strong&gt;: "show me every consumer of &lt;code&gt;ci-lint-test.yml&lt;/code&gt;, grouped by ref, with the team that owns each calling repo"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is one of the specific problems &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt; is built to solve. It scans a GitHub (or GitLab) org, parses every workflow file in every repo, extracts all &lt;code&gt;uses:&lt;/code&gt; directives — including reusable workflow calls, composite action references, and standard action pins — and builds a cross-repo dependency graph that you can query by artifact.&lt;/p&gt;

&lt;p&gt;The result: before you rename that input or remove that step, you open the graph, click the workflow, and see exactly which repos call it and which ref each one pins to. You know who'll break. You know who to notify. You know who's pinned to &lt;code&gt;main&lt;/code&gt; and will see the change immediately versus who's on a tagged release and has time to migrate.&lt;/p&gt;

&lt;p&gt;No org-wide grep. No stale GitHub code search. No "let's push it and see whose CI goes red."&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How is your team solving this today?&lt;/strong&gt; I'd genuinely like to know — drop a comment or find me at &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;riftmap.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>devops</category>
      <category>platformengineering</category>
      <category>dependencymanagement</category>
    </item>
    <item>
      <title>How to Find Every Consumer of Your Terraform Module</title>
      <dc:creator>Daniel Westgaard</dc:creator>
      <pubDate>Wed, 15 Apr 2026 09:30:27 +0000</pubDate>
      <link>https://dev.to/danielwe/how-to-find-every-consumer-of-your-terraform-module-enh</link>
      <guid>https://dev.to/danielwe/how-to-find-every-consumer-of-your-terraform-module-enh</guid>
      <description>&lt;p&gt;&lt;em&gt;You maintain a shared Terraform module. You need to make a breaking change. Which repos across your org actually source it — and at which version? Here's why the answer is harder than it should be.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;You maintain an internal Terraform module. Maybe it's &lt;code&gt;terraform-aws-vpc&lt;/code&gt; or &lt;code&gt;modules/rds-cluster&lt;/code&gt; or a shared networking baseline that half the org builds on top of. It started as a convenience — one team wrote a good VPC module, others adopted it, and now it's load-bearing infrastructure for dozens of repos.&lt;/p&gt;

&lt;p&gt;Then you need to change it. Maybe you're upgrading the AWS provider from v4 to v5 and the module interface needs to change. Maybe you're deprecating a variable that seemed like a good idea eighteen months ago. Maybe you're fixing a security misconfiguration and the fix requires consumers to pass a new required variable.&lt;/p&gt;

&lt;p&gt;The question is simple: &lt;strong&gt;which repos across our org source this module, and at which version?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you can't answer this, you're deploying blind. You either make the change and wait for &lt;code&gt;terraform plan&lt;/code&gt; failures to trickle in across teams, or you don't make the change at all and let the tech debt compound. Neither option is good.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario
&lt;/h2&gt;

&lt;p&gt;Here's what this typically looks like. Your platform team publishes a module, and repos across the org consume it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git::https://gitlab.company.com/infra/terraform-aws-vpc.git?ref=v2.3.0"&lt;/span&gt;

  &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;
  &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some repos pin to a specific Git tag. Some pin to a branch. Some point at &lt;code&gt;main&lt;/code&gt; with no ref at all, which means they're consuming whatever happens to be at HEAD when they next run &lt;code&gt;terraform init&lt;/code&gt;. Some source the module from your internal Terraform registry instead of Git. Some use a relative path because the module lives in a monorepo alongside the consuming root module.&lt;/p&gt;

&lt;p&gt;You need to find all of them, understand which version each one uses, and figure out which teams to notify before you push the breaking change. Right now, most teams do this by grepping, searching the Terraform registry UI, or asking in Slack. None of these give you a complete answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What existing tools give you (and where they stop)
&lt;/h2&gt;

&lt;p&gt;Several tools address pieces of the Terraform module consumer problem. Each one is useful in its own context. None of them answer the full question.&lt;/p&gt;

&lt;h3&gt;
  
  
  HCP Terraform (Terraform Cloud) Module Registry
&lt;/h3&gt;

&lt;p&gt;If you use HCP Terraform or Terraform Enterprise, the private module registry tracks which workspaces consume a published module. The registry UI shows a module's versions, usage statistics, and which workspaces reference it.&lt;/p&gt;

&lt;p&gt;This works within the HCP Terraform ecosystem. The limitation is scope: it only sees modules published to &lt;em&gt;its&lt;/em&gt; registry and consumed by &lt;em&gt;its&lt;/em&gt; workspaces. If a team sources the module directly via a Git URL — which is extremely common, especially in orgs that adopted Terraform before the private registry existed — that consumption is invisible. If some repos use HCP Terraform and others use a different backend (local state, S3, Spacelift, env0, Atlantis), you're seeing a partial picture. It's also worth noting that the HCP Terraform free tier was discontinued in March 2026, so this option now requires a paid plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spacelift / Scalr / env0
&lt;/h3&gt;

&lt;p&gt;Spacelift has the most advanced module consumer tracking of the orchestration platforms. Its module registry tracks which stacks consume each module version, and trigger policies can automatically kick off runs on consumer stacks when a new module version is published. Scalr offers a modules report that shows which modules and versions are used across all workspaces, including which workspaces are running outdated versions. env0 has similar workspace-level dependency awareness.&lt;/p&gt;

&lt;p&gt;These are genuinely useful capabilities — if all your Terraform runs flow through a single platform. The limitation is the same for all of them: they only see what's inside their own ecosystem. If your org uses Spacelift for production infrastructure but engineers run &lt;code&gt;terraform plan&lt;/code&gt; locally for development, or if one team uses Atlantis while another uses GitHub Actions with raw &lt;code&gt;terraform&lt;/code&gt; commands, no single orchestrator has the full picture. And none of them see the module source references in the &lt;em&gt;code&lt;/em&gt; — they see module consumption at the &lt;em&gt;workspace execution&lt;/em&gt; level, which is a different layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;terraform providers&lt;/code&gt; and &lt;code&gt;terraform graph&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Terraform's built-in &lt;code&gt;terraform graph&lt;/code&gt; command outputs a DOT-format dependency graph for a single root module. &lt;code&gt;terraform providers&lt;/code&gt; lists the providers required by a configuration and its modules.&lt;/p&gt;

&lt;p&gt;These commands work per-configuration, not per-org. They answer "what does &lt;em&gt;this&lt;/em&gt; root module depend on?" They don't answer "which root modules across my org depend on &lt;em&gt;that&lt;/em&gt; shared module?" You'd need to clone every repo, run the command in every Terraform root, and aggregate the results — which is essentially building a custom scanner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grep / GitHub code search
&lt;/h3&gt;

&lt;p&gt;The most common approach. Clone everything (or use GitHub's code search), and search for the module source URL:&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;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"terraform-aws-vpc"&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*.tf"&lt;/span&gt; ~/repos/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This finds direct string matches. It works for a one-off audit. But it breaks down in several ways that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It doesn't resolve registry sources. &lt;code&gt;source = "app.terraform.io/company/vpc/aws"&lt;/code&gt; and &lt;code&gt;source = "git::https://gitlab.company.com/infra/terraform-aws-vpc.git"&lt;/code&gt; might reference the same module — grep treats them as unrelated strings.&lt;/li&gt;
&lt;li&gt;It doesn't extract versions. You can see that a repo references the module, but parsing out &lt;code&gt;?ref=v2.3.0&lt;/code&gt; or the &lt;code&gt;version = "~&amp;gt; 2.0"&lt;/code&gt; constraint from a registry source requires additional work per match.&lt;/li&gt;
&lt;li&gt;The results are stale immediately. The search reflects the state of whatever you cloned. By tomorrow, three repos might have changed their module references.&lt;/li&gt;
&lt;li&gt;At 100+ repos, it takes long enough that nobody does it proactively. It becomes an incident response activity, not a planning tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Renovate
&lt;/h3&gt;

&lt;p&gt;Renovate understands Terraform module sources and can open pull requests when a new version is available. It parses &lt;code&gt;source&lt;/code&gt; and &lt;code&gt;version&lt;/code&gt; attributes in &lt;code&gt;.tf&lt;/code&gt; files and supports Git refs, registry modules, and GitHub releases.&lt;/p&gt;

&lt;p&gt;Like with Docker images, Renovate &lt;em&gt;implicitly&lt;/em&gt; knows who consumes what — it's configured per-repo and has parsed the module sources. But it doesn't expose this as a queryable view. You can't ask Renovate "which repos in my org source &lt;code&gt;terraform-aws-vpc&lt;/code&gt;?" It's a dependency &lt;em&gt;updater&lt;/em&gt;, not a dependency &lt;em&gt;mapper&lt;/em&gt;. It reacts after a new version exists. It doesn't give you the blast radius &lt;em&gt;before&lt;/em&gt; you publish the new version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is harder than it looks
&lt;/h2&gt;

&lt;p&gt;Terraform module sourcing is more complex than most people realise, because the same module can be referenced through at least five different source types — and each one looks completely different in the HCL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Git sources&lt;/strong&gt; are the most common for internal modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git::https://gitlab.company.com/infra/terraform-aws-vpc.git?ref=v2.3.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The URL might use HTTPS or SSH. The ref might be a tag, a branch, or a commit SHA. Some teams use the &lt;code&gt;git::&lt;/code&gt; prefix, others rely on Terraform's URL inference. The same module can appear as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git::https://gitlab.company.com/infra/terraform-aws-vpc.git?ref=v2.3.0"&lt;/span&gt;
&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git@gitlab.company.com:infra/terraform-aws-vpc.git?ref=v2.3.0"&lt;/span&gt;
&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gitlab.company.com/infra/terraform-aws-vpc?ref=v2.3.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These all point at the same module. A grep for one form misses the others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Registry sources&lt;/strong&gt; use a completely different syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app.terraform.io/company/vpc/aws"&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This references the same underlying module, but through the registry. The version constraint is in a separate attribute, not in the source URL. Matching this to the Git-sourced references requires knowing that the registry module &lt;code&gt;company/vpc/aws&lt;/code&gt; maps to the Git repo &lt;code&gt;infra/terraform-aws-vpc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local paths&lt;/strong&gt; appear when modules live alongside root configurations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../../modules/vpc"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a dependency on an internal module, but there's no URL to grep for. The relationship is purely structural — you need to resolve the relative path within the repo's directory tree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub/GitLab shorthand&lt;/strong&gt; sources add another variant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github.com/company/terraform-aws-vpc?ref=v2.3.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform interprets this as a Git clone from GitHub, but the syntax differs from both the explicit &lt;code&gt;git::&lt;/code&gt; form and the registry form.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subdirectory references&lt;/strong&gt; complicate things further:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc_endpoints"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git::https://gitlab.company.com/infra/terraform-aws-vpc.git//modules/endpoints?ref=v2.3.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;//&lt;/code&gt; separator means "clone this repo, then use the &lt;code&gt;modules/endpoints&lt;/code&gt; subdirectory." This is a dependency on the same repo but a different module path within it. A consumer tracking system needs to handle both the repo-level relationship and the subpath.&lt;/p&gt;

&lt;p&gt;Then there's the &lt;strong&gt;transitive problem&lt;/strong&gt;. Module A sources module B, which sources module C. If you change module C, both B and A are affected — but A never mentions C anywhere in its code. Understanding the full blast radius requires resolving the entire chain, not just direct consumers.&lt;/p&gt;

&lt;p&gt;None of the tools in the previous section handle all of these source types and resolve them to a unified view. This is why teams keep falling back to grep and tribal knowledge — and why both keep failing at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the full answer requires
&lt;/h2&gt;

&lt;p&gt;To reliably answer "who consumes this Terraform module," you need a system that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scans every repo in the org&lt;/strong&gt;, not just those registered in a specific orchestration platform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parses all five source types&lt;/strong&gt; — Git URLs (HTTPS and SSH), registry sources, local paths, GitHub/GitLab shorthand, and subdirectory references — and normalises them to a single identity per module&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extracts version constraints&lt;/strong&gt; from both &lt;code&gt;?ref=&lt;/code&gt; parameters in Git sources and &lt;code&gt;version&lt;/code&gt; attributes in registry sources&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolves transitive dependencies&lt;/strong&gt; so you can see not just direct consumers but the full downstream blast radius&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeps the graph current&lt;/strong&gt; through scheduled or event-triggered rescans, not one-off audits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Makes the result queryable&lt;/strong&gt;: "show me every consumer of &lt;code&gt;terraform-aws-vpc&lt;/code&gt;, grouped by version, with the team that owns each consuming repo"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is one of the specific problems &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt; is built to solve. It scans a GitLab or GitHub org, parses every &lt;code&gt;.tf&lt;/code&gt; file across every repo, resolves all five module source types to a unified dependency graph, and lets you click on any module to see every consumer — direct and transitive — with the version each one pins to.&lt;/p&gt;

&lt;p&gt;The result: before you push the breaking change, you open the graph, click the module, and see exactly which repos and teams are affected. You know who to notify. You know who's still on v1.x and who's already on v2.x. You know which repos pin to &lt;code&gt;main&lt;/code&gt; and will break immediately versus which pin to a tag and have time to migrate.&lt;/p&gt;

&lt;p&gt;No grepping. No Slack polling. No "let's just push it and see who complains."&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How is your team solving this today?&lt;/strong&gt; I'd genuinely like to know — drop a comment or find me at &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;riftmap.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>devops</category>
      <category>infrastructure</category>
      <category>security</category>
    </item>
    <item>
      <title>How to Find Every Consumer of Your Docker Base</title>
      <dc:creator>Daniel Westgaard</dc:creator>
      <pubDate>Mon, 13 Apr 2026 19:16:20 +0000</pubDate>
      <link>https://dev.to/danielwe/how-to-find-every-consumer-of-your-docker-base-e32</link>
      <guid>https://dev.to/danielwe/how-to-find-every-consumer-of-your-docker-base-e32</guid>
      <description>&lt;p&gt;&lt;em&gt;You maintain a shared base image. A CVE drops. Which repos are affected? Here's why the answer is harder than it should be.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;You maintain an internal Docker base image. Maybe it's &lt;code&gt;platform/node-base&lt;/code&gt; or &lt;code&gt;company/python-runtime&lt;/code&gt;. A dozen repos use it. Or thirty. Or you're not sure how many, because nobody's counted since the last team reorganisation.&lt;/p&gt;

&lt;p&gt;Then a critical CVE hits the base OS layer, and you need to push a patched version. The question that matters is simple: &lt;strong&gt;which repos across our org pull this image, and at which version?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This should be easy to answer. It isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario
&lt;/h2&gt;

&lt;p&gt;Here's what this looks like in practice. Your platform team builds and publishes &lt;code&gt;registry.company.com/platform/base-image&lt;/code&gt;. Across the org, Dockerfiles reference it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; registry.company.com/platform/base-image:v2.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some repos pin to a specific tag. Some use &lt;code&gt;latest&lt;/code&gt;. Some have &lt;code&gt;ARG&lt;/code&gt;-parameterized &lt;code&gt;FROM&lt;/code&gt; statements where the tag is injected at build time. Some reference the image not in a Dockerfile but in a &lt;code&gt;docker-compose.yml&lt;/code&gt; or a CI pipeline config.&lt;/p&gt;

&lt;p&gt;You need to find all of them, check which version each one uses, and coordinate the update. Right now, most teams do this by grepping, asking on Slack, or checking CI logs. None of these give you a complete, current answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What existing tools give you (and where they stop)
&lt;/h2&gt;

&lt;p&gt;Several tools address parts of the Docker base image problem. Each one is useful. None of them answer the full question.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Scout
&lt;/h3&gt;

&lt;p&gt;Docker Scout analyzes a &lt;em&gt;built image&lt;/em&gt; and tells you what base image it uses, whether that base is outdated, and what vulnerabilities it contains. &lt;code&gt;docker scout recommendations&lt;/code&gt; will suggest updated base images with fewer CVEs.&lt;/p&gt;

&lt;p&gt;This is valuable, but it works per-image, not per-org. It answers "what base image does &lt;em&gt;this&lt;/em&gt; image use?" It doesn't answer "which of my 200 repos use &lt;em&gt;this&lt;/em&gt; base image?" You'd need to run Scout against every image in your registry and correlate the results back to source repos — which is a project in itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Renovate
&lt;/h3&gt;

&lt;p&gt;Renovate detects &lt;code&gt;FROM&lt;/code&gt; statements in Dockerfiles and can open pull requests when a newer tag is available. It &lt;em&gt;implicitly&lt;/em&gt; knows who consumes what, because it's configured per-repo and parses the Dockerfiles.&lt;/p&gt;

&lt;p&gt;But Renovate doesn't expose this as a queryable view. You can't ask Renovate "show me every repo that references &lt;code&gt;platform/base-image&lt;/code&gt;." It reacts when a new version appears. It doesn't give you the pre-release blast radius: before you push the patched image, which repos and teams will need to update?&lt;/p&gt;

&lt;h3&gt;
  
  
  Backstage / Roadie Tech Insights
&lt;/h3&gt;

&lt;p&gt;Roadie (a managed Backstage provider) has a Tech Insights feature that can parse Dockerfiles via regex, extract base image versions, and create scorecards tracking migration progress. This is the closest existing solution to the consumer-tracking problem.&lt;/p&gt;

&lt;p&gt;The limitation is that it requires Backstage to be set up with &lt;code&gt;catalog-info.yaml&lt;/code&gt; per repo. You're tracking Docker base images through a service catalog that depends on manual registration. If a repo isn't in the catalog, its Dockerfile is invisible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Container registries
&lt;/h3&gt;

&lt;p&gt;Your registry (Docker Hub, Harbor, ACR, ECR) knows which images have been &lt;em&gt;pulled&lt;/em&gt; and how often. Some provide pull statistics by tag. But pull logs don't tell you which &lt;em&gt;source repo&lt;/em&gt; initiated the pull. A CI pipeline pulling &lt;code&gt;base-image:v2.1&lt;/code&gt; shows up as a pull event, not as "repo &lt;code&gt;frontend-api&lt;/code&gt; depends on this image via its Dockerfile on line 3."&lt;/p&gt;

&lt;h3&gt;
  
  
  Grep
&lt;/h3&gt;

&lt;p&gt;The fallback everyone uses. Clone all the repos, run &lt;code&gt;grep -r "platform/base-image"&lt;/code&gt;, assemble the results. It works once. The results are stale immediately. It misses parameterized &lt;code&gt;FROM&lt;/code&gt; statements, compose files, and CI configs. And at a hundred repos, it takes long enough that nobody does it proactively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is harder than it looks
&lt;/h2&gt;

&lt;p&gt;The core difficulty is that Docker base image dependencies live in multiple places and multiple formats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dockerfiles&lt;/strong&gt; are the obvious source, but &lt;code&gt;FROM&lt;/code&gt; statements can use &lt;code&gt;ARG&lt;/code&gt; substitution:&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;ARG&lt;/span&gt;&lt;span class="s"&gt; BASE_TAG=v2.1&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; registry.company.com/platform/base-image:${BASE_TAG}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simple grep for the image name finds this. A simple grep for the &lt;em&gt;version&lt;/em&gt; doesn't, because the version is in a variable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker Compose files&lt;/strong&gt; reference images differently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.company.com/platform/base-image:v2.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a dependency on the same image, declared in a completely different file format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI pipeline configs&lt;/strong&gt; often pull images directly:&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;build&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;registry.company.com/platform/base-image:v2.1&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;make build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A GitLab CI &lt;code&gt;image:&lt;/code&gt; directive or a GitHub Actions &lt;code&gt;container:&lt;/code&gt; field is another consumption point for the same base image, and it's in yet another file.&lt;/p&gt;

&lt;p&gt;Then there's the &lt;strong&gt;producer-side problem&lt;/strong&gt;. Knowing which repos &lt;em&gt;consume&lt;/em&gt; the image is half the story. You also need to know which repo &lt;em&gt;builds&lt;/em&gt; it. That's usually a CI pipeline with &lt;code&gt;docker build&lt;/code&gt; and &lt;code&gt;docker push&lt;/code&gt; commands, not something declared in a manifest. Connecting the consumer graph to the producer requires cross-referencing multiple file types within and across repos.&lt;/p&gt;

&lt;p&gt;No single tool today connects all of these surfaces into one view.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the full answer requires
&lt;/h2&gt;

&lt;p&gt;To reliably answer "who consumes this base image," you need a system that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scans every repo in the org&lt;/strong&gt;, not just the ones registered in a catalog&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parses Dockerfiles, Compose files, and CI configs&lt;/strong&gt;, because the same image can be referenced in all three&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handles variable substitution&lt;/strong&gt; in &lt;code&gt;FROM&lt;/code&gt; statements by resolving &lt;code&gt;ARG&lt;/code&gt; defaults&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detects which repos produce which images&lt;/strong&gt; by scanning CI configs for build and push commands&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeps the results current&lt;/strong&gt; through scheduled or event-triggered rescans&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Makes the graph queryable&lt;/strong&gt;: "show me every consumer of &lt;code&gt;platform/base-image&lt;/code&gt;, grouped by version"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is one of the specific problems I'm building &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt; to solve. It scans a GitLab or GitHub org, parses Dockerfiles (including multi-stage builds, ARG defaults, and Compose files), detects which repos produce which images via CI config analysis, and builds a cross-repo dependency graph you can query by artifact.&lt;/p&gt;

&lt;p&gt;The result: when that CVE drops, you click on the base image in the graph and immediately see every repo that depends on it, which version each one uses, and who owns them. No grepping. No Slack archaeology. No stale spreadsheet from last quarter.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How is your team solving this today?&lt;/strong&gt; I'd genuinely like to know — drop a comment or find me at &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;riftmap.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>infrastructure</category>
      <category>security</category>
    </item>
  </channel>
</rss>
