Free — no signup required

Dependency Inversion in IaC

3 min read

The Problem: Tight Coupling

Tight coupling in infrastructure means a module assumes too much about its environment. It creates resources it should receive, or hardcodes values it should accept as inputs. The result is a module that only works in one specific context.

Here is a concrete example. You have an app module that creates an EC2 instance. The instance needs a Security Group. The naive approach has the module create its own security group with a hardcoded VPC ID baked directly into the resource. This module is now coupled to a specific VPC. It creates its own security group, so you cannot reuse an existing one. If you want to deploy this app into a legacy VPC where the security group already exists and is managed by a separate team, you cannot — the module will try to create a duplicate and fail.

The Solution: Inject the Dependency

Dependency Inversion means: instead of a module creating its dependencies, it accepts them as inputs. The module declares what it needs — a security group ID, a subnet ID — as variables, and the caller decides how to provide them. That decoupling means the exact same module code works whether the caller passes in the ID of a security group Terraform just created moments earlier in the same run, or the ID of a security group that already existed in a legacy environment and is managed by an entirely different team. The app module's code is identical in both scenarios. Only the caller changes.

Dependency Inversion and the Data Source Pattern

A related technique is using Terraform data sources to look up existing resources rather than hardcoding their IDs. This is a softer form of dependency inversion — the module resolves its own dependency at plan time (querying for a security group matching a known tag, say) rather than requiring the caller to pass it explicitly.

This is useful when the dependency is stable and discoverable by convention (e.g., always tagged with a known name). However, it introduces an implicit dependency on naming conventions existing in the account, which can make modules harder to test in isolation. Prefer explicit variable injection for module interfaces; use data sources for cross-stack lookups at the root level.

Interview Tip

A common interview scenario: "Your team has a Terraform module that works perfectly in the dev environment but fails in production because the VPC and security groups are managed by a different team's Terraform state." The weak answer is "import the resources." The strong answer describes Dependency Inversion: refactor the module to accept vpc_id and security_group_id as input variables, then use terraform_remote_state or data sources at the root level to resolve those IDs from the other team's state. This decouples the modules and respects team ownership boundaries.

Key Point: Dependency Inversion is the pattern that makes modules genuinely reusable across environments. A module that creates its own dependencies is really a script in disguise — it only works in one context. A module that accepts its dependencies as inputs is a true building block that can be composed into any environment. When reviewing a module, ask: "Does this module create anything it could instead receive?" If yes, that's a refactoring opportunity.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free