Terragrunt dependencies: The power of “Terragrunt run-all” command

Amit Karni
Israeli Tech Radar
Published in
6 min readApr 18, 2023

This article, will explore the power of Terragrunt’s dependencies feature and how it can help you manage your infrastructure as code more effectively. It will also cover the “terragrunt run-all” command and provide some hands-on examples of how to use it to deploy resources on Azure.

Introduction

As part of a recent project I worked on, I was required to develop and implement infrastructure as code (IaC) and applications deployment on Kubernetes to run on Azure infrastructure for the company’s SaaS application.

The effectiveness of the deployment process was important, guaranteeing it could become completely functional as quickly and seamlessly as possible. This would make building and destroying easier, faster, and smoother.

Integrating infrastructure as code (IaC)-(Terraform and Terragrunt) with Helm(Terraform Helm provider), and GitOps-(ArgoCD) provided a seamless deployment experience from top to bottom.

The “terragrunt run-all” command was especially useful, enabling us to deploy 12+ distinct cloud resources, each defined in separate Terraform modules, and essential company’s and third-party applications/operators on Kubernetes.
This powerful command simplified the end-to-end deployment process, starting from provisioning the first cloud resource to achieving a fully operational cloud infrastructure and applications running on a Kubernetes cluster, all with a single “terragrun run-all” command execution.

This approach also ensured proper deployment and configuration of critical third-party Kubernetes applications within the cluster. Combining Terraform, Terragrunt, Helm(TF-Provider) and GitOps-ArgoCD streamlined the deployment pipeline, reduced errors, simplified maintenance, and allowed us to efficiently deploy and destroy complex infrastructures, ultimately delivering a reliable and consistent environment for our organization super fast.

sample output from my "terragrunt run-all apply" command execution:

amit.karni:$ tg run-all apply
INFO[0000] The stack at /Users/amit.karni/Company/Company-config/azure/Project/tg-modules will be processed in the following order for command plan:
Group 1
- Module /Users/amit.karni/Company/Company-config/azure/Project/tg-modules/azure-firewall
- Module /Users/amit.karni/Company/Company-config/azure/Project/tg-modules/azure-resource_group
Group 2
- Module /Users/amit.karni/Company/Company-config/azure/Project/tg-modules/azure-vnet
Group 3
- Module /Users/amit.karni/Company/Company-config/azure/Project/tg-modules/azure-nsg
- Module /Users/amit.karni/Company/Company-config/azure/Project/tg-modules/azure-route-table
Group 4
- Module /Users/amit.karni/Company/Company-config/azure/Project/tg-modules/azure-vnet-peering
Group 5
- Module /Users/amit.karni/Company/Company-config/azure/Project/tg-modules/azure-dns
Group 6
- Module /Users/amit.karni/Company/Company-config/azure/Project/tg-modules/azure-mysql/company-app-sql-name
Group 7
- Module /Users/amit.karni/Company/Company-config/azure/Project/tg-modules/azure-storage-account(nfs)
- Module /Users/amit.karni/Company/Company-config/azure/Project/tg-modules/for_each-azure-linux-vm
Group 8
- Module /Users/amit.karni/Company/Company-config/azure/Project/tg-modules/azure-aks/company-app-cluster-name
Group 9
- Module /Users/amit.karni/Company/Company-config/azure/Project/tg-modules/azure-helm/argocd-and-applications

Difficulties involved in managing infrastructure as code dependencies

Managing infrastructure as code (IaC) can quickly become a challenging task as the number of resources and dependencies in your environment grows. As a result, it is essential to have a tool that makes it easier to manage dependencies and ensures that resources are deployed in the correct order. Terragrunt is one such tool that offers a simple solution to this problem with its “dependencies” feature.

Terragrunt Dependencies: What They Are and How They Work

Terragrunt dependencies establish the deployment order of resources and can be defined between modules. By doing so, Terragrunt ensures that dependent modules are deployed before the ones relying on them. To define a dependency, specify the path to the dependent module in the “dependencies” block of the deploying module. For example, if “module-b” depends on “module-a,” the “terragrunt.hcl” file for “module-b” should include:

dependencies = [ "../module-a" ]

This informs Terragrunt that “module-a” should be deployed before “module-b.”

Using Terragrunt Dependencies with “terragrunt run-all” and Delivering Outputs

After defining dependencies, you can use the “terragrunt run-all” command to deploy all modules in the correct order. This command traverses the dependency graph, ensuring all dependencies are met. To use “terragrunt run-all”, navigate to your Terragrunt project’s root and run:

terragrunt run-all apply

This applies all Terraform configurations in your project in the correct order. The “run-all” command can also be used with other Terragrunt commands like “plan” and “destroy.”

Terragrunt also simplifies output delivery between dependent modules. The “output” command retrieves outputs from a specific module, and the “locals” block in the “terragrunt.hcl” file can access and store these outputs. By utilizing the “dependency” keyword within the “locals” block, you can pass outputs from one module to another, enhancing the modularity and reusability of your infrastructure code.

Hands-On Tutorial: Deploying 4 Resources on Azure

In this hands-on tutorial, we’ll explore how to deploy four resources to Azure Cloud using Terragrunt, a popular infrastructure provisioning tool. We’ll utilize Terragrunt’s dependency management and run-all command to deploy everything at once.

Prerequisites

Before starting, you’ll need to have the following installed and configured:

Also, ensure you have an active Azure subscription and are logged in via Azure CLI:

az login

Project Structure

Our project will have the following structure:

.
├── resource_group
│ ├── main.tf
│ └── terragrunt.hcl
├── storage_account
│ ├── main.tf
│ └── terragrunt.hcl
├── virtual_network
│ ├── main.tf
│ └── terragrunt.hcl
└── virtual_machine
├── main.tf
└── terragrunt.hcl

We’ll create four resources: Resource Group, Storage Account, Virtual Network, and Virtual Machine. Each resource will have a main.tf file containing the Terraform configuration and a terragrunt.hcl file for Terragrunt settings.

Define Resources

Resource Group

In the resource_group folder, create a main.tf file with the following content:

resource "azurerm_resource_group" "example" {
name = var.resource_group_name
location = var.location
}

Now create a terragrunt.hcl file in the same folder:

include {
path = find_in_parent_folders()
}

terraform {
source = "../resource_group"
}

inputs = {
resource_group_name = "myResourceGroup"
location = "East US"
}

Storage Account

In the storage_account folder, create a main.tf file with the following content:

resource "azurerm_storage_account" "example" {
name = var.storage_account_name
resource_group_name = var.resource_group_name
location = var.location
account_tier = "Standard"
account_replication_type = "LRS"
}

Now create a terragrunt.hcl file in the same folder:

include {
path = find_in_parent_folders()
}

terraform {
source = "../storage_account"
}

dependency "resource_group" {
config_path = "../resource_group"
}

inputs = {
resource_group_name = dependency.resource_group.outputs.resource_group_name
location = dependency.resource_group.outputs.location
storage_account_name = "mystorageaccount"
}

Virtual Network

In the virtual_network folder, create a main.tf file with the following content:

resource "azurerm_virtual_network" "example" {
name = var.virtual_network_name
resource_group_name = var.resource_group_name
location = var.location
address_space = ["10.0.0.0/16"]
}

Now create a terragrunt.hcl file in the same folder:j

include {
path = find_in_parent_folders()
}

terraform {
source = "../virtual_network"
}

dependency "resource_group" {
config_path = "../resource_group"
}

inputs = {
resource_group_name = dependency.resource_group.outputs.resource_group_name
location = dependency.resource_group.outputs.location
virtual_network_name = "myVirtualNetwork"
}

Virtual Machine

In the virtual_machine folder, create a main.tf file with the following content:

resource "azurerm_virtual_machine" "example" {
name = var.virtual_machine_name
resource_group_name = var.resource_group_name
location = var.location
vm_size = "Standard_DS1_v2"
network_interface_ids = [azurerm_network_interface.example.id]

storage_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}

storage_os_disk {
name = "osdisk"
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Standard_LRS"
}

os_profile {
computer_name = var.virtual_machine_name
admin_username = "ubuntu"
admin_password = "Password123!"
}

os_profile_linux_configuration {
disable_password_authentication = true
ssh_keys {
path = "/home/ubuntu/.ssh/authorized_keys"
key_data = var.ssh_public_key
}
}
}

resource "azurerm_network_interface" "example" {
name = "${var.virtual_machine_name}-nic"
location = var.location
resource_group_name = var.resource_group_name
ip_configuration {
name = "internal"
subnet_id = var.subnet_id
private_ip_address_allocation = "Dynamic"
}
}

Now create a terragrunt.hcl file in the same folder:

include {
path = find_in_parent_folders()
}

terraform {
source = "../virtual_machine"
}

dependency "resource_group" {
config_path = "../resource_group"
}

dependency "virtual_network" {
config_path = "../virtual_network"
}

inputs = {
resource_group_name = dependency.resource_group.outputs.resource_group_name
location = dependency.resource_group.outputs.location
virtual_machine_name = "myVirtualMachine"
subnet_id = dependency.virtual_network.outputs.example_subnet_id
ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDc..."
}

Deploying Resources

With all resources defined, we can now use Terragrunt’s run-all command to deploy everything at once. In the root directory, create a terragrunt.hcl file with the following content:

locals {
common_vars = {
resource_group_name = "myResourceGroup"
location = "East US"
}
}

remote_state {
backend = "azurerm"
config = {
resource_group_name = local.common_vars.resource_group_name
storage_account_name = "mytfstatestorage"
container_name = "tfstate"
key = "${path_relative_to_include()}/terraform.tfstate"
}
}

This configures a remote state backend to store the Terraform state in Azure Blob Storage(Blob storage should be prepared beforehand)

Now, from the root directory, run the following command:

terragrunt run-all apply

Terragrunt will automatically determine the dependencies between resources and deploy them in the correct order. After the deployment is complete, you’ll have your four resources up and running in Azure.

To clean up the resources when you’re done, use the following command:

terragrunt run-all destroy

Conclusion

Terragrunt’s dependencies feature is a powerful tool for managing infrastructure as code. By defining dependencies between modules, you can ensure that resources are deployed in the correct order and avoid errors that arise from incorrect order or missing modules.
The “terragrunt run-all” command simplifies the process of deploying resources with dependencies, making it easier, faster, and more efficient to manage your infrastructure as code. With the examples provided in this article, you should now have a good understanding of how to use Terragrunt dependencies to deploy resources on Azure.

In this tutorial, we demonstrated how to deploy four resources to Azure Cloud using Terragrunt dependencies. We also showed how to use Terragrunt’s run-all command to deploy everything at once. This powerful tool streamlines the deployment process and makes it easier to manage complex infrastructure environments.

--

--

Amit Karni
Israeli Tech Radar

Senior DevOps Engineer with a passion for keeping up with new technologies. https://www.linkedin.com/in/amit-karni