linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Knowledge base
  • Cheat sheet
  • Capstone
  • Interview prep
home/terraform/lessons/tf-beginner-10-locals-and-functions

lesson ── terraform-beginner ── ~12 мин ── 3 шагов

locals and functions: removing duplication in HCL

When several resources share the same prefix, a set of tags, or a computed value, copy-paste is a bad idea. HCL gives you two tools: locals for named values inside HCL, and built-in functions for transforming strings, numbers, and lists.

In this lesson you build a bucket name from a variable plus the current region, move shared tags into locals, and apply string functions to normalize values. This is the end of the beginner track. See tf-locals, tf-interpolation, tf-functions-string.

▶ интерактивный sandbox

Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.

запустить sandbox →

stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя

Шаги

  1. 01

    Move the prefix into locals

    Create main.tf:

    hcl
    variable "env" {
      type    = string
      default = "dev"
    }
    data "aws_region" "current" {}
    locals {
      name_prefix = "linuxlab-${var.env}-${data.aws_region.current.name}"
      common_tags = {
        Project   = "linuxlab-terraform-course"
        Env       = var.env
        ManagedBy = "terraform"
      }
    }
    resource "aws_s3_bucket" "logs" {
      bucket = "${local.name_prefix}-logs-${random_id.suffix.hex}"
      tags   = local.common_tags
    }
    resource "aws_s3_bucket" "data_store" {
      bucket = "${local.name_prefix}-data-${random_id.suffix.hex}"
      tags   = local.common_tags
    }
    resource "random_id" "suffix" {
      byte_length = 4
    }

    Notice:

    • local.name_prefix, assembled from a variable and a data source.
    • local.common_tags, defined once, used twice.
    • Change the prefix tomorrow and you change it in one place.
    bash
    cd /home/student/tf-locals
    terraform init -input=false
    terraform apply -auto-approve -input=false
    подсказка

    The prefix is `local.x` without the s: a common mistake. Declaration: `locals { ... }`, reference: `local.x`.

    ✓ Both buckets got the same set of tags through locals.

  2. 02

    Add specific tags with merge

    Right now both buckets have identical tags. Sometimes you need to add a specific tag on top of the shared ones, for example a Purpose that differs per bucket. The merge() function does that:

    Open main.tf and change the tags blocks:

    hcl
    resource "aws_s3_bucket" "logs" {
      bucket = "${local.name_prefix}-logs-${random_id.suffix.hex}"
      tags = merge(local.common_tags, {
        Purpose = "logs"
      })
    }
    resource "aws_s3_bucket" "data_store" {
      bucket = "${local.name_prefix}-data-${random_id.suffix.hex}"
      tags = merge(local.common_tags, {
        Purpose = "data"
      })
    }

    merge(map1, map2), merge two maps into one. On a key collision the second one wins. More detail in tf-functions-collection.

    Apply:

    bash
    terraform apply -auto-approve

    The plan shows a ~ change with the added Purpose tag on each bucket.

    подсказка

    merge: a built-in function, plain parentheses. Don't confuse it with {{merge}}: this is HCL, not Jinja.

    ✓ logs now has Purpose=logs, data_store has Purpose=data. The rest of the tags are shared.

  3. 03

    A final output built with functions

    Add a file outputs.tf:

    hcl
    output "buckets_summary" {
      value = format(
        "%s in region %s: logs=%s, data=%s",
        upper(var.env),
        data.aws_region.current.name,
        aws_s3_bucket.logs.bucket,
        aws_s3_bucket.data_store.bucket,
      )
    }
    output "common_tags" {
      value = local.common_tags
    }

    Run:

    bash
    terraform apply -auto-approve
    terraform output buckets_summary

    You get a string like:

    "DEV in region us-east-1: logs=linuxlab-dev-us-east-1-logs-abc12345, data=linuxlab-dev-us-east-1-data-abc12345"

    format(), this is sprintf. %s, a placeholder for a string. upper(), raise to uppercase. These functions are pure: no side effects, and they always return the same result for the same inputs.

    More detail in tf-functions-string.

    подсказка

    If apply complains about "inconsistent format": check that the number of %s matches the number of arguments after the spec.

    ✓ The output is built with format() from several sources: locals, data, and a variable.

    locals plus other resources: IAM, SQS, Lambda

    S3 is not the only place where locals are handy. The same common_tags and name_prefix fit nicely onto other AWS resources that LocalStack emulates:

    hcl
    # IAM role with shared tags
    resource "aws_iam_role" "events_handler" {
      name = "${local.name_prefix}-events-handler"
      tags = merge(local.common_tags, { Purpose = "events" })
      assume_role_policy = jsonencode({
        Version = "2012-10-17"
        Statement = [{
          Action    = "sts:AssumeRole"
          Effect    = "Allow"
          Principal = { Service = "lambda.amazonaws.com" }
        }]
      })
    }
    # SQS queue, shared prefix, shared tags, its own Purpose
    resource "aws_sqs_queue" "events" {
      name = "${local.name_prefix}-events"
      tags = merge(local.common_tags, { Purpose = "events-queue" })
    }

    After this the beginner track closes. Intermediate topics:

    • Modules, pull this group of resources into a reusable whole. Basic practice in any real project.
    • Remote state, move state into S3 for teamwork.
    • CI/CD, terraform plan in the PR, apply after review.
    • Testing, native terraform test or terratest.

    The same holds on OpenTofu: locals and built-in functions work identically, and the syntax is identical. See tf-opentofu-parity.

    • → locals in full
    • → String functions
    • → Collection functions
    • → OpenTofu parity

Что ты узнал

You moved the shared prefix and tags into locals, used the functions upper(), lower(), format() to normalize values, and assembled a bucket name from several parts. The HCL is now DRY: add a new resource and it picks up the shared tags automatically.

команды

  • terraform consolecheck the value of local.x before apply
  • terraform plansee how locals expand into real values

концепции

  • · locals: internal names, visible only inside HCL
  • · merge() to combine shared and specific tags
  • · Pure functions: no side effects, can be nested

← предыдущий

OIDC, an IAM role for CI without access keys

следующий →

preconditions, postconditions, and the check block

Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies