[Webinar] How to effectively prove compliance in a multi-cloud, multi-IaC world

➡️ Register now

Terraform

Atlantis with Terragrunt – Automate Terraform Workflows

atlantis with terragrunt

🚀 Level Up Your Infrastructure Skills

You focus on building. We’ll keep you updated. Get curated infrastructure insights that help you make smarter decisions.

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.

What is Atlantis?

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.

See: Atlantis: Terraform Pull Request Automation (Tutorial)

What is Terragrunt?

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.

How to run Atlantis with Terragrunt

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:

  1. Download Atlantis, Terraform, and Terragrunt.
  2. Prepare a sample Terraform and Terragrunt configuration.
  3. Configure Atlantis for Terragrunt.
  4. Set up a GitHub webhook for Atlantis.
  5. Start the Atlantis server.
  6. 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:

Run a Terragrunt workflow

The result of the plan operation is added as a comment to the pull request:

comment to the pull request terragrunt with atlantis

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:

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:

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:

browse the Atlantis web UI

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.

Best practices for integrating Atlantis with Terragrunt in CI/CD pipelines

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.

Alternative to Atlantis - Spacelift

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 stacks

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

Key takeaways

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.

Learn more

The Practitioner’s Guide to Scaling Infrastructure as Code

Transform your IaC management to scale

securely, efficiently, and productively

into the future.

ebook global banner
Share your data and download the guide