You want to have a single Terraform root module, which incorporates dev, test, prod as module blocks?
Personally, that sounds like a mistake to me.
It commits you to having a single Terraform state file that encompasses all of your environments, rather than one per environment, which means that:
You’ve sacrificed the ability to test anything that operates at the level of the Terraform state in lower environments. You want to move to a newer version of Terraform? Can’t deploy to dev first, it takes effect on everything including prod at once. You find you need to perform some manual manipulation on your Terraform state? Can’t test it cleanly on a lower environment first, as all of your environments are in one state.
You’ve sacrificed some scalability - Terraform doesn’t deal well with huge states, and you’ve applied a multiplier to yours by combining all environments in one.
You’ve sacrificed concurrency - if a Terraform run is working on deploying a change to one environment, you have to let it finish before it will be able to start doing something else.
You’ve introduced unwanted coupling between environments - let’s say, one day, an engineer wants to push some minor change to dev, and the Terraform plan flags up that it intends to make some unexpected change to prod - maybe there has been some manual change made outside of Terraform control, which Terraform wants to revert, or maybe it’s a bug. Whatever the reason, either your dev pipeline is now blocked whilst you diagnose prod, or the change goes unnoticed and rolls out without the oversight appropriate to production changes.
In conclusion to that part:
You absolutely want separate root modules per environment.
Exactly how you model this in Git is up to you.
I would be hesitant to use a monorepo, because I regard it as misleading: Git tags are inherently for the whole repo, so if you tag versions, you have to have internal team documentation which explains “Oh, our tags relate to these sub-paths within the repository, but not those other sub-paths”.
Whether you use Git branches for each environment, or separate subdirectories within the same branch instead, is up to you. There are trade-offs with each:
- With branches, you can’t update all environments in one PR - cross-cutting refactors are harder.
- With directories, you can’t merge between them, and are reduced to manually copying the changes, and re-reviewing them.
Lastly, consider that whilst deploying tagged versioned releases makes sense for prod, you almost certainly do not want to set up your dev environment such that every Terraform change needs to be merged to the main branch, tagged, and have a version number updated in another file, before it ever gets to dev.
This may involve a Terraform configuration that fetches the application module directly from a Git branch, in the dev environment (but then, think carefully about how and when Terraform runs will be triggered, based on which paths in which repositories will be changing.)