locals vs variable — 違いと使い分けの完全ガイド

1. 概要

この記事では、以下の内容を解説します。

  • localsvariableの根本的な違い(「コード内で計算」vs「外部から受け取る」)
  • それぞれが適切なケースの判断基準と具体例
  • 実務でよく見るアンチパターンと正しい使い方
  • localsvariableを組み合わせた設計パターン

結論を先に述べます: variableは「Terraformの外部(CIやオペレーター)から変えたい値」に使い、localsは「コード内の計算や繰り返しを整理するため」に使います。この判断基準さえ押さえれば、迷うことはありません。


2. 一言で言うとどう違うか

比較項目variablelocals
誰が値を決めるか外部(tfvars・CIの-var)コード(自分)
外から変更できるか✅ できる❌ できない
式・計算を書けるか❌ 値のみ(式は不可)✅ 任意の式・計算結果
参照方法var.<名前>local.<名前>
ブロック名variablelocals
主な用途環境・設定値の切り替え繰り返しの式に名前をつける
デフォルト値defaultで設定可能常にコード内で定義
型制約typeで宣言可能型制約なし

3. variableを使うべきケース

variableTerraformの外部から値を渡したい場合に使います。

ケース1: 環境ごとに値を変えたい

# variable: 環境名は外部から渡す(dev/stg/prdで変わる)
variable "environment" {
  description = "デプロイ先の環境名(dev/stg/prd)"
  type        = string

  validation {
    condition     = contains(["dev", "stg", "prd"], var.environment)
    error_message = "environmentはdev/stg/prdのいずれかを指定してください。"
  }
}

variable "instance_type" {
  description = "EC2インスタンスタイプ"
  type        = string
  default     = "t3.micro"
}
# CIやオペレーターが外から渡す
terraform apply -var="environment=prd" -var="instance_type=t3.medium"

# またはtfvarsファイルで指定
# prd.tfvars
# environment = "prd"
# instance_type = "t3.medium"
terraform apply -var-file="prd.tfvars"

ケース2: モジュールに設定値を渡す

# modules/ec2/variables.tf
variable "project" {
  description = "プロジェクト名(リソース名のプレフィックスに使用)"
  type        = string
}

variable "vpc_id" {
  description = "リソースを配置するVPCのID"
  type        = string
}

variable "subnet_ids" {
  description = "EC2を配置するサブネットIDのリスト"
  type        = list(string)
}
# 呼び出し元(ルートモジュール)
module "ec2" {
  source = "./modules/ec2"

  project    = var.project  # 呼び出し元のvariableを渡す
  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnet_ids
}

4. localsを使うべきケース

localsコード内の計算・繰り返しを整理するために使います。

ケース1: 繰り返し使う式に名前をつける

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

provider "aws" {
  region = "ap-northeast-1"
}

variable "project" {
  type = string
}

variable "environment" {
  type = string
}

locals {
  # 繰り返し使うプレフィックスを1か所で定義
  name_prefix = "${var.project}-${var.environment}"

  # 条件式の結果に名前をつける
  is_production = var.environment == "prd"

  # 条件から派生する値を計算
  retention_days = local.is_production ? 90 : 7
  multi_az       = local.is_production ? true : false
}

resource "aws_cloudwatch_log_group" "app" {
  name              = "/aws/${local.name_prefix}/app"
  retention_in_days = local.retention_days  # 毎回条件式を書かなくて済む

  tags = {
    Name        = "${local.name_prefix}-log-group"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

ケース2: 共通タグのDRY化

locals {
  # 全リソースに付けるタグを一元管理
  common_tags = {
    Project     = var.project
    Environment = var.environment
    ManagedBy   = "terraform"
    Owner       = "infra-team"
  }
}

data "aws_ami" "amazon_linux_2023" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.amazon_linux_2023.id
  instance_type = "t3.micro"

  # merge()で共通タグ + 個別タグを合成
  tags = merge(local.common_tags, {
    Name = "${local.name_prefix}-web"
    Role = "web-server"
  })
}

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = merge(local.common_tags, {
    Name = "${local.name_prefix}-vpc"
  })
}

ケース3: for式と組み合わせたデータ変換

variable "subnet_configs" {
  type = list(object({
    name = string
    cidr = string
    az   = string
  }))
  default = [
    { name = "public-1a",  cidr = "10.0.1.0/24",  az = "ap-northeast-1a" },
    { name = "private-1a", cidr = "10.0.11.0/24", az = "ap-northeast-1a" },
  ]
}

locals {
  # listをfor_eachに渡せるmapに変換(for式)
  # ← この変換はvariableには書けない(式が書けないため)
  subnets_map = { for s in var.subnet_configs : s.name => s }
}

resource "aws_subnet" "main" {
  for_each = local.subnets_map  # localsを経由してfor_eachに渡す

  vpc_id            = aws_vpc.main.id
  cidr_block        = each.value.cidr
  availability_zone = each.value.az

  tags = merge(local.common_tags, {
    Name = each.key
  })
}

5. 判断フローチャート

値を定義したい
       │
       ▼
「Terraformの外部(CIやオペレーター)から変えたい?」
       │
  Yes ─┤                         No
       │                          │
       ▼                          ▼
   variable                「コード内で式や計算をしたい?」
(tfvars/-varで渡す)              │
                             Yes ─┤           No
                                  │            │
                                  ▼            ▼
                               locals      variable の
                          (for式・計算)   default値で十分

6. よくあるアンチパターン

❌ アンチパターン1: localsで書くべきものをvariableに書く

# ❌ 外から変えない計算結果をvariableに書くのは間違い
variable "name_prefix" {
  default = "myapp-dev"  # 環境が変わったら手動で変える必要がある
  # → 実態はprojectとenvironmentから計算できる派生値
}

# ✅ localsで計算するのが正しい
locals {
  name_prefix = "${var.project}-${var.environment}"  # 自動で決まる
}

❌ アンチパターン2: variableで書くべきものをlocalsに書く

# ❌ 環境ごとに変えたい値をlocalsに書くと、コードを直接変更しないといけない
locals {
  environment = "prd"  # コードを書き換えないとdev/stgにできない
}

# ✅ variableにすべき
variable "environment" {
  type    = string
  default = "dev"
}

❌ アンチパターン3: variableに式を書こうとする

# ❌ variableのdefaultには式を書けない(文字列・数値・リスト等の固定値のみ)
variable "name_prefix" {
  default = "${var.project}-${var.environment}"  # Error!
}

# ✅ localsで計算する
locals {
  name_prefix = "${var.project}-${var.environment}"
}

7. variableとlocalsを組み合わせた設計例

実務ではvariablelocalsを組み合わせて使います。variableで外部入力を受け取り、localsでその値を加工・派生させるパターンが最も一般的です。

# ── 入力(外から変えられる) ──────────────────────
variable "project" {
  description = "プロジェクト名"
  type        = string
}

variable "environment" {
  description = "環境名(dev/stg/prd)"
  type        = string
}

variable "owner_team" {
  description = "担当チーム名"
  type        = string
  default     = "infra-team"
}

# ── 計算・派生値(コード内で自動的に決まる) ─────────
locals {
  # variableから計算する値
  name_prefix   = "${var.project}-${var.environment}"
  is_production = var.environment == "prd"

  # 環境に応じて変わる設定(外から渡す必要はない)
  instance_type  = local.is_production ? "t3.medium" : "t3.micro"
  retention_days = local.is_production ? 90 : 7

  # タグの共通定義
  common_tags = {
    Project     = var.project
    Environment = var.environment
    Owner       = var.owner_team
    ManagedBy   = "terraform"
  }
}

8. 関連記事


9. まとめ

  • variable外部から値を渡すもの。tfvarsやCI環境変数から変えられる
  • localsコード内で計算・整理するもの。外から変えられない代わりに式が書ける
  • 「外から変えたいか?」→Yes:variable / No:「式を書きたいか?」→Yes:locals
  • アンチパターン: variableに式は書けない。localsで環境切り替えをしないこと
  • 実務の定番: variableで外部入力を受け取り、localsでプレフィックス・タグ・派生値を計算する

動作確認バージョン: Terraform >= 1.9 / AWS Provider ~> 5.0 対象リージョン: ap-northeast-1(東京) 公式ドキュメント(variable): https://developer.hashicorp.com/terraform/language/values/variables 公式ドキュメント(locals): https://developer.hashicorp.com/terraform/language/values/locals