Terraform Local Values: How to Define and Use Locals

Central reusable values connected to consistently named and tagged cloud resource blocks

What You’ll Learn

In this lesson, you will learn how to define and use Terraform local values, commonly called locals. Locals let you calculate a value once and reuse it across multiple resources.

  • Define local values inside a locals block.
  • Reference locals with the local. prefix.
  • Reuse computed resource names and common tags.
  • Understand when locals are preferable to repeating expressions.

The Concept

A local value is a named expression that Terraform calculates within a module. You define locals in a locals block:

locals {
  environment_label = "${var.project_name}-${var.environment}"
}

After defining the local, reference it with local.environment_label. The local. prefix tells Terraform that the value comes from a local value rather than from a variable or resource.

Locals are useful when the same expression appears in multiple places. For example, several cloud resources might need:

  • A name based on the project and environment.
  • The same owner, project, and environment tags.
  • A shared setting derived from one or more variables.

Unlike input variables, locals are not values that a person supplies when running Terraform. They are calculated inside your configuration. A local can use variables, resource attributes, and other expressions.

Basic Example

This example creates two Amazon S3 buckets. Both bucket names and their tags use local values. The AWS provider is included so the configuration is complete; you would need AWS credentials before running terraform apply.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

variable "aws_region" {
  type    = string
  default = "us-east-1"
}

variable "project_name" {
  type    = string
  default = "billing-api"
}

variable "environment" {
  type    = string
  default = "staging"
}

locals {
  name_prefix = "${var.project_name}-${var.environment}"

  common_tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "Terraform"
  }

  logs_bucket_name   = "${local.name_prefix}-logs"
  backups_bucket_name = "${local.name_prefix}-backups"
}

resource "aws_s3_bucket" "logs" {
  bucket = local.logs_bucket_name
  tags   = local.common_tags
}

resource "aws_s3_bucket" "backups" {
  bucket = local.backups_bucket_name
  tags   = local.common_tags
}

output "logs_bucket_name" {
  value = local.logs_bucket_name
}

output "backups_bucket_name" {
  value = local.backups_bucket_name
}

Expected Output

With the default variable values, Terraform calculates these local values:

logs_bucket_name    = "billing-api-staging-logs"
backups_bucket_name = "billing-api-staging-backups"

common_tags:
  Project     = "billing-api"
  Environment = "staging"
  ManagedBy   = "Terraform"

These are the values Terraform uses for the bucket names, tags, and outputs. S3 bucket names must be globally unique, so you may need to add a unique suffix before applying this example in a real AWS account.

How the Code Works

A relationship diagram showing Terraform input variables flowing into computed local values. Project and environment variables feed a name prefix and shared tags; the name prefix produces separate logs and backups bucket names. Both bucket resources reference their computed local name and the shared tags, and outputs expose the calculated bucket names.
Terraform variables provide inputs, locals calculate reusable names and tags, and multiple resources reference those locals consistently.

Defining the locals

The locals block contains four local values:

locals {
  name_prefix = "${var.project_name}-${var.environment}"

  common_tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "Terraform"
  }

  logs_bucket_name    = "${local.name_prefix}-logs"
  backups_bucket_name = "${local.name_prefix}-backups"
}

name_prefix combines two input variables. With the default values, it becomes billing-api-staging.

common_tags is a map containing tags shared by both buckets. Defining the map once prevents the resource blocks from repeating the same tag entries.

The bucket name locals use another local value, local.name_prefix. Terraform allows one local value to refer to another local value.

Using locals in resources

Each resource uses the appropriate computed name and the shared tag map:

resource "aws_s3_bucket" "logs" {
  bucket = local.logs_bucket_name
  tags   = local.common_tags
}

resource "aws_s3_bucket" "backups" {
  bucket = local.backups_bucket_name
  tags   = local.common_tags
}

The resource labels, such as logs and backups, are Terraform’s internal names. The actual AWS bucket names come from the bucket arguments.

Using a local in an output

The output blocks also reference the locals directly. This is useful when you want to display a calculated value after Terraform applies the configuration.

Changing the project_name or environment variable automatically changes the values calculated from them. You do not need to edit every resource name manually.

Another Example

Locals can also organize names for different types of resources. This example creates a CloudWatch log group and an SNS topic. The resources use a local map for names and a separate local map for shared tags.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

variable "service_name" {
  type    = string
  default = "checkout"
}

variable "environment" {
  type    = string
  default = "production"
}

locals {
  resource_names = {
    logs   = "/services/${var.service_name}/${var.environment}"
    alerts = "${var.service_name}-${var.environment}-alerts"
  }

  service_tags = {
    Service     = var.service_name
    Environment = var.environment
    ManagedBy   = "Terraform"
  }
}

resource "aws_cloudwatch_log_group" "application" {
  name              = local.resource_names.logs
  retention_in_days = 30
  tags              = local.service_tags
}

resource "aws_sns_topic" "alerts" {
  name = local.resource_names.alerts
  tags = local.service_tags
}

output "log_group_name" {
  value = local.resource_names.logs
}

output "alerts_topic_arn" {
  value = aws_sns_topic.alerts.arn
}

This pattern is slightly different from the first example. Instead of creating one local for every name, it stores related names in the resource_names map. The log group uses local.resource_names.logs, while the SNS topic uses local.resource_names.alerts.

The topic ARN is not available until the resource is created, so the output uses aws_sns_topic.alerts.arn instead of a local. Locals are best for expressions you can calculate from known values; resource attributes should still be referenced from the resource that creates them.

Common Mistakes

Forgetting the local. prefix

A local named name_prefix must be referenced as local.name_prefix. Writing only name_prefix does not refer to the local.

Using a variable when the value should be calculated

Input variables are appropriate for values supplied from outside the module, such as an environment name or AWS region. A value derived from those inputs usually belongs in a local.

For example, the environment can be a variable, while a full resource name can be a local:

variable "environment" {
  type    = string
  default = "staging"
}

locals {
  application_name = "payments-${var.environment}"
}

Expecting locals to be configurable at runtime

Locals do not create input prompts and cannot be set directly with a -var option. If a user or deployment system needs to provide a value, define it as a variable first, then use a local to build a reusable expression from it.

Repeating tags in every resource

Repeated tag maps are easy to make inconsistent. One resource might accidentally use Production while another uses production. A shared local keeps common tags consistent across resources.

Try It Yourself

Starting with the basic example, change the default project name to inventory-api and the environment to development. Before applying the configuration, predict the two bucket names and the values in the shared tag map.

Then add an output that displays the shared environment value:

output "environment" {
  value = var.environment
}

Use terraform plan to review how Terraform uses the computed names and tags. You do not need to apply the configuration to practice reading the calculated values.

Challenge

Create a Terraform configuration for a small web application with these requirements:

  • Define variables named app_name and environment.
  • Create a local named resource_prefix by combining those variables.
  • Create a local named common_tags containing the application name, environment, and ManagedBy = "Terraform".
  • Create an S3 bucket for uploaded files named with the prefix and the suffix -uploads.
  • Create an S3 bucket for reports named with the prefix and the suffix -reports.
  • Apply the shared tags to both buckets.

Use the AWS provider and include outputs for both calculated bucket names.

Solution

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

variable "app_name" {
  type    = string
  default = "web-portal"
}

variable "environment" {
  type    = string
  default = "development"
}

locals {
  resource_prefix = "${var.app_name}-${var.environment}"

  common_tags = {
    Application = var.app_name
    Environment = var.environment
    ManagedBy   = "Terraform"
  }

  uploads_bucket_name = "${local.resource_prefix}-uploads"
  reports_bucket_name = "${local.resource_prefix}-reports"
}

resource "aws_s3_bucket" "uploads" {
  bucket = local.uploads_bucket_name
  tags   = local.common_tags
}

resource "aws_s3_bucket" "reports" {
  bucket = local.reports_bucket_name
  tags   = local.common_tags
}

output "uploads_bucket_name" {
  value = local.uploads_bucket_name
}

output "reports_bucket_name" {
  value = local.reports_bucket_name
}

The solution defines the two input variables first, then uses them to calculate resource_prefix. The two bucket-name locals build on that prefix, while common_tags is reused by both resources. This keeps the resource blocks short and ensures both buckets receive the same tags.

Key Takeaways

  • Define local values inside a locals block.
  • Reference a local with the local. prefix.
  • Use locals to calculate names from variables and other expressions.
  • Use shared map locals to apply consistent tags across resources.
  • Use variables for external inputs and locals for values calculated from those inputs.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top