---
title: Terragrunt Scale
description: Signing up for Terragrunt Scale Free Tier and walking through the onboarding wizards
slug: guides/ci-with-terragrunt/terragrunt-scale
sidebar:
  order: 3
---

import { Aside } from '@astrojs/starlight/components';
import FileTree from '@components/vendored/starlight/FileTree.astro';
import signupPage from '../../../../assets/img/guides/continuous-integration-with-terragrunt/signup-page.png';
import onboardingWizard from '../../../../assets/img/guides/continuous-integration-with-terragrunt/onboarding-wizard.png';
import selectScm from '../../../../assets/img/guides/continuous-integration-with-terragrunt/select-scm.png';
import selectCloud from '../../../../assets/img/guides/continuous-integration-with-terragrunt/select-cloud.png';
import bootstrapScript from '../../../../assets/img/guides/continuous-integration-with-terragrunt/bootstrap-script.png';

Terragrunt Scale is Gruntwork's solution for running Terragrunt at scale. You can learn more about it [here](/terragrunt-scale/overview/).

Onboarding is organized into three short wizards:

1. **Connect your source control manager**, which installs the Gruntwork GitHub App and picks the repository to set up.
2. **Run your first pipeline**, which installs Pipelines in your repository and walks you through a full plan/apply/destroy cycle without using any cloud credentials.
3. **Connect your cloud**, which provisions OIDC authentication and the IAM roles Pipelines uses to deploy real infrastructure.

The ordering is deliberate. You get a working pipeline that plans on every PR and applies on merge before you connect AWS, so you can see the GitOps workflow end to end before any real infrastructure or credentials are involved.

By the end of this section, you'll have a GitHub repository with Terragrunt infrastructure code, GitHub Actions workflows for plan-on-PR and apply-on-merge automation, OIDC authentication to your AWS account, and a pipeline ready to deploy real infrastructure.

## Sign up for Terragrunt Scale Free Tier

Head to the [Terragrunt Scale Free Tier signup](https://app.gruntwork.io/signup/terragrunt-scale-free-tier) to create an account. The free tier supports up to 25 [units](/getting-started/terminology/#unit) with a convenient onboarding wizard for GitHub and AWS. No credit card is required for signup.

<img src={signupPage.src} alt="Terragrunt Scale Free Tier signup page" />

After signing up, the home screen presents the three setup steps. The first step unlocks the other two, and you can step away and pick back up where you left off at any point.

The walkthrough below captures the experience so you can see what each wizard does before you commit to running it yourself. Expand it when you're ready to follow along.

<Aside type="note">
The screenshots throughout this guide might not match exactly what you see in the Gruntwork Developer Portal. We're constantly improving the onboarding experience, so the wording and layout can change between releases. The overall flow stays the same.
</Aside>

<details>
<summary>Walkthrough screenshots</summary>

<img src={onboardingWizard.src} alt="Terragrunt Scale home screen showing the three setup steps" />

## Connect your source control manager

Right now, the only Source Control Manager (SCM) the onboarding wizard supports is GitHub. Pipelines also works with GitLab, but the wizard doesn't support GitLab yet.

<img src={selectScm.src} alt="Select your source control manager step in the onboarding wizard" />

The wizard prompts you to install the [Gruntwork.io](https://gruntwork.io/) GitHub App on your GitHub account or organization. This app is what allows Terragrunt Scale to open pull requests and post plan results as comments on them, and to check out the Git repositories you give it access to. It acquires this access from within your GitHub Actions workflows, and it does not access your cloud infrastructure or your OpenTofu state.

When prompted, choose "Only select repositories" and select the repository you want to set up.

After the app is installed and verified, the wizard asks you to choose the repository to configure. You can select an existing infrastructure repository or create a new empty one and select it here.

A fresh, empty repository works best. If you point the wizard at an existing repository, the pull requests it opens can conflict with files already in place.

## Run your first pipeline

This wizard installs Pipelines in your repository and teaches the GitOps workflow without using any cloud credentials. It does this with a placeholder unit that creates a `null_resource`, which exercises the full plan/apply cycle without provisioning anything in AWS.

You'll work through three pull requests:

1. **Add a unit.** The first pull request installs Pipelines in your repository (the GitHub Actions workflows, the Pipelines configuration, a `.mise.toml`, and a credential-less environment) and adds the tutorial unit. Pipelines runs `plan` automatically and posts the result as a comment. Merge it and Pipelines runs `apply`.
2. **Modify a unit.** The second pull request changes an input value on the tutorial unit. Pipelines detects the change from the Git diff, plans it, and applies it on merge.
3. **Destroy a unit.** The third pull request removes the tutorial unit. Pipelines runs `plan -destroy` to show what will be removed, then destroys the resource on merge.

Because this wizard runs without cloud credentials, the tutorial unit uses local state and the environment it lands in has an empty `authentication {}` block. The real cloud authentication gets added in the next wizard.

<Aside type="note">
This wizard is optional. If you'd rather connect your cloud right away, you can skip it. The "Connect your cloud" wizard installs Pipelines for you if the tutorial hasn't already done so.
</Aside>

## Connect your cloud

Right now, the only supported cloud provider for the onboarding wizard is AWS. Pipelines has support for Azure and GCP (and custom self-configured authentication for anything else), but the wizard only supports AWS at the time of writing.

<img src={selectCloud.src} alt="Select your cloud provider step in the onboarding wizard" />

The wizard generates a script that you run in [AWS CloudShell](https://docs.aws.amazon.com/cloudshell/latest/userguide/welcome.html) against the AWS account you want to use. This script provisions an [OpenID Connect (OIDC) provider](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html), the IAM roles that GitHub Actions will assume when running plans and applies, and an S3 bucket for your OpenTofu/Terraform state. If you're unsure about running it, you can read the full source to learn what it does. You can even use the convenient "Analyze script with AI?" option to help you understand the different parts.

<img src={bootstrapScript.src} alt="AWS bootstrap script step in the onboarding wizard" />

<Aside type="tip">
The Free Tier is self-serve. If you're on a paid Terragrunt Scale plan, Gruntwork support can help walk you through this bootstrap step and tailor it to your account setup.
</Aside>

We'll look at what OIDC does and why it matters later in this section.

Once the script finishes, the wizard detects completion and opens a pull request that imports those IAM resources into your infrastructure code and configures Pipelines to authenticate with them. This pull request adds the S3 backend configuration, the bootstrap stack, and a real environment with an `aws_oidc` block in place of the empty `authentication {}` from the previous wizard. You can optionally include a sample `s3-bucket` unit in this pull request to verify the connection by deploying a real resource.

Review and merge that pull request, and your repository is ready to deploy real infrastructure through Pipelines.

</details>

## Understanding what onboarding set up

Onboarding did a lot of work automatically across those three wizards. Before we start using the pipeline, it's worth slowing down a second and exploring what's been assembled.

### Repository structure

[Clone the repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) that onboarding configured and take a look at the directory tree.

<FileTree>
- .github
  - workflows
    - pipelines-unlock.yml
    - pipelines.yml
- .gitignore
- .gruntwork
  - environment-account-name.hcl
  - repository.hcl
- .mise.toml
- account-name
  - _global
    - bootstrap
      - terragrunt.stack.hcl
    - region.hcl
  - account.hcl
- README.md
- root.hcl
</FileTree>

This is the end state after both pull requests merge. The "Run your first pipeline" wizard contributes the workflows, the Pipelines configuration, and `.mise.toml`. The "Connect your cloud" wizard contributes `root.hcl`, the account directory, the bootstrap stack, and the `aws_oidc` authentication.

If you've worked with Terragrunt before, this structure should look familiar. If you want to see the canonical version of this pattern, take a look at the [terragrunt-infrastructure-live-stacks-example](https://github.com/gruntwork-io/terragrunt-infrastructure-live-stacks-example) repository.

The key files:

- `.github/workflows` contains the GitHub Actions workflows that Terragrunt Scale configured.
- `.gruntwork` contains the configurations for Gruntwork products to understand how to properly interact with your environments (more on that later).
- `.mise.toml` defines the versions pinned for Terragrunt and OpenTofu using [mise](https://mise.jdx.dev/).
- Account directories (`account-name`) organize infrastructure by AWS account.
- `terragrunt.stack.hcl` files define stacks, which are collections of related units.
- `root.hcl` contains common configuration inherited by all units, including backend and provider settings.

### The GitHub Actions workflows

Open the `.github/workflows/` directory. The main file you want to focus on is `pipelines.yml`.

The [Pipelines](https://www.gruntwork.io/platform/pipelines) workflow is triggered both when a PR is opened/updated and when it is merged. When the PR is opened/updated, Pipelines will run a Terragrunt plan on the source branch (e.g. `tutorial/add-unit-1776108877709`), and when it is merged, Pipelines will run a Terragrunt apply/destroy (for added/changed or removed units/stacks respectively) on the deploy branch (e.g. `main`). Pipelines will minimize the blast radius of these applies/destroys by ensuring that runs will only be performed in the units/stacks that are impacted by changes in the pull request. Pipelines also runs Terragrunt intelligently such that the [Directed Acyclic Graph (DAG)](/getting-started/terminology/#directed-acyclic-graph-dag) is respected.

This is the plan-on-PR, apply-on-merge pattern described in the introduction. **The main branch is always the source of truth for what's deployed.**

### Gruntwork HCL configuration

Pipelines uses [HCL](https://docs.gruntwork.io/2.0/reference/pipelines/configurations-as-code/) to configure how it operates.

Onboarding installed two files in `.gruntwork` to configure Pipelines for you: `repository.hcl` and `environment-account-name.hcl`.

#### `repository.hcl`

```hcl
// repository.hcl

// Gruntwork Pipelines repository-wide configuration.
// Docs: https://docs.gruntwork.io/2.0/docs/pipelines/configuration/settings

repository {
  // Commits on this branch trigger `terragrunt apply`.
  // PRs against it trigger `terragrunt plan`.
  // If you change this, also update the branch trigger in your CI workflow file.
  deploy_branch_name = "main"
  env {
    PIPELINES_FEATURE_EXPERIMENT_IGNORE_UNITS_WITHOUT_ENVIRONMENT = "true"
  }
}
```

The `repository` block defines configurations that are global to the repository. These are configurations that should apply in all scenarios. The default configurations include:

1. The `deploy_branch_name`, which configures the branch that drives infrastructure deployment. Code updated in this branch results in apply/destroy runs.
2. The `env`, which configures environment variables that are always set for Pipelines and Terragrunt. In the default bootstrap generated by Pipelines, the opt-in `PIPELINES_FEATURE_EXPERIMENT_IGNORE_UNITS_WITHOUT_ENVIRONMENT` feature flag is set to ensure that any units without environments configured (more on that later) don't result in runs.

#### `environment-account-name.hcl`

```hcl
// environment-account-name.hcl

// Pipelines environment config for the account-name AWS account.
// Pipelines reads all .hcl files in .gruntwork/.
// Add a new file here to register a new environment.
// Docs: https://docs.gruntwork.io/2.0/docs/pipelines/configuration/settings

environment "account-name" {
  // Defines the environment as matching all units under account-name/.
  filter {
    paths = ["account-name/*"]
  }

  authentication {
    // Pipelines assumes these IAM roles via OIDC. No static credentials needed.
    // plan role: read-only, used on PRs. apply role: write, used on merge to deploy branch.
    // Both roles are created by the bootstrap stack in _global/bootstrap/.
    aws_oidc {
      account_id         = "123456789012"
      plan_iam_role_arn  = "arn:aws:iam::123456789012:role/pipelines-plan"
      apply_iam_role_arn = "arn:aws:iam::123456789012:role/pipelines-apply"
    }
  }
}
```

The `environment` block defines configurations for a particular set of units in your repository. You use the `filter` block to select those units by glob, and an `authentication` block to define how those units should be authenticated.

During the "Run your first pipeline" wizard, this `authentication` block was empty (`authentication {}`), which is why the tutorial unit planned and applied without any cloud credentials. When you connected your cloud, the wizard filled it in with the `aws_oidc` block above.

There are a couple [different ways to authenticate](https://docs.gruntwork.io/2.0/docs/pipelines/concepts/cloud-auth/) in Pipelines. The one that's been pre-configured here is the AWS OIDC authentication method. In this method, authentication takes place in an AWS account with the account ID `account_id`, and the `plan_iam_role_arn` IAM role is assumed on PR creation/update, whereas the `apply_iam_role_arn` IAM role is assumed on pushes to the deploy branch (e.g. `main`).

### OIDC authentication

When the bootstrap script ran, it created an [OIDC provider](https://docs.gruntwork.io/2.0/docs/pipelines/concepts/cloud-auth/#overview) and [IAM roles](https://docs.gruntwork.io/2.0/docs/pipelines/concepts/cloud-auth/aws?config-type=hcl#how-pipelines-knows-what-aws-principals-to-authenticate-as) in your AWS account. Here's [what's happening when a CI job runs](https://docs.gruntwork.io/2.0/docs/pipelines/concepts/cloud-auth/aws?config-type=hcl#publicly-available-cicd-platforms):

1. GitHub Actions requests a short-lived token from GitHub's OIDC provider.
2. That token is exchanged with AWS STS for temporary credentials by assuming the IAM role that the bootstrap step created.
3. The CI job uses those temporary credentials to run `terragrunt plan` or `terragrunt apply/destroy`.
4. The credentials expire after the job finishes.

At no point are long-lived AWS access keys stored in GitHub. There are no `AWS_ACCESS_KEY_ID` or `AWS_SECRET_ACCESS_KEY` values sitting in your repository secrets. Each CI run gets temporary credentials scoped to the permissions it needs (read-only on plan, and read-write on apply/destroy). If you want to learn more about how Terragrunt handles authentication in different contexts, read the [Authentication documentation](/features/units/authentication/).

Note that the roles provisioned in your AWS account had trust policies that only allowed role assumption when particular claims were made on the JWT token sent from GitHub Actions to AWS.

#### The plan role trust policy

This is what the trust policy will look like for the plan role:

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:<your-org>/<your-repo>:*"
                }
            }
        }
    ]
}
```

Note that it will allow any branch on the `<your-org>/<your-repo>` repository to assume it. This is necessary, given that this role is going to be assumed when generating a plan during pull request open/update. It's for this reason that the plan role is only given read-only permissions by default in the onboarding wizard. You want to ensure that IaC changes undergo review before they make any changes to live infrastructure.

#### The apply role trust policy

Note how this differs from the trust policy that is used in the apply role:

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "token.actions.githubusercontent.com:sub": "repo:<your-org>/<your-repo>:ref:refs/heads/main"
                }
            }
        }
    ]
}
```

The trust policy in this role *only* trusts the `main` branch of the `<your-org>/<your-repo>` repository. Branch protection on `main` becomes infrastructure protection: if a user can't push to `main` or bypass required checks, they can't apply changes to live infrastructure either.
