Do you currently use a serverless configuration for any applications or interested in migrating a current application to use serverless? Have you wondered how you can easily configure your CI/CD pipeline to easily deploy to multiple environments such as development
, staging
, and all production
environments? With the Serverless Framework you can do just that. Continue reading to find out how.
Intro To Serverless Framework
Currently, I use the Serverless Framework to deploy to multiple environments and regions on the AWS cloud. I deploy to our development
, staging
, and production
environments. Our development and staging environments are deployed in 2 different regions. Our production environment is deployed to 4 regions. So I have a serverless.yml
file with many properties found in the docs for serverless.yml
found here.
Configuration
- All properties that remain the same for all environments and/or regions go into the
serverless.yml
base file - For all properties that are different between each environment such as
development
,staging
, andproduction
, I have another config file with the following naming convention under myconfig/
directory (the same naming I have is not required, but just what i use): config/config.dev.yml
,config/config.stg.yml
,config/config.prod.yml
, etc
Following this guide, I add all the needed custom properties to those environment-specific config files, and then in the main serverless.yml file, I create the same property but then reference the property from the environment/stage specified on the serverless deploy -s {stage}CLI command. I use a combination of both the stage CLI parameter and setting an ENV variable in a bash script before calling serverless deploy. Then I can also use ${env:ENV}
to reference that value inside serverless.yml
.
Environment Specific Configurations
Here’s an example of an environment-specific YAML configuration file I have for environment-specific variables:
stageSuffix: -${self:provider.stackSuffix, 'dev'}
lambda:
versioning: false
prune:
automatic: ${self:lambda.versioning}
number: 5
api:
domain: api.dev.${self:custom.domain}
storage:
bucket: dev.${self:provider.region}.${self:custom.domain}
slackChannel: 'dev-slack-channel'
And inside serverless.yml
, I reference those values by doing the following:
environment:
ENV: ${env:ENV, 'dev'}
STAGE: ${opt:stage, 'dev'}
REGION: ${opt:region, 'us-east-1'}
SLACK_CHANNEL: ${file(./config/config.${self:provider.stage}.yml):slackChannel}
Also, using a lot of shell environment-based variables allows you to make your serverless config even more decoupled away from specific projects/microservices since you then can just create a .env
file specific to each project, create a bash script to run your serverless deploy
commands, and before running serverless deploy
, run source .env
and many values can be populated easily by just supplying your .env
file
Lastly, here’s an example of the bash script we use to run our serverless deploy
commands that will supply many of our values in serverless.yml
with values from a .env
file instead of manually finding and replacing all the values between all our different microservices:
set -evif [[ -e "./secret.env" ]]; then
source "./secret.env"
fi
if [[ -e "./.env" ]]; then
source "./.env"
fiBRANCH="${CIRCLE_BRANCH:-$BRANCH}"if [[ $# -gt 0 ]]; then
REGION="$1"
fiif [[ -n "$REGION" ]]; then
REGION="$REGION"
else
REGION="us-east-1"
fiif [[ "$BRANCH" == "development" ]] || [[ "$BRANCH" =~ ^issue-* ]]; then
ENV="dev"
elif [[ "$BRANCH" == "staging" ]]; then
ENV="stg"
elif [[ "$BRANCH" == "master" ]]; then
ENV="prod"
fiif [[ "$BRANCH" =~ ^issue-* ]]; then
export STAGE="feature"
export STACK_SUFFIX="$BRANCH"
export API_BASE_PATH_SUFFIX="-$BRANCH"
"$(dirname "$0")"/create_domain.sh -s "$STAGE" -r "$REGION"
"$NODE_MODULES_CLI_DIR"/serverless deploy -v -s "$STAGE" -r "$REGION"
else
export STACK_SUFFIX="$ENV"
"$(dirname "$0")"/create_domain.sh -s "$ENV" -r "$REGION"
"$NODE_MODULES_CLI_DIR"/serverless deploy -v -s "$ENV" -r "$REGION"
fi
and the .env
file we use to supply the variable values:
export BRANCH="$(git branch | grep \* | cut -d ' ' -f2)"
export ENV="dev"
export STACK_PREFIX="{STACK_PREFIX_HERE}"
export STACK_SUFFIX="$ENV"
export APP="{APP_ID_HERE}"
export APP_NAME="{APP_NAME_HERE}"
export COMPONENT="{COMPONENT_NAME_HERE}"
export REGION="us-east-1"
export DYNAMODB_ENDPOINT="dynamodb.$REGION.amazonaws.com"
export GITHUB_USERNAME="{GITHUB_USERNAME_HERE}"
export DB_TABLE_NAME="$COMPONENT"
export DOMAIN="{DOMAIN_NAME_HERE}"
export EMAIL_SUPPORT="support@${DOMAIN}"
export NODE_PRESERVE_SYMLINKS=1
export TEST_DEPLOYMENT_SUFFIX="-test"
export SLACK_CHANNEL="slack-channel-${ENV}"
export API_VERSION="v1"
export API_BASE_PATH=""
export API_BASE_PATH_PREFIX=""
export API_BASE_PATH_SUFFIX=""
export NODE_MODULES_CLI_DIR="./node_modules/.bin"
Conclusion
Let me know what you think and if you also see any improvement I myself could make to this process!