AWS Resource Naming Conventions
This is a reference pattern. Learn from the approach, adapt to your context — don't copy verbatim.
Status: 🔴 CRITICAL PATTERN Category: Infrastructure Applies To: All AWS resources across all projects
Overview
This document defines the standard naming conventions for AWS resources. Following these patterns ensures:
- Clear project ownership
- Environment separation and safety
- Consistent resource identification
- Easy filtering and management in AWS Console
- Prevention of resource conflicts and data corruption
Standard Naming Pattern
Core Format
{project}-{resource-name}-{environment}Components
- Project Prefix (required)
- Short identifier for the project (2-4 characters) - Lowercase letters only - Examples: cme, aip, app - Identifies which project owns the resource
- Resource Name (required)
- Describes the resource's purpose - Lowercase with hyphens - Examples: job-extractor, opportunities, website
- Environment Suffix (required)
- dev - Development environment - prod - Production environment - staging - Staging environment (optional) - local - Local development (for configuration only, never deployed) - Always in last position for consistency
Why This Pattern?
- ✅ Project ownership clear: Prefix identifies the owning project
- ✅ Consistent environment position: Always at the end
- ✅ Short names: No account/region unless required for uniqueness
- ✅ AWS best practice: Lowercase with hyphens
- ✅ Easy filtering: Can filter by prefix in AWS Console
- ✅ Multi-project support: Clear separation when multiple projects share an account
- ✅ Prevents conflicts: Environment suffix prevents dev/prod overwrites
Account Strategy: Single vs. Multi-Account
Two Deployment Approaches
Option 1: Single Account (dev and prod in same AWS account)
- Lower cost (no cross-account complexity)
- Simpler IAM and networking
- Requires environment suffix to prevent conflicts
Option 2: Multi-Account (separate AWS accounts for dev and prod)
- Better isolation and security
- Separate billing and cost tracking
- Still requires environment suffix for safety
Why Environment Suffix is Always Required
Even with separate AWS accounts, the environment suffix provides critical safety:
- Human Error Prevention: Immediately see which environment you're working in
# Without environment suffix aws lambda list-functions → job-extractor # Which environment is this? # With environment suffix aws lambda list-functions → job-extractor-prod # Clear: this is production! - Account Confusion Protection: Prevents mistakes when switching between accounts
# You think you're in dev account, but you're actually in prod # Without suffix: You might delete "job-extractor" thinking it's dev # With suffix: You see "job-extractor-prod" and stop immediately - Future-Proofing: Account strategy might change
- Start with single account, later split to multi-account - Merge accounts for cost optimization - Add staging environment to existing account
- Consistency: Same naming pattern works everywhere
- Local development - CI/CD pipelines - AWS Console - CloudFormation/CDK code
Real-World Scenario
# You're debugging an issue and need to check Lambda logs
# You have AWS CLI configured with multiple profiles
# Scenario 1: Without environment suffix
aws lambda get-function --function-name job-extractor --profile prod-account
# ⚠️ Is this the right function? Hard to tell from name alone
# Scenario 2: With environment suffix
aws lambda get-function --function-name job-extractor-prod --profile prod-account
# ✅ Name confirms you're looking at production resource
# ✅ Immediate visual confirmation prevents mistakesRule: Always include environment suffix, regardless of account strategy. It's a safety layer that costs nothing but prevents costly mistakes.
Critical: Why Environment Suffix Matters
The Problem
Without environment identifiers in resource names, dev and prod resources conflict when deployed to the same AWS account, causing overwrites and data corruption.
Real-World Impact
Lambda Functions (no environment in name):
// ❌ WRONG
functionName: 'job-data-extractor'
// GitHub Actions deploys dev
ENVIRONMENT=dev → Lambda: job-data-extractor
// GitHub Actions deploys prod
ENVIRONMENT=prod → Lambda: job-data-extractor // OVERWRITES dev!DynamoDB Tables (no environment in name):
// ❌ WRONG
tableName: `opportunities-${account}-${region}`
// Both environments use SAME table
// Result: Dev and prod data MIXED!The Solution
Always include environment in resource names:
// ✅ CORRECT
functionName: `job-data-extractor-${environment}`
tableName: `opportunities-${environment}`
// Results:
// dev: job-data-extractor-dev, opportunities-dev
// prod: job-data-extractor-prod, opportunities-prodResource-Specific Patterns
Lambda Functions
Pattern: {project}-{function-name}-{environment}
Examples:
// ✅ CORRECT
functionName: `cme-job-extractor-${environment}`
functionName: `aip-data-sync-${environment}`
// Results:
// cme-job-extractor-dev
// cme-job-extractor-prod
// aip-data-sync-devRationale: Lambda names must be unique per account. Environment suffix prevents dev/prod conflicts.
DynamoDB Tables
Pattern: {project}-{table-name}-{environment}
Examples:
// ✅ CORRECT
tableName: `cme-opportunities-${environment}`
tableName: `aip-user-sessions-${environment}`
// Results:
// cme-opportunities-dev
// cme-opportunities-prodOptional Extended Pattern (when uniqueness needed):
tableName: `{project}-{table-name}-${environment}-${account}-${region}`
// Example:
// cme-opportunities-dev-123456-us-east-1Rationale: Table names must be unique per account. Environment suffix is critical to prevent data mixing.
S3 Buckets
Pattern: {project}-{bucket-purpose}-{environment} (add random suffix if collision)
Examples:
// ✅ CORRECT
bucketName: `cme-website-${environment}`
bucketName: `aip-assets-${environment}`
// Results:
// cme-website-dev
// cme-website-prodIf collision occurs (bucket name already taken globally):
bucketName: `cme-website-${environment}-x7k2m`
// Results:
// cme-website-dev-x7k2m
// cme-website-prod-p9n4qImportant Notes:
- Bucket names must be globally unique across ALL AWS accounts
- Use lowercase letters, numbers, and hyphens only (no underscores)
- If deployment fails with "BucketAlreadyExists", add random suffix
- Suffix format: 5 random lowercase alphanumeric characters
Rationale: Bucket names must be globally unique. Start with clean pattern, add suffix only if needed.
API Gateway
Pattern: {PROJECT} API - {environment} (display name, uppercase project)
Examples:
// ✅ CORRECT
restApiName: `CME API - ${environment}`
restApiName: `AIP API - ${environment}`
// Results:
// CME API - dev
// CME API - prodRationale: Display name for humans. Uppercase for readability in AWS Console.
CloudFront Distributions
Pattern: Comment field: {Project Name} - {environment}
Examples:
Career Match Engine - dev
AI Portfolio - prodTag: Environment: dev or Environment: prod
Rationale: CloudFront IDs are auto-generated. Use comment and tags for identification.
Lambda Layers
Pattern: {project}-{layer-name} or {project}-{layer-name}-{environment}
Examples:
// Shared across environments
layerName: 'cme-common-layer'
layerName: 'aip-utils-layer'
// Environment-specific
layerName: `cme-config-layer-${environment}`Note: Layers are often shared across environments. Environment suffix optional.
Rationale: Layers are versioned. Sharing across environments reduces duplication.
Lambda Layer Code Organization
Goal: Provide each Lambda with only what it needs while avoiding code duplication. This is a trade-off between deployment size and code reuse - evaluate on a case-by-case basis.
Layer Structure: Organize by scope and reusability
layers/
common/ # Truly shared utilities (3+ consumers)
nodejs/
logger.mjs
response-builder.mjs
config-loader.mjs
validation.mjs
feature-utils/ # Feature-specific utilities
nodejs/
feature-operations.mjs
feature-schemas.mjs
external-sdks/ # Third-party integrations
nodejs/
package.json # npm dependencies
sdk-wrapper.mjsOrganizing Shared Code:
Common Layer - For widely-used utilities:
- Generic helpers (logging, validation, response formatting)
- Used by 3+ different features
- No feature-specific logic
Feature Layer - For feature-specific shared code:
- Operations and schemas for a specific domain
- Used by 2+ functions within that feature
- Contains feature-specific logic
Integration Layer - For external dependencies:
- Third-party SDK packages
- Wrappers around external services
- Isolates version management
Using Multiple Layers:
// Function can reference multiple layers
const myFunction = new Function({
layers: [
commonLayer, // Generic utilities
featureALayer, // Feature A utilities
featureBLayer // Feature B utilities (if needed)
]
});Decision Guide:
- Generic utilities →
commonlayer - Feature-specific code → feature layer
- External packages → integration layer
- Cross-feature needs → Reference multiple layers
Trade-offs:
- More layers = lighter individual Lambdas but more complexity
- Fewer layers = simpler setup but larger deployments
- Balance based on your project's needs
Example Scenario: API schemas used by multiple features
- If generic: Move to
commonlayer - If feature-specific: Keep in feature layer, other features reference it
- Don't duplicate: Use layer composition
SQS Queues
Pattern: {project}-{queue-name}-{environment}
Examples:
queueName: `cme-job-processing-${environment}`
queueName: `aip-notifications-${environment}`SNS Topics
Pattern: {project}-{topic-name}-{environment}
Examples:
topicName: `cme-alerts-${environment}`
topicName: `aip-events-${environment}`EventBridge Rules
Pattern: {project}-{rule-name}-{environment}
Examples:
ruleName: `cme-daily-sync-${environment}`
ruleName: `aip-cleanup-${environment}`Step Functions
Pattern: {project}-{state-machine-name}-{environment}
Examples:
stateMachineName: `cme-workflow-${environment}`
stateMachineName: `aip-pipeline-${environment}`Secrets Manager
Pattern: {project}/{environment}/{secret-name}
Examples:
secretName: `cme/${environment}/api-key`
secretName: `aip/${environment}/db-password`SSM Parameters
Pattern: /{project}/{environment}/{namespace}/{key}
Examples:
/cme/dev/lambda/job-extractor/LLM_API_KEY
/cme/prod/api/corsAllowedOrigins
/aip/dev/frontend/api-endpointNote: Use camelCase for project name in SSM for historical compatibility.
CloudWatch Log Groups
Pattern: /aws/lambda/{project}-{function-name}-{environment}
Examples:
/aws/lambda/cme-job-extractor-dev
/aws/lambda/cme-recruiter-chat-prod
/aws/lambda/aip-data-sync-devNote: Auto-generated by Lambda. Follows Lambda naming automatically.
CDK Implementation Pattern
Construct Props
export interface ResourceConstructProps {
projectPrefix: string; // 'cme', 'aip', etc.
environment: string; // 'dev', 'prod', 'staging'
account: string;
region: string;
}Lambda Function
const myFunction = new lambda.Function(this, 'MyFunction', {
functionName: `${props.projectPrefix}-my-function-${props.environment}`,
runtime: lambda.Runtime.NODEJS_22_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda'),
environment: {
ENVIRONMENT: props.environment,
PROJECT: props.projectPrefix
}
});DynamoDB Table
const myTable = new dynamodb.Table(this, 'MyTable', {
tableName: `${props.projectPrefix}-my-table-${props.environment}`,
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
removalPolicy: props.environment === 'prod'
? cdk.RemovalPolicy.RETAIN
: cdk.RemovalPolicy.DESTROY
});S3 Bucket
const myBucket = new s3.Bucket(this, 'MyBucket', {
bucketName: `${props.projectPrefix}-my-bucket-${props.environment}`,
removalPolicy: props.environment === 'prod'
? cdk.RemovalPolicy.RETAIN
: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: props.environment !== 'prod'
});Environment Variable Setup
Required Environment Variable
# Must be set before deployment
export ENVIRONMENT=dev # or 'prod', 'staging', etc.
export PROJECT_PREFIX=myapp # Short project identifierDeployment Script
#!/bin/bash
set -e
if [ -z "$ENVIRONMENT" ]; then
echo "Error: ENVIRONMENT is not set"
echo "Usage: ENVIRONMENT=dev ./deploy.sh"
exit 1
fi
if [ -z "$PROJECT_PREFIX" ]; then
echo "Error: PROJECT_PREFIX is not set"
echo "Usage: PROJECT_PREFIX=myapp ENVIRONMENT=dev ./deploy.sh"
exit 1
fi
echo "Deploying $PROJECT_PREFIX to environment: $ENVIRONMENT"
cdk deploy --allGitHub Actions
name: Deploy
on:
push:
branches:
- main # Deploys to prod
- develop # Deploys to dev
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set environment
run: |
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "ENVIRONMENT=prod" >> $GITHUB_ENV
else
echo "ENVIRONMENT=dev" >> $GITHUB_ENV
fi
echo "PROJECT_PREFIX=myapp" >> $GITHUB_ENV
- name: Deploy
run: |
echo "Deploying $PROJECT_PREFIX to $ENVIRONMENT"
./deploy.shValidation Checklist
Before deploying any resource, verify:
- Starts with project prefix (
cme-,aip-, etc.) - Ends with environment (
-dev,-prod,-staging) - Uses lowercase with hyphens (except API Gateway display names)
- No hardcoded environment values (use variables)
- Name clearly describes resource purpose
- Follows pattern:
{project}-{resource-name}-{environment} - Environment variable is set before deployment
Common Pitfalls
❌ Pitfall 1: Hardcoded Environment
// ❌ WRONG - Hardcoded 'dev'
bucketName: `my-app-dev`
// ✅ CORRECT - Use variable
bucketName: `my-app-${environment}`❌ Pitfall 2: Missing Environment Variable
// ❌ WRONG - No environment
functionName: 'my-function'
// ✅ CORRECT - Include environment
functionName: `my-function-${environment}`❌ Pitfall 3: Inconsistent Pattern
// ❌ WRONG - Inconsistent
functionName: `my-function-${environment}` // Has environment
tableName: `my-table` // Missing environment
// ✅ CORRECT - Consistent
functionName: `my-function-${environment}`
tableName: `my-table-${environment}`❌ Pitfall 4: Missing Project Prefix
// ❌ WRONG - No project identification
functionName: `job-extractor-${environment}`
// ✅ CORRECT - Include project prefix
functionName: `cme-job-extractor-${environment}`❌ Pitfall 5: Assuming Different Accounts Mean No Environment Suffix Needed
// ❌ WRONG ASSUMPTION
// "We use different AWS accounts for dev/prod, so we don't need environment in names"
functionName: 'job-extractor' // No environment suffix
// ✅ CORRECT - Always include environment
functionName: `job-extractor-${environment}`
// REALITY:
// - Human error: You might be in wrong account without realizing
// - Visual confirmation: Name tells you it's prod even if you're confused about account
// - Account strategy might change (single → multi or multi → single)
// - Consistency: Same pattern works for single and multi-account setups
// - Safety layer: Costs nothing, prevents costly mistakesMigration from Old Names
Audit Phase
- List all AWS resources in the account
- Identify resources without environment in name
- Identify resources without project prefix
- Document which environments are affected
- Plan migration strategy
Migration Phase
- Deploy new resources with correct names
- Migrate data from old to new resources
- Update application to use new resources
- Verify new resources working
- Delete old resources after validation
Validation
How to Verify Separation
AWS Console Check:
# List all Lambda functions
aws lambda list-functions --query 'Functions[*].FunctionName'
# Should see:
# - cme-my-function-dev
# - cme-my-function-prod
# ✅ Clear project and environment separation
# NOT:
# - my-function
# ❌ No project or environment identifierDynamoDB Check:
# List all tables
aws dynamodb list-tables
# Should see:
# - cme-my-table-dev
# - cme-my-table-prod
# ✅ Clear separationDeployment Test:
# Deploy dev
ENVIRONMENT=dev ./deploy.sh
# Deploy prod
ENVIRONMENT=prod ./deploy.sh
# Verify:
# - No resource overwrites
# - Both environments coexist
# - No conflicts or errorsQuick Reference
✅ DO
- Include project prefix in ALL resource names
- Include
${environment}in ALL resource names - Use consistent naming pattern across all resources
- Validate environment variable is set before deployment
- Test both dev and prod deployments to same account
- Document naming convention in project README
❌ DON'T
- Hardcode environment values ('dev', 'prod')
- Skip project prefix in resource names
- Assume different AWS accounts mean no naming needed
- Skip environment in "temporary" or "test" resources
- Use different naming patterns for different resource types
- Deploy without verifying environment variable
Related Patterns
- Configuration Management - Environment-specific configuration
- Infrastructure Overview - General infrastructure patterns
- Security Overview - Security considerations
Summary
Critical Rules:
- Every AWS resource name MUST include the project prefix
- Every AWS resource name MUST include the environment identifier
- Use the pattern:
{project}-{resource-name}-{environment}
Why:
- Prevents resource conflicts and data corruption
- Enables clear project ownership
- Allows safe multi-environment deployments to same account
- Facilitates easy resource identification and management
How: Use ${projectPrefix} and ${environment} variables in all resource names consistently.
Validation: Deploy both dev and prod to same account and verify no conflicts.
Remember: This is not optional. It's a critical pattern that prevents production incidents and enables clear resource management.
Progressive Improvement
If the developer corrects a behavior that this skill should have prevented, suggest a specific amendment to this skill to prevent the same correction in the future.