Config
Built-in support for securely managing environment variables and secrets.
Overview
SST has a built-in way to connect your frontend and functions to your infrastructure, called Resource Binding. However, there are a couple of cases where you need to manually pass in some info.
You can do this using Config
. It allows you to pass in:
- Secrets: Sensitive values that cannot be defined in your code. You can use the
sst secrets
CLI to set them. - Parameters: Values from non-SST constructs, ie. static values or CDK constructs.
Once defined you can access these in your frontend or functions using the sst/node/config
package.
Get started
Start by creating a new SST + Next.js app by running the following command in your terminal. We are using Next.js for this example but you can use your favorite frontend.
npx create-sst@two --template standard/nextjs
Let's define some config values and load them in our frontend.
Define a secret
Add a secret to your stacks.
const STRIPE_KEY = new Config.Secret(stack, "STRIPE_KEY");
note
We don't set the values for the secret in your code.
Define a parameter
Add a parameter to your stacks.
const VERSION = new Config.Parameter(stack, "VERSION", {
value: "1.2.0",
});
Unlike the secret, we are setting the value of a parameter in code.
Add the imports
Import the Config
construct at the top.
- import { StackContext, NextjsSite } from "sst/constructs";
+ import { Config, StackContext, NextjsSite } from "sst/constructs";
Bind the config
Let's bind the secret and parameter to our Next.js app.
const site = new NextjsSite(stack, "site", {
+ bind: [VERSION, STRIPE_KEY],
path: "packages/web",
});
This allows Next.js app to access them.
Set the secret value
Then in your terminal set a value for the secret.
npx sst secrets set STRIPE_KEY sk_test_abc123
We use the sst secrets
CLI.
Load the config
Now you can access the secret and parameter in your Next.js app.
import { Config } from "sst/node/config";
export async function getServerSideProps() {
console.log(Config.VERSION, Config.STRIPE_KEY);
return { props: { loaded: true } };
}
tip
The Config client is imported from sst/node/config
, not sst/constructs
.
How it works
Let's take a look at how secrets and parameters work behind the scenes.
Secrets
Behind the scenes, secrets are stored as AWS SSM or AWS Systems Manager Parameters in your AWS account. When you run:
npx sst secrets set STRIPE_KEY sk_test_abc123
An SSM parameter of the type SecureString
is created with the name /sst/{appName}/{stageName}/Secret/STRIPE_KEY/value
, where {appName}
is the name of your SST app, and {stageName}
is the stage you are configuring for. The parameter value sk_test_abc123
gets encrypted and stored in AWS SSM.
tip
You can set a secret for another stage using the --stage
option.
By default, the sst secrets
CLI acts on your current local stage. To set a secret for another stage (say prod
), you can use the --stage
option.
npx sst secrets set --stage prod STRIPE_KEY sk_test_abc123
Function handler
And when you pass secrets into a function.
new Function(stack, "MyFunction", {
handler: "lambda.handler",
bind: [STRIPE_KEY],
}
It adds a Lambda environment variables named SST_Secret_value_STRIPE_KEY
to the function. The environment variable has a placeholder value __FETCH_FROM_SSM__
to indicate that the value for STRIPE_KEY
needs to be fetched from SSM at runtime using top-level await.
Top-level await
At runtime when you import the Config
package in your function.
import { Config } from "sst/node/config";
It performs a top-level await to fetch and decrypt STRIPE_KEY
from SSM. Once fetched, you can reference Config.STRIPE_KEY
directly in your code.
note
Due to the use of top-level await, your functions need to be bundled in the esm
format. This is the default in SST v2. Here's how to set it explicitly.
Note that the secret values are fetched once when the Lambda container first boots up, and the values are cached for subsequent invocations.
Error handling
If you reference a secret that hasn't been set in the bind
prop for the function, you'll get an error. For example, if you reference something like Config.XYZ
and it hasn't been set; you'll get the following runtime error.
Config.XYZ has not been set for this function.
Typesafety
The Config
object in your Lambda function code is also typesafe and your editor should be able to autocomplete the options.
Updating secrets
Secret values are not refetched on subsequent Lambda function invocations. So if the value of a secret changes while the Lambda container is still warm, it'll hang on to the old value.
To address this, SST forces functions to refetch the secret when its value changes. So when you run:
npx sst secrets set STRIPE_KEY sk_test_abc123
SST looks up all the functions using STRIPE_KEY
. And for each function, SST sets a Lambda environment variable named SST_ADMIN_SECRET_UPDATED_AT
with the value of the current timestamp. This will trigger the Lambda containers to restart. If a container is in the middle of handling an invocation, it will restart after the invocation is complete.
Fallback values
Sometimes you might be creating ephemeral stages or preview environments from pull requests. It can be annoying to manually set the value of a secret for these stages especially because they might all be using the same value.
To make this easier, you can set a fallback value for a secret. And if its value isn't set for a stage, it'll use the fallback value instead.
tip
Set a fallback value for your secret so you don't have to set them for ephemeral stages.
In the above example, it's likely all the dev stages share the same STRIPE_KEY
. So set a fallback value by running:
npx sst secrets set --fallback STRIPE_KEY sk_test_abc123
Similar to the set
command, SST creates an AWS SSM Parameter of the type SecureString
. And the parameter name in this case is /sst/{appName}/.fallback/Secret/STRIPE_KEY/value
.
info
The fallback value can only be inherited by stages deployed in the same AWS account and region.
If a function uses the STRIPE_KEY
secret, but neither the secret value or the fallback value has been set, you'll get a runtime error when you import sst/node/config
.
The following secrets were not found: STRIPE_KEY
Parameters
Behind the scenes, parameters are stored as Lambda environment variables. When you pass a parameter into a function:
new Function(stack, "MyFunction", {
handler: "lambda.handler",
bind: [USER_UPDATED_TOPIC],
}
A Lambda environment variable is added to the function, named SST_Parameter_value_USER_UPDATED_TOPIC
with the value of the topic name.
Function handler
At runtime when you import the Config
package in your function.
import { Config } from "sst/node/config";
It reads the value from process.env.SST_Parameter_value_USER_UPDATED_TOPIC
and assigns it to Config.USER_UPDATED_TOPIC
. You can then reference Config.USER_UPDATED_TOPIC
directly in your code.
Copy to SSM
SST also stores a copy of the parameter value in AWS SSM. For each parameter, an SSM parameter of the type String
is created with the name /sst/{appName}/{stageName}/parameters/USER_UPDATED_TOPIC
, where {appName}
is the name of your SST app, and {stageName}
is the stage. The parameter value is the topic name stored in plain text.
Storing the parameter values in SSM might seem redundant. But it provides a convenient way to fetch all the parameters used in your application. This can make it easy to test your functions. Read more about how SST uses Config
to make testing easier.
Error handling
If you reference a parameter that hasn't been set in the bind
prop, you'll get an error. For example, if you reference something like Config.XYZ
and it hasn't been set; you'll get the following runtime error.
Config.XYZ has not been set for this function.
Typesafety
The Config
object in your Lambda function code is also typesafe and your editor should be able to autocomplete the options.
Default values
By default the app name and the current stage are also made available in the Config
object as well.
import { Config } from "sst/node/config";
Config.APP;
Config.STAGE;
Other options
The sst/node
package only supports Node.js functions. For other runtimes, SST supports loading environment variables using dotenv.
note
If you are using Python, check out this community contributed snippet on using Config in your functions.
dotenv
The .env
support in SST is similar to what Create React App and Next.js do for environment variables. For example if you add the following .env
file to your project root.
TABLE_READ_CAPACITY=5
TABLE_WRITE_CAPACITY=5
SST will load the process.env.TABLE_READ_CAPACITY
and process.env.TABLE_WRITE_CAPACITY
variables into the Node.js environment; automatically allowing you to use them in your CDK code.
caution
If you are using JavaScript or TypeScript, it's strongly recommended that you use Config
instead of .env
.
Types of .env
files
Aside from the default .env
file, there are a couple of other types of .env
files. You can use them to better organize the environment variables in your SST app.
.env.{stageName}
You can add a
.env.{stageName}
file to override the default values for a specific stage. For example, this overrides the value for theprod
stage:.env.prodTABLE_READ_CAPACITY=20
.env*.local
You can also add
.env.local
and.env.{stageName}.local
files to set up environment variables that are specific to your local machine.
Here's the priority in which these files are loaded. Starting with the one that has the highest priority.
.env.dev.local
.env.dev
.env.local
.env
Assume that the current stage is dev
.
Committing .env
files
The .env
and .env.{stageName}
files can be committed to Git. On the other hand, the .env.local
and .env.{stageName}.local
shouldn't.
The .env*.local
files are meant to specify sensitive information specific to your machine. They should be ignored through the .gitignore
file.
caution
Don't commit any .env
files to Git that contain sensitive information.
Note that, SST doesn't enforce these conventions. They are just guidelines that you can use to organize your environment variables. Similar to the ones used by Create React App and Next.js.
Expanding variables
SST will also automatically expand variables ($VAR
). For example:
DEFAULT_READ_CAPACITY=5
USERS_TABLE_READ_CAPACITY=$DEFAULT_READ_CAPACITY
POSTS_TABLE_READ_CAPACITY=$DEFAULT_READ_CAPACITY
If you are trying to use a variable with a $
in the actual value, it needs to be escaped, \$
.
NAME=Spongebob
# becomes "Hi Spongebob"
GREETING=Hi $NAME
# becomes "Hi $NAME"
GREETING=Hi \$NAME
Other environment variables
The .env
environment variables will not modify an environment variable that has been previously set. So if you run the following:
NAME=Spongebob
npx sst deploy
While your .env
has.
NAME=Patrick
The .env
value will be ignored and process.env.NAME
will be set to Spongebob
.
Environment variables in Seed
The above idea also applies to environment variables that are set in Seed or other CIs. If you have an environment variable set in Seed, it'll override the one you have set in your .env
files.
Environment variables in Lambda functions
The .env
environment variables are only available in your infrastructure code.
You can also set them as Lambda environment variables by including them in the Function environment
prop:
new Function(stack, "MyFunction", {
handler: "src/api.main",
environment: {
MY_ENV_VAR: process.env.MY_ENV_VAR,
},
});
Or you can use the App's setDefaultFunctionProps
method to set it for all the functions in your app.
export default function main(app) {
app.setDefaultFunctionProps({
environment: { MY_ENV_VAR: process.env.MY_ENV_VAR },
});
new MySampleStack(app, "sample");
}
FAQ
Here are some frequently asked questions about Config
.
How much does it cost to use Config
?
Secrets and Parameters are stored in AWS SSM as Standard parameters by default. This makes Config
free to use in your SST apps. However when storing a Config.Secret
the value is encrypted by AWS KMS. These are retrieved at runtime in your Lambda functions when it starts up. AWS KMS has a free tier of 20,000 API calls per month. And it costs $0.03 for every 10,000 subsequent API calls. This is worth keeping in mind as these secrets are fetched per Lambda function cold start.
Note that Standard parameters have a content size limit of 4KB. If your secrets exceed this size, they will be stored as Advanced parameters, which can store up to 8KB. Each advanced parameter costs roughly $0.05 per month.
Should I use Config.Secret
or .env
for secrets?
Although SST supports managing secrets using .env
files, it's not recommended. Here are a couple of reasons why.
Let's take the example of a Stripe secret key. Using the .env
way, you would create a .env.local
file on your local.
STRIPE_KEY=sk_test_abc123
Since the .env.local
file is not committed to git, every team member working on the app would need to create a similar .env.local
file. And they'll need to bug you to get the value.
If you want to deploy the app through your CI pipeline, you'll need to store the STRIPE_KEY
in your CI pipeline. In addition, if you are deploying to multiple stages through your CI pipeline, each stage would need its own STRIPE_KEY
, you'd store both versions of the key (ie. STRIPE_KEY_STAGE_FOO
and STRIPE_KEY_STAGE_BAR
), and pick the one that matches the stage name at deploy time.
All these are made simpler and far more secure, with Config
. You set the secrets centrally for all stages:
npx sst secrets set STRIPE_KEY sk_test_abc123
npx sst secrets set STRIPE_KEY sk_live_xyz456 --stage foo
npx sst secrets set STRIPE_KEY sk_live_xyz789 --stage bar
You can also set a fallback value for ephemeral stages.
npx sst secrets set --fallback STRIPE_KEY sk_test_abc123
At runtime, the functions are going to pick up the correct value based on the stage, whether they are running locally, inside a test, or in production.
Finally, the Config
object in your Lambda function handles errors and is typesafe. So unlike process.env
, Config.STRIPE_KEY
will autocomplete. And an invalid secret like Config.XYZ
will throw a runtime error.