Documentation Index Fetch the complete documentation index at: https://mintlify.com/anomalyco/sst/llms.txt
Use this file to discover all available pages before exploring further.
Stages in SST let you deploy multiple isolated instances of your application. This guide shows you how to effectively manage stages for different environments and workflows.
What are stages?
A stage is an isolated instance of your application with its own:
Resources — Separate Lambda functions, databases, buckets, etc.
State — Independent deployment state
Secrets — Stage-specific secret values
Configuration — Different settings per stage
# Deploy to different stages
sst deploy --stage dev
sst deploy --stage staging
sst deploy --stage production
Each stage is completely isolated—resources don’t conflict or interfere.
Default stage
If you don’t specify a stage, SST uses your username:
sst deploy
# Equivalent to: sst deploy --stage <your-username>
This gives each developer their own personal stage automatically.
Personal stages let developers work independently without stepping on each other’s toes.
Specifying stages
Set the stage in multiple ways:
Via flag
sst deploy --stage production
Via environment variable
export SST_STAGE = production
sst deploy
Via .env file
The precedence order is:
--stage flag (highest priority)
SST_STAGE environment variable
.env file
Username (default)
Stage naming
Stage names:
Must be URL-safe (alphanumeric, hyphens, underscores)
Should be descriptive and consistent
Are case-sensitive
Common stage names:
# Permanent stages
production
staging
development
# Personal stages
alice
bob-dev
john-test
# Feature stages
feature-payments
refactor-api
# PR stages
pr-123
pr-456
Avoid generic names like test, dev, prod if multiple people might use them. Be specific: alice-dev, production.
Common stage patterns
Three-stage workflow
Most teams use:
Development — Personal stages for each developer
Staging — Shared QA/testing environment
Production — Live customer-facing environment
# Developers
sst dev --stage alice
sst dev --stage bob
# QA team
sst deploy --stage staging
# Production
sst deploy --stage production
PR preview stages
Create a stage for each pull request:
console : {
autodeploy : {
target ( event ) {
if ( event . type === "pull_request" ) {
return { stage: `pr- ${ event . number } ` };
}
},
},
},
PR #123 deploys to stage pr-123, giving each PR its own environment.
Multi-region stages
Deploy the same stage to multiple regions:
# US East
AWS_REGION = us-east-1 sst deploy --stage production-us
# EU West
AWS_REGION = eu-west-1 sst deploy --stage production-eu
Stage-specific configuration
Configure stages differently in your sst.config.ts:
app ( input ) {
return {
name: "my-app" ,
home: "aws" ,
removal: input . stage === "production" ? "retain" : "remove" ,
protect: input . stage === "production" ,
};
}
Stage-based resources
Use different resources per stage:
const db = new sst . aws . Postgres ( "Database" , {
instance: $app . stage === "production"
? "db.t4g.large"
: "db.t4g.small" ,
});
Stage-based settings
const api = new sst . aws . Function ( "MyApi" , {
handler: "src/api.handler" ,
memory: $app . stage === "production" ? "2 GB" : "512 MB" ,
timeout: $app . stage === "production" ? "30 seconds" : "10 seconds" ,
environment: {
LOG_LEVEL: $app . stage === "production" ? "error" : "debug" ,
},
});
Environment variables per stage
Use stage-specific .env files:
API_URL = https://api.example.com
LOG_LEVEL = error
API_URL = https://staging-api.example.com
LOG_LEVEL = debug
API_URL = http://localhost:3000
LOG_LEVEL = debug
SST loads the appropriate file based on the stage:
sst deploy --stage production # Loads .env.production
sst deploy --stage staging # Loads .env.staging
sst dev # Loads .env
The .env file takes precedence over .env.<stage> files.
Secrets per stage
Set different secrets for each stage:
# Development
sst secret set StripeKey sk_test_abc123 --stage dev
# Production
sst secret set StripeKey sk_live_xyz789 --stage production
Or use fallback secrets:
# All stages use this by default
sst secret set ApiKey default-key --fallback
# Production overrides with its own
sst secret set ApiKey prod-key --stage production
Switching between stages
Work with multiple stages:
# Deploy to staging
sst deploy --stage staging
# Test staging
curl https://staging.example.com
# Deploy to production
sst deploy --stage production
# Test production
curl https://example.com
Or keep multiple terminals open:
# Terminal 1 - Development
sst dev
# Terminal 2 - Staging tests
sst deploy --stage staging
# Terminal 3 - Production monitoring
sst logs --stage production --tail
Listing stages
See all deployed stages:
This shows:
All stages in your AWS account
Last updated timestamp
Number of resources
Removing stages
Delete a stage and all its resources:
sst remove --stage old-feature
Based on your removal setting:
app ( input ) {
return {
name: "my-app" ,
home: "aws" ,
removal: input . stage === "production" ? "retain" : "remove" ,
};
}
Options:
"remove" — Delete everything
"retain" — Keep data resources (S3, DynamoDB), delete others
"retain-all" — Keep all resources
Automatic PR cleanup
Remove PR stages automatically:
console : {
autodeploy : {
target ( event ) {
if ( event . type === "pull_request" ) {
if ( event . action === "removed" ) {
// This triggers removal
return ;
}
return { stage: `pr- ${ event . number } ` };
}
},
},
},
When a PR closes, SST automatically removes the stage.
Protecting stages
Prevent accidental removal:
app ( input ) {
return {
name: "my-app" ,
home: "aws" ,
protect: input . stage === "production" ,
};
}
Now sst remove --stage production will fail:
sst remove --stage production
Error: Cannot remove protected stage
Cost management
Stages can multiply costs. Manage this by:
Use smaller resources in dev
const db = new sst . aws . Postgres ( "Database" , {
instance: $app . stage === "production" ? "db.r6g.xlarge" : "db.t4g.micro" ,
});
Remove unused stages
# List all stages
sst state list
# Remove old ones
sst remove --stage old-feature-branch
sst remove --stage pr-123
Set up automatic cleanup
Use AWS Lambda to remove old stages:
const cleanup = new sst . aws . Cron ( "StageCleanup" , {
schedule: "rate(1 day)" ,
job: "src/cleanup.handler" ,
});
import { SST } from "sst" ;
export const handler = async () => {
// Find stages older than 7 days
// Run `sst remove` for each
};
Monitor costs per stage
Tag resources with stage name:
const bucket = new sst . aws . Bucket ( "MyBucket" , {
transform: {
bucket: {
tags: {
Stage: $app . stage ,
Environment: $app . stage === "production" ? "prod" : "dev" ,
},
},
},
});
Then use AWS Cost Explorer to see costs per stage.
Multi-account stages
Deploy stages to different AWS accounts:
Via profiles
# Development account
AWS_PROFILE = dev-account sst deploy --stage dev
# Production account
AWS_PROFILE = prod-account sst deploy --stage production
Via Console environments
Configure in the SST Console:
Go to app settings
Add environment “production”
Select production AWS account
Map stage “production” to this environment
Autodeploy uses the correct account automatically.
Stage comparison
Compare configurations across stages:
# View staging outputs
sst deploy --stage staging
cat .sst/outputs.json
# View production outputs
sst deploy --stage production
cat .sst/outputs.json
# Compare
diff <( cat .sst/outputs.json) <( AWS_PROFILE = prod cat .sst/outputs.json)
Best practices
Use consistent naming
Pick a naming scheme and stick to it:
# Good - consistent
production
staging
alice-dev
bob-dev
# Avoid - inconsistent
prod
stage
alice
bobDev
Document your stages
Document the purpose of each permanent stage:
## Stages
- `production` - Live environment (AWS account: 123456789)
- `staging` - QA testing (AWS account: 987654321)
- `<username>` - Personal development stages
- `pr-<number>` - Pull request previews (auto-removed)
Clean up regularly
Schedule regular stage cleanup:
# Monthly cleanup day
sst state list
sst remove --stage old-experiment
sst remove --stage pr-123
Keep production isolated
Use a separate AWS account for production:
Different credentials
Different IAM policies
Stricter access controls
Separate billing
Test in staging first
Always deploy to staging before production:
# Deploy and test staging
sst deploy --stage staging
# Run tests...
# If tests pass:
sst deploy --stage production
Troubleshooting
Wrong stage deployed
If you deployed to the wrong stage:
# Remove the wrong deployment
sst remove --stage wrong-stage
# Deploy to the correct stage
sst deploy --stage correct-stage
Stage won’t remove
If sst remove fails:
Check if stage is protected
Try with --continue flag
Manually delete stuck resources in AWS Console
Run sst unlock if state is locked
Can’t find stage
If SST can’t find your stage:
# List all stages
sst state list
# Verify stage name is correct
sst deploy --stage correct-name
Next steps
Deployment Learn about deployment strategies
Secrets Manage secrets per stage
Development Workflow Build an effective workflow
Console Manage stages in the Console