Part of an series while developing, Lamby - Simple Rails & AWS Lambda Integration using Rack đđ.
Most Rails applications require over a dozen environment variables to configure themselves or use popular gems. Most notable is the DATABASE_URL
or others like SECRET_KEY_BASE
which is used by Rails itself to sign encrypted cookies for sessions.
There are numerous ways to configure environment variables ranging from quick and dirty commits to GitHub 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 one we think fits nicely with AWS and Lambda. But first, some alternatives:
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 Lamby::SsmParameterStore
object with Dotenv. The end result below will write out a .env.RAILS_ENV
file within your deploy script and ensure it is included in your Lambda package.
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 ensures Dotenv works when using `rails server.
require_relative 'boot'
require "rails"
require 'dotenv/rails-now'
# ...
Change your Lamby app.rb
to require and load Dotenv after the boot require and before requiring Lamby. This will load your environment variables when your function is deployed or using SAM's development server locally.
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/deploy
script right before the sam package
lines. Replace myapp
with the name of your app or completely customize the path to match your own hierarchal structure.
./bin/rake lamby:ssm:dotenv \
LAMBY_SSM_PARAMS_PATH="/config/${RAILS_ENV}/myapp/env"
mv ".env.${RAILS_ENV}" ./.aws-sam/build/RailsFunction
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 we did for the Lamby Demo app's SECRET_KEY_BASE
env var.
aws ssm put-parameter \
--name "/config/production/myapp/env/SECRET_KEY_BASE" \
--type "SecureString" \
--value $(rails secret)
The name
option is our path and is an example hierarchy, you can come up with your own. But here is how we have structured ours.
/config
path establishes that all our application's config will be under this path./production
component can change for each RAILS_ENV
./env
path sets down that everything in this path is for environment variables.Again, feel free to come up with your own path hierarchies. Whatever is found in the path you pass via LAMBY_SSM_PARAMS_PATH
will be converted to entries in your Dotenv file.
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 lamby:ssm:dotenv \
LAMBY_SSM_PARAMS_PATH="/config/${RAILS_ENV}/myapp/env" \
LAMBY_SSM_PARAMS_LABEL="live"
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