Use AWS Secrets Manager references in your Ruby dotenv files. A .env value that
begins with aws-sm: is resolved from AWS Secrets Manager at process boot and
written into ENV in place.
# Gemfile — require AFTER dotenv-rails so ENV is populated first
gem "dotenv-rails"
gem "dotenv-secretsmanager"bundle install# whole plaintext secret
RAILS_MASTER_KEY=aws-sm:myproject/master-key
# one key from a JSON secret (all three share a single API call)
DB_PASSWORD=aws-sm:myproject/prod|db_password
YELP_SECRET=aws-sm:myproject/prod|yelp_client_secret
TWILIO_TOKEN=aws-sm:myproject/prod|twilio_auth_token
# non-reference values are left untouched
RAILS_LOG_LEVEL=info<secret-id> may be a friendly name or a full ARN. The optional |<json-key>
selector is split on the last |, so ARNs (full of colons, never pipes) parse
correctly.
No wiring needed. The railtie resolves references automatically after
dotenv-rails loads and before initializers and database.yml run.
require "dotenv/secretsmanager"
Dotenv::SecretsManager.resolve!(ENV)Dotenv::SecretsManager.configure do |c|
c.on_error = :raise # :raise (default) — aggregate all failures, raise once
# :warn — log each failure, leave literal in ENV
c.logger = nil # defaults to Rails.logger if present, else $stderr
c.client = nil # inject a custom Aws::SecretsManager::Client
endCredentials and region come from the standard AWS SDK credential chain. The gem makes zero AWS calls and builds no client when no references are present.
Set the DOTENV_SECRETSMANAGER_SKIP env var (or configuration.skip) to skip
resolution: no AWS calls and no client constructed. Instead of resolving them,
resolve! removes every ENV key whose value is an aws-sm: reference, so
the net effect is as if those references were never in ENV.
This deletion is deliberate: a raw aws-sm: value is never valid for any
consumer, and a present-but-invalid secret breaks boot. For example, leaving
RAILS_MASTER_KEY="aws-sm:..." in ENV makes Rails credentials decryption fail
with ArgumentError: key must be 16 bytes, whereas an absent RAILS_MASTER_KEY
is tolerated. Non-reference inline config (e.g. DEFAULT_URL_HOST) is left
intact — the build still wants those values.
DOTENV_SECRETSMANAGER_SKIP=trueDotenv::SecretsManager.configure { |c| c.skip = true }- The env var is truthy when it is
1,true,yes, oron(case-insensitive; surrounding whitespace is ignored). Anything else —"",0,false,no, or unset — does not by itself skip. - Either source skips: a truthy env var or
configuration.skip == true. The config flag skips regardless of the env var. - The env var is read at call time (when the railtie fires), so it is the right knob for build-time use.
The primary use case is an image build that boots the app — for example a Rails
assets:precompile step in a Docker build — where there is no AWS region or
credentials and no secrets are needed. Without skipping, constructing the AWS
client raises (e.g. Aws::Errors::MissingRegionError) and fails the build. Set
DOTENV_SECRETSMANAGER_SKIP=true on that step only. Non-secret .env
config still loads normally; only secrets resolution is skipped.
Set only AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_REGION as
plaintext deployment env vars (a least-privilege IAM user with
secretsmanager:GetSecretValue scoped to your secrets). Put everything else in
.env.production as aws-sm: references. Keep .env.development free of
references so local development needs no AWS access.