Repository: gruntwork-io/terragrunt

Issue:
Bucket versioning for logging bucket disabled
## Describe the bug

The remote_state->config bucket specified by "bucket" creates bucket with versioning enabled by default, but the bucket specified by "accesslogging_bucket_name" creates bucket with versioning disabled by default. 

## Steps To Reproduce

Execute terragrunt init using HCL provided.

```hcl
# ----------------------------------------------------------------------------------------------------------------
# LOAD COMMON VARIABLES
# ----------------------------------------------------------------------------------------------------------------
locals {
  # Automatically load common variables shared across all accounts
  common_vars = read_terragrunt_config(find_in_parent_folders("common.hcl"))

  # Automatically load account-level variables
  account_vars = read_terragrunt_config(find_in_parent_folders("account.hcl"))

  # Extract commonly used variables for easy acess
  name_tags     = local.common_vars.locals.name_tags
  account_name  = local.account_vars.locals.account_name
  aws_partition = local.account_vars.locals.aws_partition
  account_id    = local.common_vars.locals.account_ids[local.account_name]
  aws_region    = local.account_vars.locals.aws_region
  region_prefix = local.account_vars.locals.region_prefix

  bucket_name_prefix = join("-", [
    local.name_tags.divisions.ocio,
    substr(local.account_name, 0, 1),
    local.region_prefix,
    local.account_name
  ])

}

# ----------------------------------------------------------------------------------------------------------------
# GENERATED PROVIDER BLOCK
# ----------------------------------------------------------------------------------------------------------------
generate "provider" {
  path      = "provider.tf"
  if_exists = "skip" #"overwrite_terragrunt"
  contents  = <<EOF
provider "aws" {
  region = "${local.aws_region}"

  # Only these AWS Account IDs may be operated on by this template
  allowed_account_ids = ["${local.account_id}"]
}
EOF
}

# Use an override file to lock the provider version, regardless of if required_providers is defined in the modules.
generate "provider_version" {
  path      = "provider_version_override.tf"
  if_exists = "skip"
  contents  = <<EOF
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.80"
    }
  }
}
EOF
}

# ----------------------------------------------------------------------------------------------------------------
# GENERATED REMOTE STATE BLOCK
# ----------------------------------------------------------------------------------------------------------------
# Generate the Terraform remote state block for storing state in S3
remote_state {
  backend = "s3"
  disable_dependency_optimization = true
  config = {
    encrypt                   = true
    bucket                    = "test-tf-state"
    key                       = "${path_relative_to_include()}/terraform.tfstate"
    region                    = local.aws_region
    dynamodb_table            = "terraform-locks"
    accesslogging_bucket_name = "test-tf-logs"
  }
  generate = {
    path      = "backend.tf"
    if_exists = "overwrite_terragrunt"
  }
}
# ----------------------------------------------------------------------------------------------------------------
# DEFAULT INPUTS
# ----------------------------------------------------------------------------------------------------------------
inputs = {
  # Set globaly used inputs here to keep all the child terragrunt.hcl files more DRY.
  aws_account_id = local.account_id
  aws_region     = local.aws_region
  name_tags      = local.common_vars.locals.name_tags
}

#-----------------------------------------------------------------------------------------------------------------
# ALLOW .terraform-version FILE TO BE COPIED
#-----------------------------------------------------------------------------------------------------------------
terraform {
  include_in_copy = [".terraform-version"]
}

EOF
}

## Expected behavior

Expect the created buckets "test-tf-state" and "test-tf-logs" to both have versioning enabled.

## Nice to haves

- [ ] Terminal output
- [ ] Screenshots

## Versions

- Terragrunt version: v0.54.17 
- OpenTofu/Terraform version: v1.10.5 on linux_amd64 
- Environment details (Ubuntu 20.04, Windows 10, etc.): RHEL8

## Additional context

Add any other context about the problem here.


Hints from issue comments:
@kenroydhs: Here are some logs for the terragrunt init:

"test-tf-state" shows "Enabling versioning", but "test-tf-logs" does not.

`Remote state S3 bucket test-tf-state does not exist or you don't have permissions to access it. Would you like Terragrunt to create it? (y/n) y
DEBU[0024] Create S3 bucket with retry test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0024] Create S3 bucket test-tf-state with versioning, SSE encryption, and access logging.  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0024] Creating S3 bucket test-tf-state          prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0024] Created S3 bucket test-tf-state           prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0024] Waiting for bucket test-tf-state to be created  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0024] S3 bucket test-tf-state created.          prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0024] Enabling root access to S3 bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0025] Policy not exists for bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0025] Enabled root access to bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0025] Enabling enforced TLS access for S3 bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0025] Policy already exists for bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0025] Enabled enforced TLS access for bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0025] Blocking all public access to S3 bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0025] Blocked all public access to S3 bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0025] No tags specified for bucket test-tf-state.  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0025] Enabling versioning on S3 bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0025] Enabled versioning on S3 bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0025] Enabling bucket-wide SSE on AWS S3 bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0026] Enabled bucket-wide SSE on AWS S3 bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0026] Enabling bucket-wide Access Logging on AWS S3 bucket test-tf-state - using as TargetBucket test-tf-logs  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
Logs S3 bucket test-tf-logs for the remote state does not exist or you don't have permissions to access it. Would you like Terragrunt to create it? (y/n) y
DEBU[0059] Creating S3 bucket test-tf-logs           prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0059] Created S3 bucket test-tf-logs            prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0059] Blocking all public access to S3 bucket test-tf-logs  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0059] Blocked all public access to S3 bucket test-tf-logs  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0059] Granting WRITE and READ_ACP permissions to S3 Log Delivery (http://acs.amazonaws.com/groups/s3/LogDelivery) for bucket test-tf-logs. This is required for access logging.  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0059] Waiting for ACL bucket test-tf-logs to have the updated ACL for access logging.  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0059] Bucket test-tf-logs now has the proper ACL permissions for access logging!  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0059] Putting bucket logging on S3 bucket test-tf-state with TargetBucket test-tf-logs and TargetPrefix TFStateLogs/  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0059] Enabled bucket-wide Access Logging on AWS S3 bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0059] Enabling bucket-wide SSE on AWS S3 bucket test-tf-logs  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0060] Enabled bucket-wide SSE on AWS S3 bucket test-tf-logs  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0060] Enabling enforced TLS access for S3 bucket test-tf-logs  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0060] Policy not exists for bucket test-tf-logs  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0060] Enabled enforced TLS access for bucket test-tf-logs  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0060] No tags specified for bucket test-tf-logs.  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0060] Verifying AWS S3 Bucket Versioning test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0061] Checking if SSE is enabled for AWS S3 bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0061] Checking if bucket test-tf-state is have root access  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0061] Policy for RootAccess already exists for bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0061] Checking if bucket test-tf-state is enforced with TLS  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0061] Policy for EnforcedTLS already exists for bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0061] Checking if Access Logging is enabled for AWS S3 bucket test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0061] Checking if S3 bucket test-tf-state is configured to block public access  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0061] S3 bucket is already up to date               prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0061] Verifying AWS S3 Bucket Versioning test-tf-state  prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
DEBU[0061] Running command: terraform init               prefix=[/home/ec2-user/Desktop/iac/SCTASK0054329-TF-Buckets-Reconfig/tg/c-ai-sbx/us-east-1/networking/vpc] 
`@kenroydhs: If you look at the last two debug lines, it appears that terragrunt is verifying versioning on the wrong bucket ("test-tf-state" rather than "test-tf-logs").  Perhaps that is where the bug is?@yhakbar: Definitely worth addressing. We'll look to address it during our next tech debt cycle if the community doesn't address it first.

@kenroydhs , it seems like you've already done some root cause analysis. Are you interested in contributing a fix? If you're not comfortable with Golang, we're happy to get you ramped up for contribution. We're always looking for more help.@kenroydhs: Sure, I'd be more than happy to look at the code and provide a fix.@kenroydhs: I've looked through the code (remote_state_s3.go) and it looks like the calls to enable versioning and enable kms encryption and other stuff I need is only done for the state S3 bucket and not logging.  So I would have to rewrite parts of that file and perhaps even add additional configuration parameters.  Not sure when I will get to this.  Is anyone else working on that file at the same time?  

Solution patch:
diff --git a/docs-starlight/src/content/docs/01-getting-started/02-overview.mdx b/docs-starlight/src/content/docs/01-getting-started/02-overview.mdx
index 0ec04fab..574618f9 100644
--- a/docs-starlight/src/content/docs/01-getting-started/02-overview.mdx
+++ b/docs-starlight/src/content/docs/01-getting-started/02-overview.mdx
@@ -859,7 +859,7 @@ This repository is generally concerned with the configuration of reliably reprod
 
 What's on the default branch for this repository is generally considered the source of truth for the infrastructure you have provisioned. That default branch is generally the only version that matters when considering the state of your infrastructure.
 
-You can see an example of this in the [terragrunt-infrastructure-live-example](https://github.com/gruntwork-io/terragrunt-infrastructure-live-example) repository maintained by Gruntwork.
+You can see an example of this in the [terragrunt-infrastructure-live-stacks-example](https://github.com/gruntwork-io/terragrunt-infrastructure-live-stacks-example) repository maintained by Gruntwork.
 
 #### `infrastructure-modules`
 
@@ -871,7 +871,7 @@ You typically integrate this repository with tools like [Terratest](https://terr
 
 [Semantic Versioning](https://semver.org/) is widely used to manage communicating the impact of updates to this repository, and you typically pin the version of a consumed module in the `infrastructure-live` repository to a specific tag.
 
-You can see an example of this in the [terragrunt-infrastructure-modules-example](https://github.com/gruntwork-io/terragrunt-infrastructure-modules-example) repository maintained by Gruntwork.
+You can see an example of this in the [terragrunt-infrastructure-catalog-example](https://github.com/gruntwork-io/terragrunt-infrastructure-catalog-example) repository maintained by Gruntwork.
 
 ### Atomic Deployments
 
diff --git a/docs-starlight/src/content/docs/02-features/02-stacks.mdx b/docs-starlight/src/content/docs/02-features/02-stacks.mdx
index f5791eb4..ddbf3785 100644
--- a/docs-starlight/src/content/docs/02-features/02-stacks.mdx
+++ b/docs-starlight/src/content/docs/02-features/02-stacks.mdx
@@ -10,7 +10,7 @@ import { Aside, FileTree } from '@astrojs/starlight/components';
 
 ## Motivation
 
-Let’s say your infrastructure is defined across multiple OpenTofu/Terraform modules:
+Let's say your infrastructure is defined across multiple OpenTofu/Terraform root modules:
 
 <FileTree>
 
@@ -21,7 +21,7 @@ Let’s say your infrastructure is defined across multiple OpenTofu/Terraform mo
     - main.tf
   - mysql
     - main.tf
-  - redis
+  - valkey
     - main.tf
   - vpc
     - main.tf
@@ -30,7 +30,7 @@ Let’s say your infrastructure is defined across multiple OpenTofu/Terraform mo
 
 There is one unit to deploy a frontend-app, another to deploy a backend-app, another for the MySQL database, and so on.
 
-To deploy such an environment, you’d have to manually run `tofu`/`terraform` `apply` in each of module, wait for it to complete, and then run `tofu apply`/`terraform apply` in the next subfolder. Moreover, you have to make sure you manually run `tofu`/`terraform` `apply` in the _right_ module each time. The order in which they are applied can be important, especially if one module depends on another.
+To deploy such an environment, you'd have to manually run `tofu`/`terraform` `apply` in each root module, wait for it to complete, and then run `tofu apply`/`terraform apply` in the next root module. Moreover, you have to make sure you manually run `tofu`/`terraform` `apply` in the _right_ root module each time. The order in which they are applied can be important, especially if one root module depends on another.
 
 How do you avoid this tedious, error-prone and time-consuming process?
 
@@ -38,14 +38,50 @@ How do you avoid this tedious, error-prone and time-consuming process?
 
 Terragrunt provides special tooling for operating on sets of units at once. Sets of units in Terragrunt are called [stacks](/docs/getting-started/terminology/#stack).
 
-Right now, there is no special syntax for defining a stack as a single file, but that will change soon with the introduction of the [Stacks RFC](https://github.com/gruntwork-io/terragrunt/issues/3313).
+To work with stacks, you author [`terragrunt.stack.hcl` files](/docs/reference/hcl/#stacks) to define stacks, then use [Stack commands](/docs/reference/cli/commands/stack/generate) to generate and interact with those generated stacks.
 
-Regardless of whether you are using the new syntax or not, the core functionality provided by Stacks are the same.
-The new `terragrunt.stack.hcl` file is merely a shorthand for defining a stack, just like you could by placing units directly in a folder.
+For example, the configuration for a stack that defines the units above might look like this:
+
+```hcl
+# terragrunt.stack.hcl
+
+unit "backend_app" {
+  source = "git::git@github.com:acme/infrastructure-catalog.git//units/backend-app?ref=v0.0.1"
+  path   = "backend-app"
+}
+
+unit "frontend_app" {
+  source = "git::git@github.com:acme/infrastructure-catalog.git//units/frontend-app?ref=v0.0.1"
+  path   = "frontend-app"
+}
+
+unit "mysql" {
+  source = "git::git@github.com:acme/infrastructure-catalog.git//units/mysql?ref=v0.0.1"
+  path   = "mysql"
+}
+
+unit "valkey" {
+  source = "git::git@github.com:acme/infrastructure-catalog.git//units/valkey?ref=v0.0.1"
+  path   = "valkey"
+}
+
+unit "vpc" {
+  source = "git::git@github.com:acme/infrastructure-catalog.git//units/vpc?ref=v0.0.1"
+  path   = "vpc"
+}
+```
+
+And use commands like `stack run apply` to deploy the stack.
+
+```bash
+terragrunt stack run apply
+```
+
+Using `terragrunt.stack.hcl` files to define infrastructure is a bit more advanced than defining units directly in a repository, so learning how to work with stacks without using `terragrunt.stack.hcl` files will help you understand how to author effective `terragrunt.stack.hcl` files as well. At the end of the day, the core functionality of a stack is the same whether you are using `terragrunt.stack.hcl` files or not, they merely provide a more concise way to define a stack, and generate units on demand.
 
 ## The `run --all` command
 
-To solve the problem above, first convert the OpenTofu/Terraform modules into units. This is done simply by adding an empty `terragrunt.hcl` file within each root module folder.
+To make it possible to concurrently deploy all these OpenTofu/Terraform root modules concurrently with as little change to your existing infrastructure as possible, first convert the OpenTofu/Terraform root modules into units. This is done simply by adding an empty `terragrunt.hcl` file within each root module folder.
 
 <FileTree>
 
@@ -59,7 +95,7 @@ To solve the problem above, first convert the OpenTofu/Terraform modules into un
   - mysql
     - main.tf
     - terragrunt.hcl
-  - redis
+  - valkey
     - main.tf
     - terragrunt.hcl
   - vpc
@@ -73,7 +109,6 @@ Because you've created a directory of units, you've also implicitly created a st
 Now, you can go into the `root` directory and deploy all the units within it by using the `run --all` command with `apply`:
 
 ```bash
-cd root
 terragrunt run --all apply
 ```
 
@@ -82,32 +117,29 @@ When you run this command, Terragrunt will recursively discover all the units un
 Similarly, to undeploy all the OpenTofu/Terraform units, you can use the `run --all` command with `destroy`:
 
 ```bash
-cd root
 terragrunt run --all destroy
 ```
 
-To see the currently applied outputs of all of the subfolders, you can use the `run --all` command with `output`:
+To see the currently applied outputs of all of the units, you can use the `run --all` command with `output`:
 
 ```bash
-cd root
 terragrunt run --all output
 ```
 
 Finally, if you make some changes to your project, you could evaluate the impact by using `run --all` command with `plan`:
 
 Note: It is important to realize that you could get errors running `run --all plan` if you have dependencies between your
-projects and some of those dependencies haven’t been applied yet.
+projects and some of those dependencies haven't been applied yet.
 
-_Ex: If unit A depends on unit B and unit B hasn’t been applied yet, then `run --all plan` will show the plan for B, but exit with an error when trying to show the plan for A._
+_Ex: If unit A depends on unit B and unit B hasn't been applied yet, then `run --all plan` will show the plan for B, but exit with an error when trying to show the plan for A._
 
 ```bash
-cd root
 terragrunt run --all plan
 ```
 
 \* Note that the units _might_ run concurrently, but some units can be blocked from running until their dependencies are run.
 
-If your units have dependencies between them, for example, you can’t deploy the backend-app until MySQL and valkey are deployed. You’ll need to express those dependencies in your Terragrunt configuration as explained in the next section.
+If your units have dependencies between them, for example, you can't deploy the backend-app until MySQL and valkey are deployed. You'll need to express those dependencies in your Terragrunt configuration as explained in the next section.
 
 <Aside type="danger">
 If your units have dependencies between them, and you run a `terragrunt run --all destroy` command, Terragrunt will destroy all the units under the current working directory, _as well as each of the unit dependencies_ (that is, units you depend on via `dependencies` and `dependency` blocks)!
@@ -117,13 +149,24 @@ In the above example- this would mean that destroying the backend-app would also
 If you wish to prevent external dependencies from being destroyed, add the [`--queue-exclude-external` flag](/docs/reference/cli/commands/run#queue-exclude-external), or use the [`--exclude-dir` flag](/docs/reference/cli/commands/run#exclude-dir) once for each directory you wish to exclude.
 </Aside>
 
-## Cutting down the file count
+## The `stack run` command
+
+The `stack run` command is the equivalent of the `run --all` command for a single stack defined using a `terragrunt.stack.hcl` file.
 
-A simple way to reduce the file count from adding `terragrunt.hcl` files above is to move to [remote OpenTofu/Terraform modules](/docs/features/units/#remote-opentofuterraform-modules).
+When you run `stack run`, under the hood, Terragrunt will simply:
 
-This is a best practice, especially in larger projects, as it allows you to pin specific versions of your modules and use that specific version across multiple units.
+1. Recursively generate all stacks that are defined via the `stack` blocks of the `terragrunt.stack.hcl` file in the current directory (and all the stacks they generate).
+2. Generate all units that are defined via the `unit` blocks all the `terragrunt.stack.hcl` files in the current directory (and all the stacks generated in step 1).
+3. Perform a `run --all` in the current directory.
 
-The rest of this documentation will assume you are following that best practice.
+As such, the following is functionally equivalent to running `stack run <a-command>`:
+
+```bash
+terragrunt stack generate
+terragrunt run --all <a-command>
+```
+
+This manually generates all the relevant units, using the `stack generate` command, then explicitly running `run --all <a-command>` on those units.
 
 ## Passing outputs between units
 
@@ -136,7 +179,7 @@ Consider the following file structure:
     - terragrunt.hcl
   - mysql
     - terragrunt.hcl
-  - redis
+  - valkey
     - terragrunt.hcl
   - vpc
     - terragrunt.hcl
@@ -145,9 +188,9 @@ Consider the following file structure:
 
 Suppose that you wanted to pass in the VPC ID of the VPC that is created from the `vpc` unit in the folder structure above to the `mysql` unit as an input variable. Or that you wanted to pass in the subnet IDs of the private subnet that is allocated as part of the `vpc` unit.
 
-You can use the `dependency` block to extract those outputs and use them as `inputs` to another unit.
+You can use the `dependency` block to extract those outputs and use them as `inputs` to the `mysql` unit.
 
-For example, suppose the `vpc` unit outputs the ID under the name `vpc_id`. To access that output, you would specify in `mysql/terragrunt.hcl`:
+For example, suppose the `vpc` unit outputs the ID under the output named `vpc_id`. To access that output, you would specify in `mysql/terragrunt.hcl`:
 
 ```hcl
 # mysql/terragrunt.hcl
@@ -164,7 +207,7 @@ When you apply this unit, the output will be read from the `vpc` unit and passed
 
 You can also specify multiple `dependency` blocks to access the outputs of multiple units.
 
-For example, in the above folder structure, you might want to reference the `domain` output of the `redis` and `mysql` units for use as `inputs` in the `backend-app` unit. To access those outputs, you would specify the following in `backend-app/terragrunt.hcl`:
+For example, in the above folder structure, you might want to reference the `domain` output of the `valkey` and `mysql` units for use as `inputs` in the `backend-app` unit. To access those outputs, you would specify the following in `backend-app/terragrunt.hcl`:
 
 ```hcl
 # backend-app/terragrunt.hcl
@@ -172,13 +215,13 @@ dependency "mysql" {
   config_path = "../mysql"
 }
 
-dependency "redis" {
-  config_path = "../redis"
+dependency "valkey" {
+  config_path = "../valkey"
 }
 
 inputs = {
   mysql_url = dependency.mysql.outputs.domain
-  redis_url = dependency.redis.outputs.domain
+  valkey_url = dependency.valkey.outputs.domain
 }
 ```
 
@@ -186,7 +229,7 @@ Note that each `dependency` block results in a relevant status in the Terragrunt
 
 1. Deploy the VPC
 
-2. Deploy MySQL and Redis in parallel
+2. Deploy MySQL and valkey in parallel
 
 3. Deploy the backend-app
 
@@ -198,11 +241,11 @@ If any of the units failed to deploy, then Terragrunt will not attempt to deploy
 
 Terragrunt will return an error if the unit referenced in a `dependency` block has not been applied yet. This is because you cannot actually fetch outputs out of an unapplied unit, even if there are no resources being created in the unit.
 
-This is most problematic when running commands that do not modify state (e.g `run --all plan` and `run --all validate`) on a completely new setup where no infrastructure has been deployed. You won’t be able to `plan` or `validate` a unit if you can’t determine the `inputs`. If the unit depends on the outputs of another unit that hasn’t been applied yet, you won’t be able to compute the `inputs` unless the dependencies are all applied.
+This is most problematic when running commands that do not modify state (e.g `run --all plan` and `run --all validate`) on a completely new setup where no infrastructure has been deployed. You won't be able to `plan` or `validate` a unit if you can't determine the `inputs`. If the unit depends on the outputs of another unit that hasn't been applied yet, you won't be able to compute the `inputs` unless the dependencies are all applied.
 
 Of course, in real life usage, you typically need the ability to run `run --all validate` or `run --all plan` on a completely new set of infrastructure.
 
-To address this, you can provide mock outputs to use when a unit hasn’t been applied yet. This is configured using the `mock_outputs` attribute on the `dependency` block and it corresponds to a map that will be injected in place of the actual dependency outputs if the target config hasn’t been applied yet.
+To address this, you can provide mock outputs to use when a unit hasn't been applied yet. This is configured using the `mock_outputs` attribute on the `dependency` block and it corresponds to a map that will be injected in place of the actual dependency outputs if the target config hasn't been applied yet.
 
 Using a mock output is typically the best solution here, as you typically don't actually care that an _accurate_ value is used for a given value at this stage, just that it will plan successfully. When you actually apply the unit, that's when you want to be sure that a real value is used.
 
@@ -227,7 +270,7 @@ inputs = {
 
 You can now run `validate` on this config before the `vpc` unit is applied because Terragrunt will use the map `{vpc_id = "mock-vpc-id"}` as the `outputs` attribute on the dependency instead of erroring out.
 
-What if you wanted to restrict this behavior to only the `validate` command? For example, you might not want to use the defaults for a `plan` operation because the plan doesn’t give you any indication of what is actually going to be created.
+What if you wanted to restrict this behavior to only the `validate` command? For example, you might not want to use the defaults for a `plan` operation because the plan doesn't give you any indication of what is actually going to be created.
 
 You can use the `mock_outputs_allowed_terraform_commands` attribute to indicate that the `mock_outputs` should only be used when running those OpenTofu/Terraform commands. So to restrict the `mock_outputs` to only when `validate` is being run, you can modify the above `terragrunt.hcl` file to:
 
@@ -296,9 +339,36 @@ dependency "vpc" {
 
 If real outputs only contain `vpc_id`, `dependency.outputs` will contain a real value for `vpc_id` and a mocked value for `new_output`.
 
+## Stack outputs
+
+When defining a stack using a `terragrunt.stack.hcl` file, you also have the ability to interact with the aggregated outputs of all the units in the stack.
+
+To do this, use the [`stack output`](/docs/reference/cli/commands/stack/output) command (not the [`stack run output`](/docs/reference/cli/commands/stack/run) command).
+
+```bash
+$ terragrunt stack output
+backend_app = {
+  domain = "backend-app.example.com"
+}
+frontend_app = {
+  domain = "frontend-app.example.com"
+}
+mysql = {
+  endpoint = "terraform-20250504140737772400000001.abcdefghijkl.us-east-1.rds.amazonaws.com"
+}
+valkey = {
+  endpoint = "serverless-valkey-01.amazonaws.com"
+}
+vpc = {
+  vpc_id = "vpc-1234567890"
+}
+```
+
+This will return a single aggregated value for all the outputs of all the units in the stack.
+
 ## Dependencies between units
 
-You can also specify dependencies explicitly. Consider the following file structure:
+You can also specify dependencies without accessing any of the outputs of units. Consider the following file structure:
 
 <FileTree>
 
@@ -309,22 +379,22 @@ You can also specify dependencies explicitly. Consider the following file struct
     - terragrunt.hcl
   - mysql
     - terragrunt.hcl
-  - redis
+  - valkey
     - terragrunt.hcl
   - vpc
     - terragrunt.hcl
 
 </FileTree>
 
-Let’s assume you have the following dependencies between OpenTofu/Terraform units:
+Let's assume you have the following dependencies between OpenTofu/Terraform units:
 
-- `backend-app` depends on `mysql`, `redis`, and `vpc`
+- `backend-app` depends on `mysql`, `valkey`, and `vpc`
 
 - `frontend-app` depends on `backend-app` and `vpc`
 
 - `mysql` depends on `vpc`
 
-- `redis` depends on `vpc`
+- `valkey` depends on `vpc`
 
 - `vpc` has no dependencies
 
@@ -333,7 +403,7 @@ You can express these dependencies in your `terragrunt.hcl` config files using a
 ``` hcl
 # backend-app/terragrunt.hcl
 dependencies {
-  paths = ["../vpc", "../mysql", "../redis"]
+  paths = ["../vpc", "../mysql", "../valkey"]
 }
 ```
 
@@ -346,13 +416,13 @@ dependencies {
 }
 ```
 
-Once you’ve specified these dependencies in each `terragrunt.hcl` file, Terragrunt will be able to perform updates respecting the [DAG](/docs/getting-started/terminology/#directed-acyclic-graph-dag) of dependencies.
+Once you've specified these dependencies in each `terragrunt.hcl` file, Terragrunt will be able to perform updates respecting the [DAG](/docs/getting-started/terminology/#directed-acyclic-graph-dag) of dependencies.
 
 For the example at the start of this section, the order of runs for the `run --all apply` command would be:
 
 1. Deploy the VPC
 
-2. Deploy MySQL and Redis in parallel
+2. Deploy MySQL and valkey in parallel
 
 3. Deploy the backend-app
 
@@ -372,17 +442,17 @@ For example, if `destroy` was called on the `Valkey` unit, you'd be asked for co
 
 ## Visualizing the DAG
 
-To visualize the dependency graph you can use the `graph-dependencies` command (similar to the `terraform graph` command).
+To visualize the dependency graph you can use the `dag graph` command (similar to the `terraform graph` command).
 
 The graph is output in DOT format. The typical program used to render this file format is GraphViz, but many web services are available that can do this as well.
 
 ```bash
-terragrunt graph-dependencies | dot -Tsvg > graph.svg
+terragrunt dag graph | dot -Tsvg > graph.svg
 ```
 
 The example above generates the following graph:
 
-![terragrunt graph-dependencies](../../../assets/img/collections/documentation/graph.png)
+![terragrunt dag graph](../../../assets/img/collections/documentation/graph.png)
 
 Note that this graph shows the dependency relationship in the direction of the arrow, with the tip pointing to the dependency (e.g. `frontend-app` depends on `backend-app`).
 
@@ -392,10 +462,9 @@ The exception to this rule is during the `destroy` (and `plan -destroy`) command
 
 ## Testing multiple units locally
 
-If you are using Terragrunt to download [remote OpenTofu/Terraform modules](/docs/features/units#remote-opentofuterraform-modules) and all of your units have the `source` parameter set to a Git URL, but you want to test with a local checkout of the code, you can use the `--source` parameter to override that value:
+If you are using Terragrunt to download [remote OpenTofu/Terraform modules](/docs/features/units/#remote-opentofuterraform-modules) and all of your units have the `source` parameter set to a Git URL, but you want to test with a local checkout of the code, you can use the `--source` parameter to override that value:
 
 ```bash
-cd root
 terragrunt run --all plan --source /source/modules
 ```
 
@@ -403,7 +472,7 @@ If you set the `--source` parameter, the `run --all` command will assume that pa
 
 For each unit that is being processed via a `run --all` command, Terragrunt will:
 
-1. Read in the `source` parameter in that unit’s `terragrunt.hcl` file.
+1. Read in the `source` parameter in that unit's `terragrunt.hcl` file.
 2. Parse out the path (the portion after the double-slash).
 3. Append the path to the `--source` parameter to create the final local path for that unit.
 
@@ -568,3 +637,8 @@ Generally speaking, this is the primary tool Terragrunt users use to control the
 In addition to using your working directory to control what's included in a [run queue](/docs/getting-started/terminology/#run-queue), you can also use flags like [--include-dir](/docs/reference/cli/commands/run#include-dir) and [--exclude-dir](/docs/reference/cli/commands/run#exclude-dir) to explicitly control what's included in a run queue within a stack, or outside of it.
 
 There are more flags that control the behavior of the `run` command, which you can find in the [`run` docs](/docs/reference/cli/commands/run).
+
+## Learning more
+
+If you'd like more advanced examples on stacks, check out the [terragrunt-infrastructure-catalog-example repository](https://github.com/gruntwork-io/terragrunt-infrastructure-catalog-example/tree/main/examples/terragrunt/stacks). These have full-featured examples of stacks that deploy real, stateful infrastructure in an AWS account.
+
diff --git a/docs/_docs/01_getting-started/02-overview.md b/docs/_docs/01_getting-started/02-overview.md
index 1de367bd..55a2b112 100644
--- a/docs/_docs/01_getting-started/02-overview.md
+++ b/docs/_docs/01_getting-started/02-overview.md
@@ -853,7 +853,7 @@ This repository is generally concerned with the configuration of reliably reprod
 
 What's on the default branch for this repository is generally considered the source of truth for the infrastructure you have provisioned. That default branch is generally the only version that matters when considering the state of your infrastructure.
 
-You can see an example of this in the [terragrunt-infrastructure-live-example](https://github.com/gruntwork-io/terragrunt-infrastructure-live-example) repository maintained by Gruntwork.
+You can see an example of this in the [terragrunt-infrastructure-live-stacks-example](https://github.com/gruntwork-io/terragrunt-infrastructure-live-stacks-example) repository maintained by Gruntwork.
 
 #### `infrastructure-modules`
 
@@ -865,7 +865,7 @@ You typically integrate this repository with tools like [Terratest](https://terr
 
 [Semantic Versioning](https://semver.org/) is widely used to manage communicating the impact of updates to this repository, and you typically pin the version of a consumed module in the `infrastructure-live` repository to a specific tag.
 
-You can see an example of this in the [terragrunt-infrastructure-modules-example](https://github.com/gruntwork-io/terragrunt-infrastructure-modules-example) repository maintained by Gruntwork.
+You can see an example of this in the [terragrunt-infrastructure-catalog-example](https://github.com/gruntwork-io/terragrunt-infrastructure-catalog-example) repository maintained by Gruntwork.
 
 ### Atomic Deployments
 
diff --git a/docs/_docs/02_features/02-stacks.md b/docs/_docs/02_features/02-stacks.md
index e4d6d282..cc95b55c 100644
--- a/docs/_docs/02_features/02-stacks.md
+++ b/docs/_docs/02_features/02-stacks.md
@@ -14,20 +14,23 @@ slug: stacks
 ---
 
 - [Motivation](#motivation)
-- [Stacks to the rescue!](#stacks-to-the-rescue)
+- [Stacks to the rescue](#stacks-to-the-rescue)
 - [The `run --all` command](#the-run---all-command)
+- [The `stack run` command](#the-stack-run-command)
 - [Passing outputs between units](#passing-outputs-between-units)
   - [Unapplied dependency and mock outputs](#unapplied-dependency-and-mock-outputs)
+- [Stack outputs](#stack-outputs)
 - [Dependencies between units](#dependencies-between-units)
 - [Visualizing the DAG](#visualizing-the-dag)
 - [Testing multiple units locally](#testing-multiple-units-locally)
 - [Limiting run parallelism](#limiting-run-parallelism)
 - [Saving OpenTofu/Terraform plan output](#saving-opentofuterraform-plan-output)
 - [Nested Stacks](#nested-stacks)
+- [Learning more](#learning-more)
 
 ## Motivation
 
-Let’s say your infrastructure is defined across multiple OpenTofu/Terraform modules:
+Let’s say your infrastructure is defined across multiple OpenTofu/Terraform root modules:
 
 ```tree
 root
@@ -37,7 +40,7 @@ root
 │   └── main.tf
 ├── mysql
 │   └── main.tf
-├── redis
+├── valkey
 │   └── main.tf
 └── vpc
     └── main.tf
@@ -45,7 +48,7 @@ root
 
 There is one unit to deploy a frontend-app, another to deploy a backend-app, another for the MySQL database, and so on.
 
-To deploy such an environment, you’d have to manually run `tofu`/`terraform` `apply` in each of module, wait for it to complete, and then run `tofu apply`/`terraform apply` in the next subfolder. Moreover, you have to make sure you manually run `tofu`/`terraform` `apply` in the _right_ module each time. The order in which they are applied can be important, especially if one module depends on another.
+To deploy such an environment, you’d have to manually run `tofu`/`terraform` `apply` in each root module, wait for it to complete, and then run `tofu apply`/`terraform apply` in the next root module. Moreover, you have to make sure you manually run `tofu`/`terraform` `apply` in the _right_ root module each time. The order in which they are applied can be important, especially if one root module depends on another.
 
 How do you avoid this tedious, error-prone and time-consuming process?
 
@@ -53,14 +56,50 @@ How do you avoid this tedious, error-prone and time-consuming process?
 
 Terragrunt provides special tooling for operating on sets of units at once. Sets of units in Terragrunt are called [stacks](/docs/getting-started/terminology/#stack).
 
-Right now, there is no special syntax for defining a stack as a single file, but that will change soon with the introduction of the [Stacks RFC](https://github.com/gruntwork-io/terragrunt/issues/3313).
+To work with stacks, you author [`terragrunt.stack.hcl` files](/docs/reference/configuration/#stacks) to define stacks, then use [Stack commands](/docs/reference/cli-options/#stack-commands) to generate and interact with those generated stacks.
 
-Regardless of whether you are using the new syntax or not, the core functionality provided by Stacks are the same.
-The new `terragrunt.stack.hcl` file is merely a shorthand for defining a stack, just like you could by placing units directly in a folder.
+For example, the configuration for a stack that defines the units above might look like this:
+
+```hcl
+# terragrunt.stack.hcl
+
+unit "backend_app" {
+  source = "git::git@github.com:acme/infrastructure-catalog.git//units/backend-app?ref=v0.0.1"
+  path   = "backend-app"
+}
+
+unit "frontend_app" {
+  source = "git::git@github.com:acme/infrastructure-catalog.git//units/frontend-app?ref=v0.0.1"
+  path   = "frontend-app"
+}
+
+unit "mysql" {
+  source = "git::git@github.com:acme/infrastructure-catalog.git//units/mysql?ref=v0.0.1"
+  path   = "mysql"
+}
+
+unit "valkey" {
+  source = "git::git@github.com:acme/infrastructure-catalog.git//units/valkey?ref=v0.0.1"
+  path   = "valkey"
+}
+
+unit "vpc" {
+  source = "git::git@github.com:acme/infrastructure-catalog.git//units/vpc?ref=v0.0.1"
+  path   = "vpc"
+}
+```
+
+And use commands like `stack run apply` to deploy the stack.
+
+```bash
+terragrunt stack run apply
+```
+
+Using `terragrunt.stack.hcl` files to define infrastructure is a bit more advanced than defining units directly in a repository, so learning how to work with stacks without using `terragrunt.stack.hcl` files will help you understand how to author effective `terragrunt.stack.hcl` files as well. At the end of the day, the core functionality of a stack is the same whether you are using `terragrunt.stack.hcl` files or not, they merely provide a more concise way to define a stack, and generate units on demand.
 
 ## The `run --all` command
 
-To solve the problem above, first convert the OpenTofu/Terraform modules into units. This is done simply by adding an empty `terragrunt.hcl` file within each module folder.
+To make it possible to concurrently deploy all these OpenTofu/Terraform root modules concurrently with as little change to your existing infrastructure as possible, first convert the OpenTofu/Terraform root modules into units. This is done simply by adding an empty `terragrunt.hcl` file within each root module folder.
 
 ```tree
 root
@@ -73,7 +112,7 @@ root
 ├── mysql
 │   ├── main.tf
 │   └── terragrunt.hcl
-├── redis
+├── valkey
 │   ├── main.tf
 │   └── terragrunt.hcl
 └── vpc
@@ -86,7 +125,6 @@ Because you've created a directory of units, you've also implicitly created a st
 Now, you can go into the `root` folder and deploy all the units within it by using the `run --all` command with `apply`:
 
 ```bash
-cd root
 terragrunt run --all apply
 ```
 
@@ -95,14 +133,12 @@ When you run this command, Terragrunt will recursively discover all the units un
 Similarly, to undeploy all the OpenTofu/Terraform units, you can use the `run --all` command with `destroy`:
 
 ```bash
-cd root
 terragrunt run --all destroy
 ```
 
-To see the currently applied outputs of all of the subfolders, you can use the `run --all` command with `output`:
+To see the currently applied outputs of all of the units, you can use the `run --all` command with `output`:
 
 ```bash
-cd root
 terragrunt run --all output
 ```
 
@@ -114,16 +150,34 @@ projects and some of those dependencies haven’t been applied yet.
 _Ex: If unit A depends on unit B and unit B hasn’t been applied yet, then run --all plan will show the plan for B, but exit with an error when trying to show the plan for A._
 
 ```bash
-cd root
 terragrunt run --all plan
 ```
 
 \* Note that the units _might_ run concurrently, but some units can be blocked from running until their dependencies are run.
 
-If your units have dependencies between them, for example, you can’t deploy the backend-app until MySQL and redis are deployed. You’ll need to express those dependencies in your Terragrunt configuration as explained in the next section.
+If your units have dependencies between them, for example, you can’t deploy the backend-app until MySQL and valkey are deployed. You’ll need to express those dependencies in your Terragrunt configuration as explained in the next section.
 
 Additional note: If your units have dependencies between them, and you run a `terragrunt run --all destroy` command, Terragrunt will destroy all the units under the current working directory, _as well as each of the unit dependencies_ (that is, units you depend on via `dependencies` and `dependency` blocks)! If you wish to use exclude dependencies from being destroyed, add the `--queue-exclude-external` flag, or use the `--exclude-dir` once for each directory you wish to exclude.
 
+## The `stack run` command
+
+The `stack run` command is the equivalent of the `run --all` command for a single stack defined using a `terragrunt.stack.hcl` file.
+
+When you run `stack run`, under the hood, Terragrunt will simply:
+
+1. Recursively generate all stacks that are defined via the `stack` blocks of the `terragrunt.stack.hcl` file in the current directory (and all the stacks they generate).
+2. Generate all units that are defined via the `unit` blocks all the `terragrunt.stack.hcl` files in the current directory (and all the stacks generated in step 1).
+3. Perform a `run --all` in the current directory.
+
+As such, the following is functionally equivalent to running `stack run <a-command>`:
+
+```bash
+terragrunt stack generate
+terragrunt run --all <a-command>
+```
+
+This manually generates all the relevant units, using the `stack generate` command, then explicitly running `run --all <a-command>` on those units.
+
 ## Passing outputs between units
 
 Consider the following file structure:
@@ -136,7 +190,7 @@ root
 ├── mysql
 │   ├── main.tf
 │   └── terragrunt.hcl
-├── redis
+├── valkey
 │   ├── main.tf
 │   └── terragrunt.hcl
 └── vpc
@@ -146,9 +200,9 @@ root
 
 Suppose that you wanted to pass in the VPC ID of the VPC that is created from the `vpc` unit in the folder structure above to the `mysql` unit as an input variable. Or that you wanted to pass in the subnet IDs of the private subnet that is allocated as part of the `vpc` unit.
 
-You can use the `dependency` block to extract those outputs and use them as `inputs` to another unit.
+You can use the `dependency` block to extract those outputs and use them as `inputs` to the `mysql` unit.
 
-For example, suppose the `vpc` unit outputs the ID under the name `vpc_id`. To access that output, you would specify in `mysql/terragrunt.hcl`:
+For example, suppose the `vpc` unit outputs the ID under the output named `vpc_id`. To access that output, you would specify in `mysql/terragrunt.hcl`:
 
 ```hcl
 dependency "vpc" {
@@ -164,20 +218,20 @@ When you apply this unit, the output will be read from the `vpc` unit and passed
 
 You can also specify multiple `dependency` blocks to access the outputs of multiple units.
 
-For example, in the above folder structure, you might want to reference the `domain` output of the `redis` and `mysql` units for use as `inputs` in the `backend-app` unit. To access those outputs, you would specify the following in `backend-app/terragrunt.hcl`:
+For example, in the above folder structure, you might want to reference the `domain` output of the `valkey` and `mysql` units for use as `inputs` in the `backend-app` unit. To access those outputs, you would specify the following in `backend-app/terragrunt.hcl`:
 
 ```hcl
 dependency "mysql" {
   config_path = "../mysql"
 }
 
-dependency "redis" {
-  config_path = "../redis"
+dependency "valkey" {
+  config_path = "../valkey"
 }
 
 inputs = {
   mysql_url = dependency.mysql.outputs.domain
-  redis_url = dependency.redis.outputs.domain
+  valkey_url = dependency.valkey.outputs.domain
 }
 ```
 
@@ -185,7 +239,7 @@ Note that each `dependency` block results in a relevant status in the Terragrunt
 
 1. Deploy the VPC
 
-2. Deploy MySQL and Redis in parallel
+2. Deploy MySQL and valkey in parallel
 
 3. Deploy the backend-app
 
@@ -290,9 +344,36 @@ dependency "vpc" {
 
 If real outputs only contain `vpc_id`, `dependency.outputs` will contain a real value for `vpc_id` and a mocked value for `new_output`.
 
+## Stack outputs
+
+When defining a stack using a `terragrunt.stack.hcl` file, you also have the ability to interact with the aggregated outputs of all the units in the stack.
+
+To do this, use the [`stack output`](/docs/reference/cli-options/#stack-output) command (not the [`stack run output`](/docs/reference/cli-options/#stack-run) command).
+
+```bash
+$ terragrunt stack output
+backend_app = {
+  domain = "backend-app.example.com"
+}
+frontend_app = {
+  domain = "frontend-app.example.com"
+}
+mysql = {
+  endpoint = "terraform-20250504140737772400000001.abcdefghijkl.us-east-1.rds.amazonaws.com"
+}
+valkey = {
+  endpoint = "serverless-valkey-01.amazonaws.com"
+}
+vpc = {
+  vpc_id = "vpc-1234567890"
+}
+```
+
+This will return a single aggregated value for all the outputs of all the units in the stack.
+
 ## Dependencies between units
 
-You can also specify dependencies explicitly. Consider the following file structure:
+You can also specify dependencies without accessing any of the outputs of units. Consider the following file structure:
 
 ```tree
 root
@@ -305,7 +386,7 @@ root
 ├── mysql
 │   ├── main.tf
 │   └── terragrunt.hcl
-├── redis
+├── valkey
 │   ├── main.tf
 │   └── terragrunt.hcl
 └── vpc
@@ -315,13 +396,13 @@ root
 
 Let’s assume you have the following dependencies between OpenTofu/Terraform units:
 
-- `backend-app` depends on `mysql`, `redis`, and `vpc`
+- `backend-app` depends on `mysql`, `valkey`, and `vpc`
 
 - `frontend-app` depends on `backend-app` and `vpc`
 
 - `mysql` depends on `vpc`
 
-- `redis` depends on `vpc`
+- `valkey` depends on `vpc`
 
 - `vpc` has no dependencies
 
@@ -329,7 +410,7 @@ You can express these dependencies in your `terragrunt.hcl` config files using a
 
 ``` hcl
 dependencies {
-  paths = ["../vpc", "../mysql", "../redis"]
+  paths = ["../vpc", "../mysql", "../valkey"]
 }
 ```
 
@@ -347,7 +428,7 @@ For the example at the start of this section, the order of runs for the `run --a
 
 1. Deploy the VPC
 
-2. Deploy MySQL and Redis in parallel
+2. Deploy MySQL and valkey in parallel
 
 3. Deploy the backend-app
 
@@ -357,21 +438,21 @@ Any error encountered in an individual unit during a `run --all` command will pr
 
 To check all of your dependencies and validate the code in them, you can use the `run --all validate` command.
 
-**Note:** During `destroy` runs, Terragrunt will try to find all dependent units and show a confirmation prompt with a list of detected dependencies. This is because Terragrunt knows that once resources in a dependency is destroyed, any commands run on dependent units may fail. For example, if `destroy` was called on the `redis` unit, you'll be asked for confirmation, as the `backend-app` depends on `redis`. You can avoid the prompt by using `--non-interactive`.
+**Note:** During `destroy` runs, Terragrunt will try to find all dependent units and show a confirmation prompt with a list of detected dependencies. This is because Terragrunt knows that once resources in a dependency is destroyed, any commands run on dependent units may fail. For example, if `destroy` was called on the `valkey` unit, you'll be asked for confirmation, as the `backend-app` depends on `valkey`. You can avoid the prompt by using `--non-interactive`.
 
 ## Visualizing the DAG
 
-To visualize the dependency graph you can use the `graph-dependencies` command (similar to the `terraform graph` command).
+To visualize the dependency graph you can use the `dag graph` command (similar to the `terraform graph` command).
 
 The graph is output in DOT format. The typical program used to render this file format is GraphViz, but many web services are available that can do this as well.
 
 ```bash
-terragrunt graph-dependencies | dot -Tsvg > graph.svg
+terragrunt dag graph | dot -Tsvg > graph.svg
 ```
 
 The example above generates the following graph:
 
-![terragrunt graph-dependencies]({{site.baseurl}}/assets/img/collections/documentation/graph.png)
+![terragrunt dag graph]({{site.baseurl}}/assets/img/collections/documentation/graph.png)
 
 Note that this graph shows the dependency relationship in the direction of the arrow, with the tip pointing to the dependency (e.g. `frontend-app` depends on `backend-app`).
 
@@ -384,7 +465,6 @@ The exception to this rule is during the `destroy` (and `plan -destroy`) command
 If you are using Terragrunt to download [remote OpenTofu/Terraform modules]({{site.baseurl}}/docs/features/units/#remote-opentofuterraform-modules) and all of your units have the `source` parameter set to a Git URL, but you want to test with a local checkout of the code, you can use the `--source` parameter to override that value:
 
 ```bash
-cd root
 terragrunt run --all plan --source /source/modules
 ```
 
@@ -475,7 +555,7 @@ $ tree /tmp/json
         └── tfplan.json
 
 # combine binary and json plans
-$ terragrunt run --all plan --terragrunt-out-dir /tmp/all --terragrunt-json-out-dir /tmp/all
+$ terragrunt run --all plan --out-dir /tmp/all --json-out-dir /tmp/all
 $ tree /tmp/all
 /tmp/all
 ├── app1
@@ -539,3 +619,7 @@ Generally speaking, this is the primary tool Terragrunt users use to control the
 In addition to using your working directory to control what's included in a [run queue](/docs/getting-started/terminology/#runner-queue), you can also use flags like [--queue-include-dir](/docs/reference/cli-options/#queue-include-dir) and [--queue-exclude-dir](/docs/reference/cli-options/#queue-exclude-dir) to explicitly control what's included in a run queue within a stack, or outside of it.
 
 There are more flags that control the behavior of the `run --all` command, which you can find in the [CLI Options](/docs/reference/cli-options) section.
+
+## Learning more
+
+If you'd like more advanced examples on stacks, check out the [terragrunt-infrastructure-catalog-example repository](https://github.com/gruntwork-io/terragrunt-infrastructure-catalog-example/tree/main/examples/terragrunt/stacks). These have full-featured examples of stacks that deploy real, stateful infrastructure in an AWS account.



Test patch:
diff --git a/internal/remotestate/backend/s3/client.go b/internal/remotestate/backend/s3/client.go
index ea7ec622..7f7101bd 100644
--- a/internal/remotestate/backend/s3/client.go
+++ b/internal/remotestate/backend/s3/client.go
@@ -184,7 +184,7 @@ func (client *Client) UpdateS3BucketIfNecessary(ctx context.Context, bucketName
 	if bucketUpdatesRequired.Versioning {
 		if client.SkipBucketVersioning {
 			client.logger.Debugf("Versioning is disabled for the remote state S3 bucket %s using 'skip_bucket_versioning' config.", bucketName)
-		} else if err := client.EnableVersioningForS3Bucket(); err != nil {
+		} else if err := client.EnableVersioningForS3Bucket(bucketName); err != nil {
 			return err
 		}
 	}
@@ -295,6 +295,12 @@ func (client *Client) configureAccessLogBucket(ctx context.Context, opts *option
 		}
 	}
 
+	if client.SkipBucketVersioning {
+		client.logger.Debugf("Versioning is disabled for the remote state S3 bucket %s using 'skip_bucket_versioning' config.", client.AccessLoggingBucketName)
+	} else if err := client.EnableVersioningForS3Bucket(client.AccessLoggingBucketName); err != nil {
+		return err
+	}
+
 	return nil
 }
 
@@ -479,7 +485,7 @@ func (client *Client) CreateS3BucketWithVersioningSSEncryptionAndAccessLogging(c
 
 	if client.SkipBucketVersioning {
 		client.logger.Debugf("Versioning is disabled for the remote state S3 bucket %s using 'skip_bucket_versioning' config.", cfg.Bucket)
-	} else if err := client.EnableVersioningForS3Bucket(); err != nil {
+	} else if err := client.EnableVersioningForS3Bucket(cfg.Bucket); err != nil {
 		return err
 	}
 
@@ -939,21 +945,19 @@ func (client *Client) EnableEnforcedTLSAccesstoS3Bucket(bucket string) error {
 // Helper function to check if the enforced TLS policy is enabled for the bucket
 
 // EnableVersioningForS3Bucket enables versioning for the S3 bucket specified in the given config.
-func (client *Client) EnableVersioningForS3Bucket() error {
-	cfg := &client.RemoteStateConfigS3
-
-	client.logger.Debugf("Enabling versioning on S3 bucket %s", cfg.Bucket)
+func (client *Client) EnableVersioningForS3Bucket(bucketName string) error {
+	client.logger.Debugf("Enabling versioning on S3 bucket %s", bucketName)
 	input := s3.PutBucketVersioningInput{
-		Bucket:                  aws.String(cfg.Bucket),
+		Bucket:                  aws.String(bucketName),
 		VersioningConfiguration: &s3.VersioningConfiguration{Status: aws.String(s3.BucketVersioningStatusEnabled)},
 	}
 
 	_, err := client.PutBucketVersioning(&input)
 	if err != nil {
-		return errors.Errorf("error enabling versioning on S3 bucket %s: %w", cfg.Bucket, err)
+		return errors.Errorf("error enabling versioning on S3 bucket %s: %w", bucketName, err)
 	}
 
-	client.logger.Debugf("Enabled versioning on S3 bucket %s", cfg.Bucket)
+	client.logger.Debugf("Enabled versioning on S3 bucket %s", bucketName)
 
 	return nil
 }
diff --git a/test/fixtures/s3-backend/common.hcl b/test/fixtures/s3-backend/common.hcl
index 8782d9b1..d5ef7fbf 100644
--- a/test/fixtures/s3-backend/common.hcl
+++ b/test/fixtures/s3-backend/common.hcl
@@ -6,6 +6,10 @@ feature "enable_lock_table_ssencryption" {
   default = false
 }
 
+feature "access_logging_bucket" {
+  default = ""
+}
+
 feature "key_prefix" {
   default = ""
 }
@@ -22,7 +26,8 @@ remote_state {
     region         = "__FILL_IN_REGION__"
     dynamodb_table = "__FILL_IN_LOCK_TABLE_NAME__"
 
-    skip_bucket_versioning = feature.disable_versioning.value
+    skip_bucket_versioning         = feature.disable_versioning.value
     enable_lock_table_ssencryption = feature.enable_lock_table_ssencryption.value
+    accesslogging_bucket_name      = feature.access_logging_bucket.value
   }
 }
diff --git a/test/integration_aws_test.go b/test/integration_aws_test.go
index 106d5d7e..449faaea 100644
--- a/test/integration_aws_test.go
+++ b/test/integration_aws_test.go
@@ -80,7 +80,7 @@ func TestAwsBootstrapBackend(t *testing.T) {
 
 				require.NoError(t, err)
 
-				validateS3BucketExistsAndIsTagged(t, helpers.TerraformRemoteStateS3Region, s3BucketName, nil)
+				validateS3BucketExistsAndIsTaggedAndVersioning(t, helpers.TerraformRemoteStateS3Region, s3BucketName, true, nil)
 				validateDynamoDBTableExistsAndIsTaggedAndIsSSEncrypted(t, helpers.TerraformRemoteStateS3Region, dynamoDBName, nil, false)
 			},
 		},
@@ -92,7 +92,7 @@ func TestAwsBootstrapBackend(t *testing.T) {
 
 				require.NoError(t, err)
 
-				validateS3BucketExistsAndIsTagged(t, helpers.TerraformRemoteStateS3Region, s3BucketName, nil)
+				validateS3BucketExistsAndIsTaggedAndVersioning(t, helpers.TerraformRemoteStateS3Region, s3BucketName, true, nil)
 				validateDynamoDBTableExistsAndIsTaggedAndIsSSEncrypted(t, helpers.TerraformRemoteStateS3Region, dynamoDBName, nil, true)
 			},
 		},
@@ -104,7 +104,7 @@ func TestAwsBootstrapBackend(t *testing.T) {
 
 				require.NoError(t, err)
 
-				validateS3BucketExistsAndIsTagged(t, helpers.TerraformRemoteStateS3Region, s3BucketName, nil)
+				validateS3BucketExistsAndIsTaggedAndVersioning(t, helpers.TerraformRemoteStateS3Region, s3BucketName, true, nil)
 				validateDynamoDBTableExistsAndIsTaggedAndIsSSEncrypted(t, helpers.TerraformRemoteStateS3Region, dynamoDBName, nil, false)
 			},
 		},
@@ -161,7 +161,7 @@ func TestAwsBootstrapBackendLegacyBehavior(t *testing.T) {
 	_, stderr, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt run --all --non-interactive --log-level debug --working-dir "+rootPath+" apply")
 	require.NoError(t, err)
 
-	validateS3BucketExistsAndIsTagged(t, helpers.TerraformRemoteStateS3Region, s3BucketName, nil)
+	validateS3BucketExistsAndIsTaggedAndVersioning(t, helpers.TerraformRemoteStateS3Region, s3BucketName, true, nil)
 	validateDynamoDBTableExistsAndIsTaggedAndIsSSEncrypted(t, helpers.TerraformRemoteStateS3Region, dynamoDBName, nil, false)
 
 	assert.Contains(t, stderr, "Use the explicit `--backend-bootstrap` flag to automatically provision backend resources before they're needed.")
@@ -204,7 +204,7 @@ func TestAwsBootstrapBackendWithoutVersioning(t *testing.T) {
 	_, _, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt run --all --non-interactive --log-level debug --strict-control require-explicit-bootstrap --working-dir "+rootPath+" --feature disable_versioning=true --backend-bootstrap apply")
 	require.NoError(t, err)
 
-	validateS3BucketExistsAndIsTagged(t, helpers.TerraformRemoteStateS3Region, s3BucketName, nil)
+	validateS3BucketExistsAndIsTaggedAndVersioning(t, helpers.TerraformRemoteStateS3Region, s3BucketName, false, nil)
 	validateDynamoDBTableExistsAndIsTaggedAndIsSSEncrypted(t, helpers.TerraformRemoteStateS3Region, dynamoDBName, nil, false)
 
 	_, _, err = helpers.RunTerragruntCommandWithOutput(t, "terragrunt --non-interactive --log-level debug --working-dir "+rootPath+" --feature disable_versioning=true backend delete --all")
@@ -214,6 +214,36 @@ func TestAwsBootstrapBackendWithoutVersioning(t *testing.T) {
 	require.NoError(t, err)
 }
 
+func TestAwsBootstrapBackendWithAccessLogging(t *testing.T) {
+	t.Parallel()
+
+	helpers.CleanupTerraformFolder(t, testFixtureS3Backend)
+	tmpEnvPath := helpers.CopyEnvironment(t, testFixtureS3Backend)
+	rootPath := util.JoinPath(tmpEnvPath, testFixtureS3Backend)
+
+	testID := strings.ToLower(helpers.UniqueID())
+
+	s3BucketName := "terragrunt-test-bucket-" + testID
+	s3AccessLogsBucketName := "terragrunt-test-bucket-" + testID + "-access-logs"
+	dynamoDBName := "terragrunt-test-dynamodb-" + testID
+
+	defer func() {
+		deleteS3Bucket(t, s3BucketName, helpers.TerraformRemoteStateS3Region)
+		deleteS3Bucket(t, s3AccessLogsBucketName, helpers.TerraformRemoteStateS3Region)
+		cleanupTableForTest(t, dynamoDBName, helpers.TerraformRemoteStateS3Region)
+	}()
+
+	commonConfigPath := util.JoinPath(rootPath, "common.hcl")
+	helpers.CopyTerragruntConfigAndFillPlaceholders(t, commonConfigPath, commonConfigPath, s3BucketName, dynamoDBName, helpers.TerraformRemoteStateS3Region)
+
+	_, _, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt run --all --non-interactive --log-level debug --strict-control require-explicit-bootstrap --working-dir "+rootPath+" --feature access_logging_bucket="+s3AccessLogsBucketName+" --backend-bootstrap apply")
+	require.NoError(t, err)
+
+	validateS3BucketExistsAndIsTaggedAndVersioning(t, helpers.TerraformRemoteStateS3Region, s3BucketName, true, nil)
+	validateS3BucketExistsAndIsTaggedAndVersioning(t, helpers.TerraformRemoteStateS3Region, s3AccessLogsBucketName, true, nil)
+	validateDynamoDBTableExistsAndIsTaggedAndIsSSEncrypted(t, helpers.TerraformRemoteStateS3Region, dynamoDBName, nil, false)
+}
+
 func TestAwsMigrateBackendWithoutVersioning(t *testing.T) {
 	t.Parallel()
 
@@ -238,7 +268,7 @@ func TestAwsMigrateBackendWithoutVersioning(t *testing.T) {
 	_, _, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt run --non-interactive --log-level debug --strict-control require-explicit-bootstrap --working-dir "+unitPath+" --feature disable_versioning=true --backend-bootstrap apply -- -auto-approve")
 	require.NoError(t, err)
 
-	validateS3BucketExistsAndIsTagged(t, helpers.TerraformRemoteStateS3Region, s3BucketName, nil)
+	validateS3BucketExistsAndIsTaggedAndVersioning(t, helpers.TerraformRemoteStateS3Region, s3BucketName, false, nil)
 	validateDynamoDBTableExistsAndIsTaggedAndIsSSEncrypted(t, helpers.TerraformRemoteStateS3Region, dynamoDBName, nil, false)
 
 	_, _, err = helpers.RunTerragruntCommandWithOutput(t, "terragrunt --non-interactive --log-level debug --working-dir "+rootPath+" --feature disable_versioning=true backend migrate unit1 unit2")
@@ -471,7 +501,7 @@ func TestAwsWorksWithLocalTerraformVersion(t *testing.T) {
 	var expectedS3Tags = map[string]string{
 		"owner": "terragrunt integration test",
 		"name":  "Terraform state storage"}
-	validateS3BucketExistsAndIsTagged(t, helpers.TerraformRemoteStateS3Region, s3BucketName, expectedS3Tags)
+	validateS3BucketExistsAndIsTaggedAndVersioning(t, helpers.TerraformRemoteStateS3Region, s3BucketName, true, expectedS3Tags)
 
 	var expectedDynamoDBTableTags = map[string]string{
 		"owner": "terragrunt integration test",
@@ -1590,6 +1620,21 @@ func assertS3Tags(t *testing.T, expectedTags map[string]string, bucketName strin
 	assert.Equal(t, expectedTags, actualTags, "Did not find expected tags on s3 bucket.")
 }
 
+func assertS3BucketVersioning(t *testing.T, bucketName string, versioning bool, client *s3.S3) {
+	t.Helper()
+
+	res, err := client.GetBucketVersioning(&s3.GetBucketVersioningInput{Bucket: aws.String(bucketName)})
+	require.NoError(t, err)
+	require.NotNil(t, res)
+
+	if versioning {
+		require.NotNil(t, res.Status)
+		assert.Equal(t, *res.Status, s3.BucketVersioningStatusEnabled, "Versioning is not enabled for the remote state S3 bucket %s", bucketName)
+	} else {
+		require.Nil(t, res.Status)
+	}
+}
+
 // Check that the DynamoDB table of the given name and region exists. Terragrunt should create this table during the test.
 // Also check if table got tagged properly
 func validateDynamoDBTableExistsAndIsTaggedAndIsSSEncrypted(t *testing.T, awsRegion string, tableName string, expectedTags map[string]string, expectedSSEncrypted bool) {
@@ -1651,7 +1696,7 @@ func doesDynamoDBTableItemExist(t *testing.T, awsRegion string, tableName, key s
 
 // Check that the S3 Bucket of the given name and region exists. Terragrunt should create this bucket during the test.
 // Also check if bucket got tagged properly and that public access is disabled completely.
-func validateS3BucketExistsAndIsTagged(t *testing.T, awsRegion string, bucketName string, expectedTags map[string]string) {
+func validateS3BucketExistsAndIsTaggedAndVersioning(t *testing.T, awsRegion string, bucketName string, versioning bool, expectedTags map[string]string) {
 	t.Helper()
 
 	client := helpers.CreateS3ClientForTest(t, awsRegion)
@@ -1663,6 +1708,8 @@ func validateS3BucketExistsAndIsTagged(t *testing.T, awsRegion string, bucketNam
 		assertS3Tags(t, expectedTags, bucketName, client)
 	}
 
+	assertS3BucketVersioning(t, bucketName, versioning, client)
+
 	assertS3PublicAccessBlocks(t, client, bucketName)
 }