Skip to main content

Multi-Environment Setup Guide

Deploy separate stacks for development, staging, and production with consistent naming, configuration isolation, and safe termination protection.

Overview

carlin enables environment separation using the --environment option, configuration files, and branch-based automatic stack naming.

Environment Strategy Models

ModelDevelopmentStagingProductionNotes
Branch-Based OnlyFeature branchesmainmain with --environment productionSimple, limited isolation
Classic 3 Envsfeature/*staging stackproduction stackRecommended baseline
Multi-Regionus-east-1 devus-east-1 stagingus-east-1 + eu-west-1 prodHigh availability
Segmented AccountsDev accountStaging accountProduction accountStrong isolation + blast radius control

Naming Conventions

EnvironmentStack Naming PatternExample
Developmentmy-app-<branch>my-app-feature-auth
Stagingmy-app-stagingmy-app-staging
Productionmy-app-productionmy-app-production

Override with --stack-name for special cases:

carlin deploy --stack-name shared-vpc --environment production

Configuration Files

Create base config:

# carlin.yml
region: us-east-1
parameters:
InstanceType: t3.micro

Environment-specific configs:

# carlin.staging.yml
environment: staging
parameters:
InstanceType: t3.small
DomainName: staging.example.com
# carlin.production.yml
environment: production
parameters:
InstanceType: t3.large
DomainName: example.com
EnableAlarms: true

Deploy using explicit config:

carlin deploy --config carlin.staging.yml
carlin deploy --config carlin.production.yml

Parameter Differentiation

Use CloudFormation parameters for environment variability:

export const template = {
Parameters: {
Environment: { Type: 'String' },
InstanceType: { Type: 'String' },
EnableAlarms: { Type: 'String', Default: 'false' },
},
Resources: {
AppInstance: {
Type: 'AWS::EC2::Instance',
Properties: {
InstanceType: { Ref: 'InstanceType' },
Monitoring: {
'Fn::If': [
{ 'Fn::Equals': [{ Ref: 'EnableAlarms' }, 'true'] },
true,
false,
],
},
},
},
},
};

Pass parameters:

# Staging
carlin deploy --environment staging --parameters '{"InstanceType":"t3.small","EnableAlarms":"false"}'

# Production
carlin deploy --environment production --parameters '{"InstanceType":"t3.large","EnableAlarms":"true"}'

Termination Protection

Enabled automatically when --environment is provided (e.g., staging, production).

Delete protected stack explicitly:

carlin deploy --destroy --stack-name my-app-production

Git Branch Workflow

Multi-Region Production

Deploy production to multiple regions for low latency / redundancy:

# Primary region
carlin deploy --environment production --region us-east-1

# Secondary region
carlin deploy --environment production --region eu-west-1

Add global resources (e.g., Route 53 / CloudFront) in a separate stack referencing both.

Shared vs Isolated Resources

ResourceShared Across EnvsSeparate Per EnvRecommendation
VPC✅ Possible✅ CommonSeparate for strong isolation
S3 Buckets⚠️ Risk (naming collisions)Separate (avoid accidental data mix)
DynamoDB Tables⚠️ Data bleed riskSeparate (prefix with env)
CloudFrontSeparate (different domains)
Lambda LayersShare only stable, versioned layers

Environment Variable Files

Generate .env per environment using stack outputs:

carlin generate-env --stack-name my-app-staging --default-environment Staging
carlin generate-env --stack-name my-app-production --default-environment Production

Source when running builds:

source .env.Staging
pnpm build

Promotion Strategy

Avoid mutating production stack directly from development changes—rebuild from source:

  1. Deploy feature stack: my-app-feature-x
  2. Merge to staging; deploy staging stack
  3. Validate staging (tests, smoke)
  4. Merge to main; deploy production stack anew

Observability Differences

Enable stricter alarms only in production:

export const template = {
Parameters: { EnableAlarms: { Type: 'String', Default: 'false' } },
Conditions: {
EnableProdAlarms: { 'Fn::Equals': [{ Ref: 'EnableAlarms' }, 'true'] },
},
Resources: {
ErrorAlarm: {
Type: 'AWS::CloudWatch::Alarm',
Condition: 'EnableProdAlarms',
Properties: {
/* ... */
},
},
},
};

Drift Detection

Schedule periodic drift detection for production only (manual or via EventBridge + Lambda).

Cleanup Strategy

Remove old feature stacks periodically:

# List stacks matching pattern
aws cloudformation list-stacks --stack-status-filter CREATE_COMPLETE | jq '.StackSummaries[] | select(.StackName | test("my-app-feature-"))'

# Destroy manually
carlin deploy --destroy --stack-name my-app-feature-old-branch

Common Pitfalls

PitfallImpactFix
Using same stack for staging & prodConfig/security leakageSeparate with --environment
Not parameterizing environment-specific valuesHard to promoteUse Parameters + --parameters
Forgetting termination protectionAccidental deletionAlways set --environment production
Sharing mutable bucketsData overwrite riskPrefix bucket names with env or separate stacks
Mixing region deploymentsLatency / config divergenceDocument explicit region strategy

Checklist

  • Separate stack names per environment
  • Termination protection enabled for production
  • Config files per environment committed
  • Parameter overrides documented
  • Outputs exported for cross-stack use
  • Feature stack deletion scheduled