HashiCorp Terraform is a declarative tool for infrastructure as code (IaC) that has been around since 2014. It offers considerable flexibility in how you run it: on your local system, integrated as part of your CI/CD pipelines, on your build servers, bundled in some IaC platforms, or in another way. Smooth workflows for collaboration on Terraform configurations are crucial for successful IaC.
When you write Terraform configurations at scale for large cloud infrastructures, Terraform code, such as provider definitions and state backend configurations, will repeat throughout all your configurations. Keeping things like provider versions up to date across all Terraform configurations can be tedious.
A whole ecosystem of tools has grown around Terraform to help you run Terraform at scale. In this post, we focus on Atlantis and Terragrunt, specifically, how to run Atlantis with Terragrunt.
Note: Atlantis and Terragrunt also work well with OpenTofu. You can replace Terraform with OpenTofu for everything discussed in this blog post.
If you are looking for a TACOS with more features than Atlantis and that supports tools other than Terraform and Terragrunt — such as Pulumi, CloudFormation, Kubernetes, and Ansible – Spacelift is the answer.
You install Terraform as a single binary on your local system or almost any other system (e.g., your CI/CD platform or shared build server) and run commands to provision, update, and destroy cloud infrastructure.
It is rarely a good idea to run Terraform on your local system for your production workloads. This can lead to multiple issues if you have more than one developer working on the same Terraform configuration. You should run Terraform on a shared external platform instead. This allows you to collaborate more easily with others on your Terraform configurations.
Atlantis is a tool used for running Terraform based on events in your source control system. It allows you to automate Terraform via pull requests and get the results of terraform plan
and terraform apply
back as comments on the pull request.
With Atlantis, you can collaborate on Terraform configurations directly in pull requests within your version control system. Instead of simply making a change to your Terraform configurations and proposing a change that other developers must review and interpret, you can also ensure the change is planned and applied directly in the pull request context. The results are reported back and are visible to everyone as part of the pull request.
Terragrunt is a layer on top of Terraform with several features to help you run Terraform at scale. Terragrunt was designed to help keep code cleaner by avoiding duplicate configuration (e.g., provider version specification across multiple Terraform configurations). However, Terragrunt has grown to support many more features.
In Terragrunt, you work with units. A unit is a directory containing a terragrunt.hcl file. This file references a Terraform module and provides input to this module. You can also use include blocks to load other configuration blocks (e.g., backend or provider configuration). This is how you avoid repeating this type of Terraform configuration multiple times.
Units can be grouped into Terragrunt stacks. A stack is a collection of units that combine to create a specific environment (e.g., dev, stage, or prod).
We will not examine Terragrunt’s features in this blog post, but we will show a small example of Terragrunt in the following section. For more information, check out our Terragrunt tutorial.
Why combine Atlantis with Terragrunt?
Atlantis is used to run Terraform in a collaborative manner from a git environment, whereas Terragrunt is used to remove repetitive elements from your Terraform configurations (often referred to as DRY — Don’t Repeat Yourself). Integrating Atlantis with Terragrunt enables teams that use Terragrunt workflows to benefit from Atlantis in the same way as teams running normal Terraform workflows. This leads to a unified approach to infrastructure as code for all teams.
In the following sections, we will demonstrate how to get started running Atlantis with Terragrunt. The goal is to provide a foundation for running Atlantis with Terragrunt.
These are the steps:
- Download Atlantis, Terraform, and Terragrunt.
- Prepare a sample Terraform and Terragrunt configuration.
- Configure Atlantis for Terragrunt.
- Set up a GitHub webhook for Atlantis.
- Start the Atlantis server.
- Run a Terragrunt workflow.
Walkthrough environment
This walkthrough will use the following environment for Atlantis:
- Atlantis is run on an Ubuntu virtual machine (AMD64 architecture) on Azure.
- Atlantis will be triggered by a webhook configured for a specific GitHub repository.
- Terraform state will be stored in blob storage on Azure.
You can find a working sample implementation for this environment in the accompanying GitHub repository found here.
There are many other supported options for deploying and hosting Atlantis, e.g., on Kubernetes using Helm. Using Kubernetes makes sense if you already have a Kubernetes cluster available for hosting shared applications. See the official Atlantis documentation for details.
Step 1. Download Atlantis, Terraform, and Terragrunt
Atlantis comes as a single binary written in Go, similar to both Terraform and Terragrunt.
For a virtual machine environment, you can download and run the Atlantis binary directly from the GitHub releases page. The current latest version of Atlantis at the time of writing is 0.35.1:
$ wget https://github.com/runatlantis/atlantis/releases/download/v0.35.1/atlantis_linux_amd64.zip
$ unzip atlantis_linux_amd64.zip
You should use Systemd or a similar tool to run Atlantis to ensure it starts a backup if the virtual machine reboots or if Atlantis crashes. In the accompanying GitHub repository, Systemd is configured on the Ubuntu virtual machine.
Atlantis needs the Terraform binary to be available before it can start, so ensure you download the desired version. If your organization requires this, you could potentially install multiple different versions of Terraform.
The Terraform binary can be downloaded from the HashiCorp releases page. At the time of writing, the latest available version of Terraform is 1.12.2:
$ curl --silent --remote-name https://releases.hashicorp.com/terraform/1.12.2/terraform_1.12.2_linux_amd64.zip
$ unzip terraform_1.12.2_linux_amd64.zip
Finally, because the goal is to run Terragrunt, you must also download the Terragrunt binary from the GitHub releases page. At the time of writing, the latest available version is 0.85.0:
$ wget https://github.com/gruntwork-io/terragrunt/releases/download/v0.85.0/terragrunt_linux_amd64
$ mv terragrunt_linux_amd64 terragrunt
Ensure each of the tools (Atlantis, Terraform, Terragrunt) is available in the PATH of the user running Atlantis. In the sample implementation, Atlantis is run by a dedicated atlantis user, and each binary is placed in the /usr/bin/
directory.
Step 2. Prepare a sample Terraform and Terragrunt configuration
We will use a simple Terragrunt and Terraform configuration to test with Atlantis.
We have a single Terraform module that generates a random integer within a specified range provided as input to the module. The full source code of this Terraform module is shown in the following code block.
terraform {
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.7"
}
}
}
variable "min" {
type = number
default = 1
}
variable "max" {
type = number
default = 100
}
resource "random_integer" "default" {
min = var.min
max = var.max
}
output "result" {
value = random_integer.default.result
}
This Terraform module will be provisioned in the dev and prod environments
Note that when working with Terragrunt, it is preferable to work with immutable module versions rather than directly with local modules, as we will do in this example. This setup is for illustrative purposes only.
We will use Terragrunt to generate the Terraform state backend configuration for each environment. We will use a generate block in a shared file in the root of the repository to generate a Terraform file with the correct configuration.
Place the following code in a root.hcl file in the root of the repository:
generate "backend" {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
terraform {
backend "azurerm" {
resource_group_name = "<your azure resource group>"
storage_account_name = "<your azure storage account>"
container_name = "state"
key = "${path_relative_to_include()}/terraform.tfstate"
use_azuread_auth = true
tenant_id = "<your tenant id>"
}
}
EOF
}
When you run Terragrunt a backend.tf file is generated dynamically from this configuration with a key argument set to e.g. dev/terraform.tfstate
and prod/terraform.tfstate
.
For each of the dev and prod environments, create a directory in the root of the repository and place a terragrund.hcl file in each directory. These will be our two Terragrunt units.
As an example, for the dev environment, the terragrunt.hcl file should resemble the following:
include "root" {
path = find_in_parent_folders("root.hcl")
}
terraform {
source = "../modules/random-number"
}
inputs = {
min = 1
max = 100
}
The root.hcl file is included using an include block to generate the backend.tf file automatically. Then an instance of the random-number module is created with specified inputs.
The corresponding file for the prod environment should look identical (possibly with other input values for the min and max variables).
The directory structure of the repository should now look like the following:
$ tree .
.
├── dev
│ └── terragrunt.hcl
├── modules
│ └── random-number
│ └── main.tf
├── prod
│ └── terragrunt.hcl
└── root.hcl
The Terraform and Terragrunt configuration is now ready and we can move on to configure Atlantis to work with Terragrunt.
Step 3. Configure Atlantis for Terragrunt
Atlantis has a sensible default configuration that allows you to get started quickly if you intend to use normal Terraform workflows.
However, Atlantis does not support Terragrunt by default. Terragrunt is considered a custom workflow, so it requires some additional configuration.
On your Atlantis server, you can add a server configuration file, often named repos.yaml, with the following content:
repos:
- id: "github.com/<your github handle>/<your repository name>"
branch: /.*/
allowed_overrides: [workflow]
allow_custom_workflows: true
This configuration file targets all branches for a specific repository for your GitHub handle. If you want to apply the same configuration to many GitHub repositories under the same GitHub handle, you can use an expression. If you need different configurations for different repositories, you can extend the repos list with additional entries.
We specify that we allow custom workflows and that this repository can override the default workflows. Both of these settings are required when working with Terragrunt through Atlantis.
This configuration file should be passed to the Atlantis binary when you start the server. See the section below for an example of this command.
In the repository containing the Terragrunt configuration, add a file in the root named atlantis.yaml with the following content:
version: 3
projects:
- dir: dev
workflow: terragrunt
- dir: prod
workflow: terragrunt
workflows:
terragrunt:
plan:
steps:
- run:
command: terragrunt plan -input=false -out=$PLANFILE
output: strip_refreshing
apply:
steps:
- run: terragrunt apply $PLANFILE
The atlantis.yaml file contains definitions of what the Terragrunt workflow looks like. There is a plan and an apply workflow. Two projects are defined, one for the dev environment and one for the prod environment. Each project uses the terragrunt workflow.
Step 4. Set up a GitHub webhook for Atlantis
Atlantis interacts with GitHub through a webhook. You can create this webhook at your organization level or for specific repositories. In the following example, the webhook is created for a single repository.
To secure the webhook, you should require a password that only Atlantis knows about.
Step 5. Start the Atlantis server
You are ready to start Atlantis. You can do so using the following command:
$ atlantis server \
--repo-config="repos.yaml" \
--atlantis-url="http://<your atlantis url>" \
--gh-user="${var.github_owner}" \
--gh-token="${var.github_token}" \
--gh-webhook-secret="${random_password.webhook_secret.result}" \
--repo-allowlist="github.com/<github handle>/<github repo name>" \
--web-basic-auth=true \
--web-username=atlantis \
--web-password="${random_password.web.result}"
The arguments are explained below:
--repo-config
is for providing the repo.yaml server configuration file we created above to allow repositories to use custom Terragrunt workflows.--atlantis-url
is for the URL where you are hosting your Atlantis server. In the accompanying GitHub repository, a DNS name is created for the Atlantis server.--gh-user
is the GitHub organization or username.--gh-token
is the PAT token used to authenticate Atlantis to GitHub.--gh-webhook-secret
is the webhook secret password.--repo-allowlist
is one or more repositories that Atlantis should be allowed to be run from. This is a required argument intended to prevent Atlantis being triggered from anywhere.--web-basic-auth
,--web-username
, and--web-password
is to enable basic auth for the Atlantis web interface. If this is not added, the web interface is available for anyone with network access to the Atlantis server.
If you run Atlantis on a different compute platform (e.g., Kubernetes) or use a different git provider (e.g., GitLab), you might need to modify the command or even modify how you start Atlantis. Refer to the relevant documentation for your environment.
Step 6. Run a Terragrunt workflow
Everything is set up, and we are ready to test Atlantis with Terragrunt.
Go to the repository where the Terragrunt configuration is located and make a change to dev/terragrunt.hcl:
include "root" {
path = find_in_parent_folders("root.hcl")
}
terraform {
source = "../modules/random-number"
}
inputs = {
# change min from 1 to 10
min = 10
max = 100
}
Next, commit the change to a new branch (e.g., named feat/increase-dev-min
) and open a pull request to propose the changes to the main branch.
Once the pull request is opened, you will see Atlantis being triggered. It will run a plan operation by default:
The result of the plan operation is added as a comment to the pull request:
Note that all interactions from Atlantis are shown as my user (mattias-fjellstrom) because, in this case, Atlantis is configured with a PAT token for my user. In a production scenario, you should create a GitHub app dedicated to Atlantis in your GitHub organization and configure Atlantis to use it instead of PAT tokens.
You can trigger a new plan operation by adding a new comment with the text atlantis plan
:
This allows you to iterate on the change you are implementing and re-run Terragrunt as often as required.
When you are satisfied with the plan, run atlantis apply
to apply the change:
Atlantis reports that the apply operation was successful. Close the PR, and the change will be merged to the main branch.
The philosophy of Atlantis is to apply changes before merging them to the main branch and closing the PR. This is because having to open a new PR to fix errors discovered in the apply operation can create potential issues before the errors are resolved. Now, you can ensure the apply operation is successful before merging the change to main.
If you browse the Atlantis web UI while you have a PR open, you will see that Atlantis has created a lock for the given project:
This means that if you open a different PR targeting the same project (the dev project in this case) while this lock exists, Atlantis will report an error:
This will avoid issues that would occur if you update the same environment from two different branches.
Running Atlantis with Terragrunt workflows is not significantly different from running Atlantis with normal Terraform workflows, but it does require additional configuration.
Standardize the Terragrunt workflows
To scale your Terragrunt operations beyond a single repository (as in the example from the previous section), you should move the Terragrunt workflow definition to the server configuration file. In the example above, the file was named repos.yaml and was provided to the Atlantis binary in the startup command.
Centralizing the Terragrunt workflow configuration allows other repositories to use the same workflows. Each team does not have to repeat the same workflow definitions in the atlantis.yaml file of each repository.
You can still allow each repository to override the server-configured Terragrunt workflow if needed. To enable this, set the allow_custom_workflows
setting to true and include workflow
in the allowed_overrides
array for the relevant repositories. See the example repos.yaml file for an Atlantis server below:
repos:
- id: "github.com/<your github handle>/<your repository name>"
branch: /.*/
allowed_overrides: [workflow]
allow_custom_workflows: true
Automate generation of Terragrunt workflows at scale
If your git repositories contain many terragrunt.hcl files for configuring different Terragrunt units, configuring custom workflows in the atlantis.yaml file could be complex. A community-driven project named terragrunt-atlantis-config helps you to generate the atlantis.yaml files for a given repository.
You can integrate terragrunt-atlantis-config with your Atlantis server and configure an Atlantis pre-workflow hook to run the tool every time Atlantis clones a repository.
Secure your Atlantis server
As with any production installation of Atlantis, you should secure it using the following means:
- Host Atlantis on a secure network only accessible from trusted networks (e.g. your on-prem network or a VPN/PAM solution).
- Secure the Atlantis server with TLS by providing a certificate keypair during startup.
- Secure the webhook connection to your version control system using a strong password.
- Secure the web UI using basic authentication. This is currently the only supported authentication mechanism. You could also build a custom authentication solution for Atlantis.
- Use the repository allow-list wisely; do not allow Atlantis to interact with all repositories in your organization if it’s unnecessary.
- Do not use Atlantis for public repositories, as this allows anyone to trigger Terraform/Terragrunt operations in your environment by simply adding comments on your pull requests.
Spacelift is an IaC management platform that helps you implement DevOps best practices. Spacelift provides a dependable CI/CD layer for infrastructure tools, including OpenTofu, Terraform, Pulumi, Kubernetes, Ansible, and more, letting you automate your IaC delivery workflows.
Spacelift’s fully customizable workflow allows you to control what happens before and after every runner phase, and you have full flexibility to integrate the third-party tools you want, thanks to custom inputs and the notification policy.
In addition, Spacelift’s stack dependencies allow you to create links between multiple configurations and pass outputs from one configuration to another without the extensive work required to do so with Terraform’s remote state datasource.
With Atlantis, you have to do some work to configure OPA, but with Spacelift’s native policies based on OPA, you don’t have to do anything to configure them. You can control various decision points inside the application and also implement powerful guardrails that ensure reliability.
Spacelift gives you native drift detection, and you can schedule tasks and Stack deletion to alert you to anything happening outside your workflow, and configure cron-based tasks. Atlantis can only rely on drift detection tools installed and configured outside of the Atlantis pipeline.
Spacelift offers a native module registry that also enables you to test the module to ensure everything is working properly. This is not available in Atlantis.
Self-service infrastructure can be easily implemented with Spacelift’s blueprints.
You can see a more detailed comparison below:
Spacelift | Atlantis | |
Available frameworks | OpenTofu
Terraform Terragrunt Kubernetes CloudFormation Pulumi Ansible |
OpenTofu
Terraform Terragrunt |
SaaS offering | ✅ | ❌ |
User interface | Dedicated User Interface | Same as your VCS |
State management | ✅Can host your state, but you are free to host it wherever you want | ❌You need to handle your state |
SSO | ✅SAML 2.0 | ❌None |
Policy as code | ✅OPA | ❌ Only with integration |
Webhooks | ✅ | ✅ |
Private module registry | ✅ Built-in + functional and compliance testing solution for new module versions | ❌No registry |
VCS-driven runs + pull request-driven runs | ✅Yes for all supported frameworks | ✅Yes for all supported frameworks |
High availability | ✅ | Requires additional effort |
Sophisticated workflows | ✅Easy to build sophisticated workflows with Stack dependencies and Custom Inputs | Requires writing a lot of code to achieve a sophisticated workflow |
Resource visualization | ✅Automatic architecture trees built after resources are deployed | ❌None |
Drift detection | ✅ Built-in + optional remediation | ❌None |
Cost estimation | ✅Native Integration with Infracost | ❌ Only with integration |
Audit logs | ✅ | ✅ |
Atlantis comes with a default configuration for Terraform workflows. Configuring Atlantis for Terragrunt workflows requires additional effort.
The high-level steps to set up Atlantis with Terragrunt are:
- Install Atlantis, Terragrunt, and Terraform in the environment where you want to run Atlantis.
- Configure the Atlantis server through a server configuration file to allow repositories to use custom workflows. By default, Atlantis uses Terraform workflows, not Terragrunt workflows.
- Configure a password-protected webhook for your git repository that Atlantis will use to interact with the repository.
- Start the Atlantis server by providing the necessary configuration, e.g., the server configuration file, git details, and the webhook password.
- Add an atlantis.yaml configuration file to your git repository, where you configure your Terragrunt workflows. Alternatively, configure common Terragrunt workflows on the server, or integrate your Atlantis server with the terragrunt-atlantis-config tool to automatically generate these files.
You are now ready to create a new branch, implement a change in your Terragrunt units, and interact with Atlantis through a pull request.
To try out Spacelift and further enhance your workflow, create a free account here, and book a demo to speak with one of our engineers.
Automate Terraform deployments with Spacelift
Automate your infrastructure provisioning, build more complex workflows based on Terraform using policy as code, programmatic configuration, context sharing, drift detection, resource visualization, and many more.