Repo Structure


The repo structure has been going through a lot of iterations lately but we have began to settle in on a few patterns. We started off with a flatter 1 to 1 approach of modules in terraform to modules in terragrunt. This blew up the repo with all kinds of remote state references and the amount of boilerplate was difficult to deal with. Running a simple EC2 instance decoupled into it's constituent parts turned into calling 5-6 separate modules which became burdensome to manage on the terragrunt side. We are now moving to a more master module approach where each module has more infrastructure being deployed and tested within various contexts it is being called.

Big problem with the 1 to 1 approach was these dependency blocks as shown below where the absolute path needed to be preserved or else the whole thing broke.

dependency "vpc" {
  config_path = "../../network/vpc-main"
}

We have replaced this with the following reference:

locals {
...
  secrets = yamldecode(file(find_in_parent_folders("secrets.yaml")))
  network = find_in_parent_folders("network")
}

dependencies {
  paths = [local.network]
}

dependency "network" {
  config_path = local.network
}

inputs = {
  # Network
  security_group_id = dependency.network.outputs.security_group_id
  subnet_id = dependency.network.outputs.public_subnets[0]
}

This allows us to inherit dependencies that are in the parent directories of the module being called. The dependencies block also allows us to specify a dependency for a module that needs to be called before the module itself gets called allowing us to run terragrunt apply-all and have all the dependent modules called in a graph. In essence, this allows us to reconstruct the graph that we are decomposing into individual modules. This is critical when building large terraform projects as you need a way to join many modules together in an apply and not have them break adjacent modules.

One of the problems with this though is that you can easily bork your infrastructure if you run a destroy-all with a dependency on a module that is shared by other adjacent modules. A good example of this is with the VPC where many modules will be dependent on. A single destroy-all command could take down your whole infrastructure if it has the VPC as a dependency on it. For that reason and others, we try to decouple parts of the infrastructure that won't be changing often and refer to them by data sources from terraform or inputs from terragrunt without dependencies.