Terraform
Terraform Module Design for Multi-Cloud Portability
How to design Terraform modules that remain reusable across AWS, Azure, GCP, and OCI without provider-specific spaghetti.
2026-07-28 · 2 min read
Terraform modules become difficult to reuse when they accidentally encode implementation details from one provider into the module’s public API.
To build portability, your module interface must represent platform concepts—not provider resources.
Interface-first module design
Start by defining an interface layer:
- Inputs: provider-agnostic configuration (for example, desired subnets vs “AWS subnets”).
- Outputs: stable outputs that downstream modules can depend on.
- Implementation: provider-specific modules under the hood.
variable "cidr_block" {
type = string
description = "Primary CIDR block for the network."
}
output "network_id" {
value = module.provider_network.id
}
When a shared module’s variables mention provider identifiers (regions, provider-specific feature toggles), you’ve usually created tight coupling.
Stable outputs are your API contract
Treat output names and semantics as your module contract.
If network_id means something different on each cloud, downstream modules lose portability and teams reintroduce conditional logic everywhere.
Keep provider configuration out of shared modules
Shared modules should not define provider blocks with hard-coded credentials or region decisions.
Instead:
- Configure providers at the root/platform module.
- Pass only the minimal required configuration into modules.
This keeps modules testable and predictable.
Testing strategy for portability
Your CI should validate the same module contract across clouds:
- Run module tests for each cloud implementation.
- Use separate test accounts (never production).
- Pin provider versions so module behavior doesn’t drift between releases.
OrchesTerra’s repository intelligence can help you standardize the module structure you generate from your services—so your multi-cloud design is consistent from day one. Explore repository intelligence for details.
Common pitfalls
- Using conditional logic inside shared modules instead of separate implementations.
- Leaking provider-specific inputs into shared interfaces.
- Renaming outputs and breaking downstream callers without version discipline.
- Unpinned provider versions causing silent behavior changes.
- Shared state that mixes cloud environments.
If you want generated IaC that stays governable, start with a starter trial.
