<?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>Forem: Daniel Westgaard</title>
    <description>The latest articles on Forem by Daniel Westgaard (@danielwe).</description>
    <link>https://forem.com/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>Forem: Daniel Westgaard</title>
      <link>https://forem.com/danielwe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/danielwe"/>
    <language>en</language>
    <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://forem.com/danielwe/how-to-find-every-consumer-of-your-terraform-module-enh</link>
      <guid>https://forem.com/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://forem.com/danielwe/how-to-find-every-consumer-of-your-docker-base-e32</link>
      <guid>https://forem.com/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>
