SST provides built-in secrets management with encryption, per-stage values, and fallbacks. This guide shows you how to manage secrets securely across environments.
What are secrets?
Secrets are sensitive values that your application needs at runtime:
API keys (Stripe, Twilio, etc.)
Database passwords
OAuth client secrets
Private keys
Encryption keys
Never commit secrets to version control. Use SST’s secrets management instead.
Creating secrets
Define secrets in your sst.config.ts:
const stripeKey = new sst . Secret ( "StripeKey" );
const dbPassword = new sst . Secret ( "DatabasePassword" );
const api = new sst . aws . Function ( "MyApi" , {
handler: "src/api.handler" ,
link: [ stripeKey , dbPassword ],
});
Secrets are defined like any other resource but their values are set separately using the CLI.
Setting secret values
Use the sst secret set command:
sst secret set StripeKey sk_test_abc123
This encrypts and stores the secret in your AWS account.
From a file
For multi-line secrets like RSA keys:
# Create a file
cat > key.pem << EOF
-----BEGIN RSA PRIVATE KEY-----
MEgCQQCo9+BpMRYQ/dL3DS2CyJxRF+j6ctbT3/Qp84+KeFhnii7NT7fELilKUSnx
S30WAvQCCo2yU1orfgqr41mM70MBAgMBAAE=
-----END RSA PRIVATE KEY-----
EOF
# Set from file
sst secret set PrivateKey < key.pem
# Clean up
rm key.pem
Interactively
Omit the value to enter it interactively:
sst secret set StripeKey
Enter value: [hidden input]
Your input won’t be visible in the terminal or command history.
Using secrets
Access secrets in your functions with the SST SDK:
import { Resource } from "sst" ;
import Stripe from "stripe" ;
const stripe = new Stripe ( Resource . StripeKey . value , {
apiVersion: "2023-10-16" ,
});
export const handler = async ( event : any ) => {
const session = await stripe . checkout . sessions . create ({
// ...
});
return {
statusCode: 200 ,
body: JSON . stringify ({ sessionId: session . id }),
};
};
Secrets are decrypted automatically at runtime. You don’t need to handle decryption.
Per-stage secrets
Set different values for each stage:
# Development
sst secret set StripeKey sk_test_abc123 --stage dev
# Production
sst secret set StripeKey sk_live_xyz789 --stage production
Each stage can have its own secret values:
Development stage uses test API keys
Production stage uses live API keys
Each developer’s personal stage can have their own values
Fallback values
Set a default value that applies to all stages:
sst secret set ApiKey default-key-123 --fallback
Stages use fallback values when they don’t have their own:
SST checks for stage-specific value (e.g., production)
If not found, uses the fallback value
If no fallback, the secret is undefined
Use fallbacks for secrets that are the same across environments, like third-party API keys that don’t have test modes.
Use case: Preview stages
Fallbacks are perfect for temporary preview stages:
# Set fallback for preview stages
sst secret set StripeKey sk_test_default --fallback
# Production uses its own value
sst secret set StripeKey sk_live_xyz789 --stage production
Now PR previews automatically use the test key without needing to set secrets for each PR.
Listing secrets
View all secrets for a stage:
Output:
# fallback
ApiKey = default-key-123
# my-app/dev
StripeKey = sk_test_abc123
DatabasePassword = ***
List only fallback secrets:
sst secret list --fallback
Removing secrets
Remove a secret value:
sst secret remove StripeKey
Remove from a specific stage:
sst secret remove StripeKey --stage production
Remove fallback value:
sst secret remove StripeKey --fallback
Removing a secret doesn’t redeploy your functions. Run sst deploy or wait for the next deployment for changes to take effect.
Loading from files
Load multiple secrets from a file:
STRIPE_KEY = sk_test_abc123
SENDGRID_API_KEY = SG.abc123
DATABASE_PASSWORD = super-secret
sst secret load ./secrets.env
Supports .env format:
Key=value pairs
Comments with #
Quoted values
Multi-line values
Migrating secrets
Copy secrets from one stage to another:
# Export from staging
sst secret list --stage staging > secrets.env
# Import to production
sst secret load ./secrets.env --stage production
How secrets work
Encryption
Secrets are encrypted using AWS KMS:
When you run sst secret set, the value is encrypted with a KMS key
The encrypted value is stored in an S3 bucket in your AWS account
At runtime, the value is decrypted and injected into your function
SST never stores unencrypted secrets. Everything is encrypted at rest and in transit.
Storage
Secrets are stored in:
S3 bucket — Named <app>-<stage>-secrets in your AWS account
Encrypted — Using AES-256-GCM with AWS KMS
Per-stage — Each stage has its own secrets bucket
Runtime access
In sst dev, secrets are:
Loaded from S3
Decrypted locally
Injected as environment variables
Available through Resource.SecretName.value
In deployed functions, secrets are:
Packaged into the function bundle (encrypted)
Decrypted at runtime
Available through Resource.SecretName.value
Console integration
Manage secrets through the SST Console:
Go to your app in console.sst.dev
Click Secrets
Add, update, or remove secrets
Changes are immediately available in sst dev
The Console provides a visual interface for managing secrets across all stages.
Best practices
Use descriptive names
Name secrets clearly:
// Good
const stripeKey = new sst . Secret ( "StripeKey" );
const sendgridApiKey = new sst . Secret ( "SendgridApiKey" );
// Avoid
const secret1 = new sst . Secret ( "Secret1" );
const key = new sst . Secret ( "Key" );
Separate development and production
Always use different secrets for different stages:
# Development - test keys
sst secret set StripeKey sk_test_abc123 --stage dev
# Production - live keys
sst secret set StripeKey sk_live_xyz789 --stage production
Rotate secrets regularly
Update secrets periodically:
# Generate new key
NEW_KEY = $( openssl rand -hex 32 )
# Update secret
sst secret set EncryptionKey $NEW_KEY --stage production
# Deploy to apply
sst deploy --stage production
Limit access
Only link secrets to functions that need them:
// Good - only link what's needed
const publicApi = new sst . aws . Function ( "PublicApi" , {
handler: "src/public.handler" ,
link: [], // No secrets needed
});
const paymentApi = new sst . aws . Function ( "PaymentApi" , {
handler: "src/payment.handler" ,
link: [ stripeKey ], // Only Stripe key
});
// Avoid - linking everything everywhere
const fn = new sst . aws . Function ( "Fn" , {
handler: "src/handler.handler" ,
link: [ stripeKey , twilioKey , sendgridKey ], // Too broad?
});
Document required secrets
Document secrets in your README:
## Required Secrets
Set these secrets before deploying:
```bash
sst secret set StripeKey < your-ke y >
sst secret set SendgridApiKey < your-ke y >
sst secret set DatabasePassword < your-passwor d >
### Use fallbacks for non-sensitive config
Fallbacks work well for:
- Third-party API keys (same across stages)
- Feature flags
- Non-sensitive configuration
Avoid fallbacks for:
- Production credentials
- Database passwords
- Encryption keys
## Security considerations
### Never commit secrets
Add these to `.gitignore`:
```gitignore title=".gitignore"
# Secrets
*.env
*.pem
*.key
secrets.*
.sst/secrets
Use CI/CD secrets
In CI/CD, set secrets as environment variables:
.github/workflows/deploy.yml
- name : Deploy
run : |
sst secret set StripeKey ${{ secrets.STRIPE_KEY }}
sst deploy --stage production
Audit secret access
Check which functions have access:
# View function permissions
aws lambda get-policy --function-name my-app-production-MyApi
Monitor secret usage
Set up CloudWatch alarms for:
KMS decrypt operations
S3 access to secrets bucket
Lambda function errors (might indicate secret issues)
Migrating from environment variables
If you’re using environment variables, migrate to secrets:
Before (environment variables)
const fn = new sst . aws . Function ( "MyFunction" , {
handler: "src/handler.handler" ,
environment: {
STRIPE_KEY: process . env . STRIPE_KEY ! , // Not secure
},
});
After (secrets)
const stripeKey = new sst . Secret ( "StripeKey" );
const fn = new sst . aws . Function ( "MyFunction" , {
handler: "src/handler.handler" ,
link: [ stripeKey ],
});
Set the value:
sst secret set StripeKey sk_test_abc123
Update your code:
// Before
const stripe = new Stripe ( process . env . STRIPE_KEY ! );
// After
import { Resource } from "sst" ;
const stripe = new Stripe ( Resource . StripeKey . value );
Troubleshooting
Secret not found
If you get “secret not found” errors:
Check that the secret is set:
Verify the stage matches:
sst secret list --stage production
Set the secret if missing:
sst secret set SecretName value
Secret not updating
After changing a secret:
If using sst dev, it updates automatically
If deployed, run sst deploy to apply changes
Functions must be reinvoked to see new values
Permission errors
If you get KMS permission errors:
Check your IAM permissions include:
kms:Decrypt
kms:DescribeKey
s3:GetObject on secrets bucket
Verify the execution role has access
Next steps
Linking Learn about resource linking
Deployment Deploy with secrets
Environment Variables Use environment variables
Security Security best practices