naming-scheme-walkthrough.md

Naming Standards — General Approach

This document describes a general naming approach for Terraform-managed AWS resources, using the VPC module as a reference example.

1. Core Pattern

Every resource name follows a consistent structure:

${var.name}-<resource-type>[-<qualifier>]
  • var.name — a caller-supplied prefix identifying the deployment (e.g. an environment, workload, or stack name). The module itself never hardcodes this; it's injected from outside.
  • <resource-type> — a short, fixed token identifying what kind of resource this is (vpc, subnet, rt, nat, igw, eip, flow-log).
  • <qualifier> — optional, used when multiple instances of the same resource type exist (a tier, an AZ key, a logical map key).

Examples pulled directly from the module:

${var.name}-vpc
${var.name}-igw
${var.name}-flow-log-role
${var.name}-subnet-public-${each.key}
${var.name}-subnet-private-${each.key}
${var.name}-subnet-isolated-${each.key}
${var.name}-rt-public
${var.name}-rt-private-${each.key}
${var.name}-rt-isolated-${each.key}
${var.name}-nat-eip-${each.key}
${var.name}-nat-${each.key}

2. Resource-Type Tokens Are Abbreviated and Fixed

The module uses a small, consistent vocabulary for resource types rather than spelling things out per-resource:

Token Resource
vpc VPC
igw Internet Gateway
nat NAT Gateway
nat-eip Elastic IP for NAT
subnet Subnet
rt Route Table
flow-log VPC Flow Log
flow-log-role IAM Role for flow logs

Keeping these short and stable means names stay predictable and greppable across the whole estate, regardless of what var.name is set to.

3. Qualifiers Encode Tier and Key, Not Free Text

Where a resource type can have multiple instances, the name appends a tier (public / private / isolated) and/or the map key (each.key) that produced it — never a free-form description:

${var.name}-subnet-public-${each.key}
${var.name}-rt-isolated-${each.key}

This means the name is fully derivable from the Terraform state — anyone looking at for_each inputs can predict the resulting resource name without needing external documentation.

4. Tags Carry Structured Metadata; Names Carry Identity

The module separates two concerns:

  • Name (Name tag / resource name attribute) — human-identifiable, follows the pattern above.
  • resource-type tag — a machine-filterable, redundant encoding of what the resource is, independent of naming convention drift:
tags = merge(var.tags, local.module_tags, {
  Name          = "${var.name}-subnet-public-${each.key}"
  resource-type = "subnet"
  reachability  = "public"
})

A third convention layer — reachability — captures a semantic property (public/private/isolated) that isn't strictly a "type" but is useful for querying or policy enforcement.

Additionally, local.module_tags injects two constants on every resource:

module_tags = {
  managed-by  = "terraform"
  module-name = "networking:vpc"
}

module-name uses a <domain>:<component> convention (networking:vpc) — useful when many modules across many domains are tagging resources into the same account, so origin is traceable without inspecting state.

5. Conditional Resources Use Fixed Singleton Keys, Not Booleans in Names

For resources that may or may not exist (aws_internet_gateway.this, aws_route_table.public, aws_flow_log.this, IAM role/policy for flow logs), the module uses a for_each over a single-key map ({ igw = true }, { public = true }, { flow_log = true }, { role = true }) rather than count = 0/1. This keeps:

  • The resource address stable (aws_internet_gateway.this["igw"] rather than aws_internet_gateway.this[0]), so toggling the condition doesn't force a destroy/recreate.
  • The name string itself unaffected by the condition — the qualifier is a fixed literal ("igw", "public", "flow_log", "role"), not derived from a boolean.

6. Summary Rules

  1. Prefix every name with the caller-supplied identity (var.name); never hardcode identity inside a module.
  2. Use a short, fixed abbreviation per resource type; keep the vocabulary small and consistent module-wide.
  3. Append tier/key qualifiers only when multiplicity requires disambiguation, and derive them from for_each keys — never free text.
  4. Mirror the resource type as a tag (resource-type) so it's queryable independent of the name string.
  5. Tag every resource with a constant module identity (module-name, managed-by) using a domain:component style for the module name.
  6. For optional/conditional resources, use singleton-map for_each with a fixed key rather than count, so addresses and names stay stable across toggles.