Skip to content

Resource Naming Conventions

Consistent naming across all infrastructure resources. These conventions apply to all Terraform-managed resources — whether authored by humans or AI agents.

Internal vs Workload Scopes

Resources are scoped by what they belong to:

CategoryScope prefixWhen to useExamples
Internal infratractorbeam-Corporate tooling, security, CI/CD, shared servicestractorbeam-fleet-cluster, tractorbeam-cloudtrail-bucket
SaaS workloads{product}-Internal products deployed to shared prod/nonprod accountshelios-prod-api-service, mailman-prod-worker-service
Client workloads{client}-Client-specific infrastructure in dedicated accountscarlyle-etl-cluster, carlyle-data-bucket

TIP

If you're working in this repo (internal-infra), everything uses the tractorbeam- scope. Product workloads live in separate repos with their own scope prefix.

INFO

Account model: Internal SaaS products share two workload accounts (Production and Nonprod) and use the {product}-{env}- prefix to namespace within them. Client engagements get dedicated accounts and use {client}- as the scope prefix. The {env} segment is critical for SaaS products since multiple products coexist in the same account.

General Rules

  1. kebab-case for all AWS infrastructure resource names
  2. Names must identify the resource type — anyone reading the name should know what kind of resource it is
  3. No hardcoded account IDs in names — use ${data.aws_caller_identity.current.account_id} when account scoping is needed

Naming Hierarchy

{scope}-{env}-{component}-{resource-type}
SegmentDescriptionExamples
scopeProduct, workload, or clienttractorbeam, helios, carlyle
envEnvironment (when scope has multiple)prod, staging, dev
componentLogical component within the scopefleet, api, worker, etl
resource-typeWhat kind of resource this iscluster, database, bucket, alb, vpc

Only include segments that disambiguate. Omit env for single-environment scopes. Omit component when there's only one. But always include enough to identify the resource type.

How the hierarchy nests

Different scopes have different environments, and different environments may have different components:

tractorbeam                                ← internal infra (single env)
├── fleet-cluster                          ← tractorbeam-fleet-cluster
├── fleet-database                         ← tractorbeam-fleet-database
├── fleet-cache                            ← tractorbeam-fleet-cache
├── fleet-alb                              ← tractorbeam-fleet-alb
├── fleet-alb-logs-bucket                  ← tractorbeam-fleet-alb-logs-bucket
├── shared-services-vpc                    ← tractorbeam-shared-services-vpc
├── cloudtrail-bucket                      ← tractorbeam-cloudtrail-bucket
├── security-logs-bucket-{account_id}      ← tractorbeam-security-logs-bucket-123...
└── terraform-state-bucket                 ← tractorbeam-terraform-state-bucket

helios                                     ← SaaS product (in shared prod/nonprod accounts)
├── prod                                   ← resources in Production account
│   ├── api-service                        ← helios-prod-api-service
│   ├── api-database                       ← helios-prod-api-database
│   ├── api-cache                          ← helios-prod-api-cache
│   ├── api-alb                            ← helios-prod-api-alb
│   ├── worker-service                     ← helios-prod-worker-service
│   ├── worker-queue                       ← helios-prod-worker-queue
│   ├── uploads-bucket                     ← helios-prod-uploads-bucket
│   └── vpc                                ← helios-prod-vpc
└── staging                                ← resources in Nonprod account
    ├── api-service                        ← helios-staging-api-service
    └── api-database                       ← helios-staging-api-database

carlyle                                    ← client workload (dedicated account)
├── etl-cluster                            ← carlyle-etl-cluster
├── etl-database                           ← carlyle-etl-database
└── data-bucket                            ← carlyle-data-bucket

When to include each segment

ScenarioPatternExample
Single env, one component{scope}-{type}carlyle-vpc
Single env, multiple components{scope}-{component}-{type}tractorbeam-fleet-database
Multiple envs{scope}-{env}-{component}-{type}helios-prod-api-service
Org-widetractorbeam-{purpose}-{type}tractorbeam-cloudtrail-bucket

INFO

Most resources in internal-infra omit the environment segment because they're single-instance corporate infrastructure. The environment segment becomes important in workload repos where prod/staging/dev coexist.

Common Resource Type Suffixes

Use these consistently so resource type is always clear from the name:

ResourceSuffixExample
S3 bucket-buckettractorbeam-cloudtrail-bucket
RDS cluster/instance-databasetractorbeam-fleet-database
ElastiCache-cachetractorbeam-fleet-cache
ECS cluster-clustertractorbeam-fleet-cluster
ECS service-servicehelios-prod-api-service
ALB-albtractorbeam-fleet-alb
VPC-vpctractorbeam-shared-services-vpc
Security group-sgtractorbeam-shared-services-endpoints-sg
VPC endpoint-endpointtractorbeam-shared-services-s3-endpoint
SQS queue-queuehelios-prod-worker-queue
SNS topic-topictractorbeam-security-alerts-topic
EventBridge rule-ruletractorbeam-break-glass-login-rule
KMS key-key (alias)alias/tractorbeam-cloudtrail-key
CloudWatch log group/ prefix path/tractorbeam/fleet/ecs
SSM parameter/ prefix path/tractorbeam/tailscale/eip-mapping
Budget-budgettractorbeam-management-monthly-budget
GCP service account-sa (in display name)Google Workspace Terraform, Google Workspace Audit Logs

Exception: When a resource's purpose already implies its type unambiguously, the suffix can be omitted. For example, a VPC is already a network — tractorbeam-shared-services-vpc doesn't need -vpc repeated. Use judgement, but when in doubt, include the suffix.

WARNING

If you create a resource type not listed in this table, add it with the suffix you chose before merging. This table is the source of truth — keeping it complete prevents inconsistency.

S3 Bucket Naming

S3 buckets follow the hierarchy plus -bucket suffix. Append the account ID only when the bucket is per-account:

hcl
# Org-wide bucket
bucket = "tractorbeam-cloudtrail-bucket"

# Per-account bucket
bucket = "tractorbeam-security-logs-bucket-${data.aws_caller_identity.current.account_id}"

IAM Roles

OriginStyleExample
Terraform-createdkebab-casetractorbeam-config-aggregator-role
AWS-managed/importedPascalCase (keep original)OrganizationAccountAccessRole
OIDC rolesPascalCase (AWS convention)GitHubActionsRolePlan

OIDC roles use PascalCase because they're referenced in trust policies and follow AWS's own naming convention. Don't rename imported AWS roles.

Secrets Manager

Use a forward-slash hierarchy with kebab-case segments. The path structure mirrors the naming hierarchy:

{scope}/{component}/{secret-name}
ExamplePurpose
tractorbeam/terraform-providersShared Terraform provider credentials
tractorbeam/fleet/database-passwordFleet database password
tractorbeam/fleet/license-keyFleet license key
tractorbeam/tailscale/exit-node-ipsTailscale exit node IP addresses
helios/prod/api/database-passwordHelios prod API database password

KMS Key Aliases

kebab-case with scope prefix:

alias/tractorbeam-cloudtrail-key
alias/tractorbeam-fleet-database-key
alias/helios-prod-encryption-key

Athena

Workgroup: {scope}-{purpose}-workgroup
Database:  {purpose} (underscores for SQL compatibility)

Example: workgroup tractorbeam-security-logs-workgroup, database security_logs

INFO

Athena databases use underscores because they're SQL identifiers. Workgroups use kebab-case to match other AWS resources.

AWS Organizations

Organizational Units

Title Case, human-readable:

Security
Infrastructure
Sandbox
Workloads

Accounts

Title Case with descriptive names:

Audit
Log Archive
Shared Services
Production
Sandbox (Wade Fletcher)

Service Control Policies

PascalCase compound names describing scope and purpose:

SandboxGuardrails
ClientPOCGuardrails
WorkloadsSecurityBaseline
OrganizationSecurityBaseline

Identity Resources (Okta, GitHub)

User-facing identity resources use Title Case with hyphens for multi-word names:

Okta Groups

Functional groups (from data/groups.json):

Employees
Engineering
Platform-Admins

Per-account AWS groups (auto-generated from data/accounts.json):

PatternPurposeExample
AWS-User-{Name}Team access (PowerUserAccess)AWS-User-Production, AWS-User-Carlyle
AWS-Admin-{Name}Root email distributionAWS-Admin-Audit, AWS-Admin-Production
AWS-ReadOnly-{Name}Read-only investigationAWS-ReadOnly-Production
AWS-Sandbox-{Owner}Sandbox accessAWS-Sandbox-Wade

Project groups (from data/projects.json):

PatternPurposeExample
Project-{Name}Repo access, K8s namespacesProject-Carlyle, Project-Mailman

Projects ≠ accounts. Projects are logical team groupings that scope repos and K8s namespaces. AWS access comes through AWS-* groups.

Okta Applications

Google Workspace
AWS IAM Identity Center
Fleet Admin

GitHub Teams

Match Okta group names exactly to maintain identity integration.

Migration Plan

Resources that predate this convention:

ResourceCurrentConventionAction
CloudTrail buckettractorbeam-cloudtrail-logstractorbeam-cloudtrail-bucketRename on next major change (S3 rename = recreate)
Security logs buckettractorbeam-security-logs-{id}tractorbeam-security-logs-bucket-{id}Rename on next major change
State buckettractorbeam-terraform-state-internal-infratractorbeam-terraform-state-bucketKeep (too risky to rename state bucket)
Fleet ECS clustertractorbeam-fleettractorbeam-fleet-clusterRename on next major change
Fleet RDStractorbeam-fleettractorbeam-fleet-databaseRename on next major change
Fleet ALBtractorbeam-fleettractorbeam-fleet-albRename on next major change
Fleet ElastiCachetractorbeam-fleet-valkeytractorbeam-fleet-cacheRename on next major change
Athena workgrouptractorbeam-security-logstractorbeam-security-logs-workgroupLow priority
SNS topicsecurity-alertstractorbeam-security-alerts-topicLow priority
EventBridge rulesbreak-glass-console-logintractorbeam-break-glass-login-ruleLow priority
KMS aliasalias/cloudtrailalias/tractorbeam-cloudtrail-keyLow priority
OIDC rolesGitHubActionsRolePlanPascalCaseKeep (AWS convention)
Imported AWS rolesOrganizationAccountAccessRolePascalCaseKeep (AWS standard)

WARNING

S3 bucket renames require destroy + recreate. The state bucket should never be renamed. Other renames should be batched into a planned migration with terraform state mv where possible, or scheduled during maintenance windows for resources that require recreation.