Skip to content
Notifications
Clear all

How do I get started with multi-cloud Terraform without it becoming a mess?

20 Posts
20 Users
0 Reactions
3 Views
(@danielh)
Estimable Member
Joined: 2 weeks ago
Posts: 90
 

Hey Eric, great topic. That six-month wall is real, and I've seen teams bounce right off it.

For the initial move, I can't emphasize separate state enough, but I'd add a twist: also separate your root modules per cloud from day one. Even a simple `infra/aws/`, `infra/gcp/` structure forces you to think about composition, not just state isolation. It makes that "separate apply commands in the pipeline" advice a natural fit.

On naming and team coordination, the most valuable doc we created early was a one-page "contract" for shared variables, like `environment`, `project_id`, and `required_tags`. It wasn't perfect, but having a written reference stopped so many "what should we call this?" debates. When the GCP team needed a new field, they'd propose an addition to that contract, not just their module. It kept everyone's interfaces aligned without needing to be in each other's code all the time. The first draft will be wrong, and that's okay.


Keep deploying!


   
ReplyQuote
(@cloud_migrate_tom)
Estimable Member
Joined: 4 months ago
Posts: 101
 

That one-page contract idea is gold, honestly. It's the kind of simple thing that sounds obvious after the fact but stops so many headaches. My worry is about the first draft, like you mentioned. How do you get everyone to actually agree to use it while knowing it's going to change? There's gotta be some tension there.

Also, the `infra/aws/`, `infra/gcp/` split at the root level is something I haven't seen mentioned yet. It seems like it would really cement the separation early on. Does that mean you end up with duplicated provider configurations in each of those root directories, or is there a clever way to share that?


One step at a time


   
ReplyQuote
(@emmal)
Estimable Member
Joined: 2 weeks ago
Posts: 92
 

Getting that first draft agreed on is the hardest part. We called ours a "living contract" and made a rule that any change required updating the shared doc and a quick team sync, but the initial version was deliberately scoped to just the three things we knew we absolutely needed: environment, region, and a tags map. It was small enough that no one could reasonably object.

For the separate root directories, we ended up with duplicated provider configs, and I think that's fine. The slight duplication is a small price for the absolute isolation. Trying to share a provider config across roots felt clever but introduced a hidden dependency that broke when someone ran a plan from the wrong directory. The clarity of each cloud having its own complete, self-contained root module outweighed the DRY principle in this case.



   
ReplyQuote
(@briana)
Estimable Member
Joined: 2 weeks ago
Posts: 133
 

Oh, that "living contract" rule is such a good team discipline. We did something similar, but we put the contract itself in version control as a simple JSON schema file in a `specs/` folder. Any PR that touched a module variable had to update that schema first. It made the "quick team sync" asynchronous and gave us a commit trail of how the interface evolved.

And I'm 100% with you on the duplicated provider configs. We tried the "clever" shared config for about a week and it was a nightmare. The isolation of separate roots isn't just about preventing mistakes, it lets you upgrade or experiment with one provider's version without any risk to the other. I'd take that over a little copy-paste any day. The real trick was using a small `_provider.tf` file in each root, so it's obvious and contained.


Backup first.


   
ReplyQuote
(@carlr)
Estimable Member
Joined: 2 weeks ago
Posts: 120
 

Separate state is a prerequisite, but the real first move is enforcing a clear module ownership model. You need one team or person who "owns" the interface (the root modules, the variable contracts) even if the implementation details live in cloud-specific modules owned by different teams. Without that, you get interface drift and the six-month wall becomes a three-month wall.

On naming, the variance is inevitable, so we stopped fighting it directly. Inside a cloud-specific module, you use the provider's naming. The shared contract only standardizes the *intent*, not the field name. For example, the contract variable is `network_cidr`. The AWS module's internal variable might be `vpc_cidr_block`, the GCP one `subnet_ip_cidr_range`. The module adapts. Trying to make `cidr` mean the same concrete thing across clouds just creates confusion.

> What documentation or agreement do you put in place early on
A single `SPECIFICATION.md` that *only* defines the common input/output contract for your core abstractions (network, cluster, database). It's separate from any module's README. Its only job is to be the source of truth for what the multi-cloud promise actually is. Every deviation from it is a bug.


Your fancy demo doesn't scale.


   
ReplyQuote
Page 2 / 2