In a previous post I wrote about using per stage environment variables with the Serverless Framework. That article showed how you could set different values for environment variables depending on the stage you were deploying to.
There still seems to be a lot of misunderstaning about how powerful the environment variable implementation in Serverless is. Today I want to show you how to keep your secrets out of version control.
Before I show you how it's important to understand why. Secrets like API keys, database passwords, etc should never be committed into version control. There are two main reasons for this.
- Once they're in version control they're available to everyone who has access to version control. In many environments you don't want to provide developers with access to production systems.
- It forces everyone and every environment to share the same credentials. Typically you want developers to build in their own environment and your staging/production environments will have their own credentials.
There's a really simple way to handle this with Serverless.
It all starts by creating a secrets.yml
file to hold your secrets for each environment. I've included an example file below.
default: &default
<<: *default
COMMON_API_KEY: "AN API KEY COMMON TO ALL ENVIRONMENTS"
COMMON_API_SECRET: "AN API KEY COMMON TO ALL ENVIRONMENTS"
dev:
<<: *default
API_KEY: "YOUR DEVELOPMENT API KEY"
API_SECRET: "YOUR DEVELOPMENT API SECRET"
stage:
<<: *default
API_KEY: "YOUR STAGING API KEY"
API_SECRET: "YOUR STAGING API SECRET"
prod:
<<: *default
API_KEY: "YOUR PRODUCTION API KEY"
API_SECRET: "YOUR PRODUCTION API SECRET"
The default
section at the beginning sets default values that will be used by all environment. For example - you may be using an external service to lookup IP addresses and you only have one key that is used by all environments.
Below that is one section for each stage. The name should match the stage you're deploying to. Each of these environments inherits values from default
because of the line <<: *default
. You can override any values set in default
by setting a different value for that stage.
Now we have a secrets.yml
file we don't want it commited to version control or uploaded to AWS with our code.
Start by adding secrets.yml
to your .gitignore
file.
echo "secrets.yml" >> .gitignore
You can check that this is working by running git status
and if the file doesn't show up you're good to go.
Next you want to add secrets.yml
to the list of files excluded from your package that is uploaded to Lambda. You do this inside your serverless.yml
.
package:
exclude:
- secrets.yml
Finally you need to include the secrets.yml
file in your serverless.yml
file. I've been doing this in the custom
section by using.
custom:
stage: ${opt:stage, self:provider.stage}
secrets: ${file(secrets.yml):${self:custom.stage}}
This allows you to set your environment varilables to use the values imported from the secrets.yml
. Below is an example.
provider:
environment:
API_KEY: "${self:custom.secrets.API_KEY}"
API_SECRET: "${self:custom.secrets.API_SECRET}"
COMMON_API_KEY: "${self:custom.secrets.COMMON_API_KEY}"
COMMON_API_SECRET: "${self:custom.secrets.COMMON_API_SECRET}"
When you deploy your application using Serverless it will now load your secrets from secrets.yml
that won't be commited to version or uploaded to Lambda.
To make it easy for new developers I tend to maintain a secrets-example.yml
file that has all of the keys required by secrets.yml
with dummy values. This makes it easy for them to create their own secrets.yml
and tells them which secrets they need to acquire.
PS: There's going to be one more part to this series explaining how to combine env.yml and secrets.yml. Join my mailing list today to get updates when that's released.