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:
| Category | Scope prefix | When to use | Examples |
|---|---|---|---|
| Internal infra | tractorbeam- | Corporate tooling, security, CI/CD, shared services | tractorbeam-fleet-cluster, tractorbeam-cloudtrail-bucket |
| SaaS workloads | {product}- | Internal products deployed to shared prod/nonprod accounts | helios-prod-api-service, mailman-prod-worker-service |
| Client workloads | {client}- | Client-specific infrastructure in dedicated accounts | carlyle-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
- kebab-case for all AWS infrastructure resource names
- Names must identify the resource type — anyone reading the name should know what kind of resource it is
- 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}| Segment | Description | Examples |
|---|---|---|
scope | Product, workload, or client | tractorbeam, helios, carlyle |
env | Environment (when scope has multiple) | prod, staging, dev |
component | Logical component within the scope | fleet, api, worker, etl |
resource-type | What kind of resource this is | cluster, 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-bucketWhen to include each segment
| Scenario | Pattern | Example |
|---|---|---|
| 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-wide | tractorbeam-{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:
| Resource | Suffix | Example |
|---|---|---|
| S3 bucket | -bucket | tractorbeam-cloudtrail-bucket |
| RDS cluster/instance | -database | tractorbeam-fleet-database |
| ElastiCache | -cache | tractorbeam-fleet-cache |
| ECS cluster | -cluster | tractorbeam-fleet-cluster |
| ECS service | -service | helios-prod-api-service |
| ALB | -alb | tractorbeam-fleet-alb |
| VPC | -vpc | tractorbeam-shared-services-vpc |
| Security group | -sg | tractorbeam-shared-services-endpoints-sg |
| VPC endpoint | -endpoint | tractorbeam-shared-services-s3-endpoint |
| SQS queue | -queue | helios-prod-worker-queue |
| SNS topic | -topic | tractorbeam-security-alerts-topic |
| EventBridge rule | -rule | tractorbeam-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 | -budget | tractorbeam-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:
# Org-wide bucket
bucket = "tractorbeam-cloudtrail-bucket"
# Per-account bucket
bucket = "tractorbeam-security-logs-bucket-${data.aws_caller_identity.current.account_id}"IAM Roles
| Origin | Style | Example |
|---|---|---|
| Terraform-created | kebab-case | tractorbeam-config-aggregator-role |
| AWS-managed/imported | PascalCase (keep original) | OrganizationAccountAccessRole |
| OIDC roles | PascalCase (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}| Example | Purpose |
|---|---|
tractorbeam/terraform-providers | Shared Terraform provider credentials |
tractorbeam/fleet/database-password | Fleet database password |
tractorbeam/fleet/license-key | Fleet license key |
tractorbeam/tailscale/exit-node-ips | Tailscale exit node IP addresses |
helios/prod/api/database-password | Helios 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-keyAthena
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
WorkloadsAccounts
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
OrganizationSecurityBaselineIdentity 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-AdminsPer-account AWS groups (auto-generated from data/accounts.json):
| Pattern | Purpose | Example |
|---|---|---|
AWS-User-{Name} | Team access (PowerUserAccess) | AWS-User-Production, AWS-User-Carlyle |
AWS-Admin-{Name} | Root email distribution | AWS-Admin-Audit, AWS-Admin-Production |
AWS-ReadOnly-{Name} | Read-only investigation | AWS-ReadOnly-Production |
AWS-Sandbox-{Owner} | Sandbox access | AWS-Sandbox-Wade |
Project groups (from data/projects.json):
| Pattern | Purpose | Example |
|---|---|---|
Project-{Name} | Repo access, K8s namespaces | Project-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 AdminGitHub Teams
Match Okta group names exactly to maintain identity integration.
Migration Plan
Resources that predate this convention:
| Resource | Current | Convention | Action |
|---|---|---|---|
| CloudTrail bucket | tractorbeam-cloudtrail-logs | tractorbeam-cloudtrail-bucket | Rename on next major change (S3 rename = recreate) |
| Security logs bucket | tractorbeam-security-logs-{id} | tractorbeam-security-logs-bucket-{id} | Rename on next major change |
| State bucket | tractorbeam-terraform-state-internal-infra | tractorbeam-terraform-state-bucket | Keep (too risky to rename state bucket) |
| Fleet ECS cluster | tractorbeam-fleet | tractorbeam-fleet-cluster | Rename on next major change |
| Fleet RDS | tractorbeam-fleet | tractorbeam-fleet-database | Rename on next major change |
| Fleet ALB | tractorbeam-fleet | tractorbeam-fleet-alb | Rename on next major change |
| Fleet ElastiCache | tractorbeam-fleet-valkey | tractorbeam-fleet-cache | Rename on next major change |
| Athena workgroup | tractorbeam-security-logs | tractorbeam-security-logs-workgroup | Low priority |
| SNS topic | security-alerts | tractorbeam-security-alerts-topic | Low priority |
| EventBridge rules | break-glass-console-login | tractorbeam-break-glass-login-rule | Low priority |
| KMS alias | alias/cloudtrail | alias/tractorbeam-cloudtrail-key | Low priority |
| OIDC roles | GitHubActionsRolePlan | PascalCase | Keep (AWS convention) |
| Imported AWS roles | OrganizationAccountAccessRole | PascalCase | Keep (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.