Environment & Configuration

Most Rails applications require over a dozen environment variables to configure themselves or popular gems used within. Most notable is ActiveRecord's DATABASE_URL but others like RAILS_MASTER_KEY or SECRET_KEY_BASE require us to consider how we configure your Lambda.

There are numerous ways to configure environment variables ranging from quick and dirty commits to git (DO NOT DO THIS) all the way to a strict separation of config from code using countless methods to achieve a proper Twelve-Factor app. This document speaks to two we think fit nicely with AWS and Lambda. But we would love to hear about other options from you.

⚠️ If your application uses other AWS resources like S3, you may be using environment variables like AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. Avoid this pattern. Instead please add explicit IAM policies within your template.yaml file. They will be attached to your Lambda's Execution Role and inherently give your Lambda the needed permissions.

AWS SMS Parameter Store & Dotenv

AWS Systems Manager Parameter Store provides secure, hierarchical storage for configuration data management and secrets management and is offered at no additional charge.

To say Parameter Store is versatile is an understatement. It can be used in CloudFormation templates to the CLI and has unlimited ways to configure IAM for data access. Both Lamby and this guide below follow AWS guides-lines for Organizing Parameters into Hierarchies and a technique called Labeling Parameters. If Parameter store is new to you, please take some time to read up on it afterward.

Lamby supports a few ways to integrate with Parameter Store, but we suggest using our built-in tooling with Dotenv. The end result will write out a .env.#{RAILS_ENV} as part of your bin/build script to ensure all configuration is included in your Lambda package.

Updating Your Project's Files

In your Gemfile add both Dotenv and the AWS SDK for SMS.

gem 'dotenv-rails'
gem 'aws-sdk-ssm'

In your Rails config/application.rb file, add Dotenv's rails-now require right after rails is required. This allows Dotenv to work for local development and testing.

require_relative 'boot'

require 'rails'
require 'dotenv/rails-now'
# ...

To ensure Dotenv works under the Lambda execution environment, change your Lamby installed app.rb file to require and load Dotenv after the boot require and before requiring Lamby.

require_relative 'config/boot'
require 'dotenv' ; Dotenv.load ".env.#{ENV['RAILS_ENV']}"
require 'lamby'
require_relative 'config/application'
require_relative 'config/environment'
# ...

Add this to your bin/build script in the hook/section for Environments & Configuration. Replace myapp with the name of your Rails application or completely customize the path to match your own hierarchal structure.

# [HOOK] Environments & Configuration
./bin/bundle
./bin/rake -rlamby lamby:ssm:dotenv \
  LAMBY_SSM_PARAMS_PATH="/config/${RAILS_ENV}/myapp/env" \
  LAMBY_SSM_PARAMS_LABEL="live"
mv ".env.${RAILS_ENV}" "./.aws-sam/build/RailsFunction/"

About Lamby's SSM Support

The above lamby:ssm:dotenv Rake task is made possible with our Lamby:: SsmParameterStore class. But to understand how the above code works, we first need to create a few SSM Parameters. We can use the AWS CLI for that. Here is what you would do for a new Rails app.

# New Rails v6.0
$ aws ssm put-parameter \
  --name "/config/my_awesome_lambda/env/RAILS_MASTER_KEY" \
  --type "SecureString" \
  --value $(cat config/master.key)

# New Rails v5.2
aws ssm put-parameter \
  --name "/config/production/myapp/env/SECRET_KEY_BASE" \
  --type "SecureString" \
  --value $(rails secret)

The name option is an example hierarchy, you can come up with your own. But here is how we have structured ours.

Again, feel free to come up with your own path hierarchies that make sense for you or your organizations structure and IAM policies. Whatever is found under this path, set by the LAMBY_SSM_PARAMS_PATH var in the script, will be converted to entries in your Dotenv file.

Labeling

Parameters have a history. The last version created is found by default. However, if you want to take advantage of labels and for example use a live label migrate from one env to another in a controlled way, use the LAMBY_SSM_PARAMS_LABEL variable.

./bin/rake -rlamby lamby:ssm:dotenv \
  LAMBY_SSM_PARAMS_PATH="/config/${RAILS_ENV}/myapp/env" \
  LAMBY_SSM_PARAMS_LABEL="live"

Advanced Usage

The Lamby:: SsmParameterStore can be used at runtime too when your Lambda starts, assuming you have written out the needed Policies in your template.yaml file. This example below will write directly to the ENV after loading all your parameters.

path = "/config/#{RAILS_ENV}/myapp/env"
envs = Lamby::SsmParameterStore path, label: 'live'
envs.to_env

Rails Credentials

If you are using Rails Credentials you will need to set the RAILS_MASTER_KEY environment variable in your app.rb file. All other configurations will then be Rails.application.credentials usage within your application. To keep the master key out of source control, we are going to tuck it into AWS Systems Manager Parameter Store again. However there are other system like AWS Secrets Manager too.

$ aws ssm put-parameter \
  --name "/config/myapp/env/RAILS_MASTER_KEY" \
  --type "SecureString" \
  --value $(cat config/master.key)

Now to use this in Lambda, use our SSM Parameter Store wrapper to get the value and set the ENV variable in your app.rb file right after the require 'lamby' line.

ENV['RAILS_MASTER_KEY'] =
  Lamby::SsmParameterStore.get!('/config/myapp/env/RAILS_MASTER_KEY')

Finally, update your template.yaml CloudFormation/SAM file by adding this to the Properties section of your RailsFunction. This addition allows your Lambda's runtime policy to read configs from SSM Parameter store.

Policies:
  - Version: "2012-10-17"
    Statement:
      - Effect: Allow
        Action:
          - ssm:GetParameter
          - ssm:GetParameters
          - ssm:GetParametersByPath
          - ssm:GetParameterHistory
        Resource:
          - !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/config/myapp/*
Lamby 🆕 Application Load Balancer ALB Support     GitHub