KeyEnvKeyEnv
SDKs & Integrations

Rails Integration

Use KeyEnv with Ruby on Rails applications for secure secrets management.

Rails Integration

Integrate KeyEnv with your Ruby on Rails application to securely manage secrets like database credentials, API keys, and Rails secret key base.

Features

  • Load secrets before Rails initializes
  • Environment-aware configuration (development, test, production)
  • Works with Rails credentials and encrypted secrets
  • Supports database.yml, storage.yml, and other config files
  • Built-in caching for serverless environments

Installation

Add the KeyEnv gem to your Gemfile:

# Gemfile
gem 'keyenv'

Then install:

bundle install

Quick Start

Create an initializer to load secrets at boot:

# config/initializers/keyenv.rb
if ENV['KEYENV_TOKEN'].present?
  require 'keyenv'

  client = KeyEnv.new(token: ENV['KEYENV_TOKEN'])
  client.load_env(
    project_id: ENV['KEYENV_PROJECT'],
    environment: Rails.env
  )
end

Now use secrets in your configuration:

# config/application.rb
config.secret_key_base = ENV['SECRET_KEY_BASE']

Rails Initializer Setup

Basic Initializer

The simplest approach loads all secrets into ENV at startup:

# config/initializers/keyenv.rb
if ENV['KEYENV_TOKEN'].present?
  require 'keyenv'

  # Map Rails.env to KeyEnv environment
  keyenv_environment = case Rails.env
                       when 'development' then 'development'
                       when 'test' then 'development'
                       when 'staging' then 'staging'
                       when 'production' then 'production'
                       else Rails.env
                       end

  client = KeyEnv.new(token: ENV['KEYENV_TOKEN'])
  count = client.load_env(
    project_id: ENV['KEYENV_PROJECT'],
    environment: keyenv_environment
  )

  Rails.logger.info "KeyEnv: Loaded #{count} secrets for #{keyenv_environment}" if defined?(Rails.logger)
end

With Caching (Serverless)

For serverless environments like AWS Lambda, enable caching:

# config/initializers/keyenv.rb
if ENV['KEYENV_TOKEN'].present?
  require 'keyenv'

  client = KeyEnv.new(
    token: ENV['KEYENV_TOKEN'],
    cache_ttl: 300  # Cache for 5 minutes
  )

  client.load_env(
    project_id: ENV['KEYENV_PROJECT'],
    environment: Rails.env
  )
end

Early Loading with boot.rb

For secrets needed before initializers run (like database credentials), load in boot.rb:

# config/boot.rb
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)

require 'bundler/setup'
require 'bootsnap/setup' if defined?(Bootsnap)

# Load KeyEnv secrets before Rails initializes
if ENV['KEYENV_TOKEN'].present?
  require 'keyenv'

  rails_env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
  client = KeyEnv.new(token: ENV['KEYENV_TOKEN'])
  client.load_env(
    project_id: ENV['KEYENV_PROJECT'],
    environment: rails_env
  )
end

Environment-Aware Configuration

Automatic Environment Detection

Map Rails environments to KeyEnv environments:

# config/initializers/keyenv.rb
if ENV['KEYENV_TOKEN'].present?
  require 'keyenv'

  def detect_keyenv_environment
    # Check explicit override first
    return ENV['KEYENV_ENV'] if ENV['KEYENV_ENV'].present?

    # Map Rails.env to KeyEnv environment
    case Rails.env
    when 'development', 'test'
      'development'
    when 'staging', 'review'
      'staging'
    when 'production'
      'production'
    else
      Rails.env
    end
  end

  client = KeyEnv.new(token: ENV['KEYENV_TOKEN'])
  client.load_env(
    project_id: ENV['KEYENV_PROJECT'],
    environment: detect_keyenv_environment
  )
end

Platform-Specific Detection

Detect environment from deployment platforms:

# config/initializers/keyenv.rb
def detect_keyenv_environment
  return ENV['KEYENV_ENV'] if ENV['KEYENV_ENV'].present?

  # Heroku
  if ENV['HEROKU_APP_NAME'].present?
    return ENV['HEROKU_APP_NAME'].include?('staging') ? 'staging' : 'production'
  end

  # Render
  return 'production' if ENV['RENDER'].present?

  # Railway
  return ENV['RAILWAY_ENVIRONMENT'] if ENV['RAILWAY_ENVIRONMENT'].present?

  # Fly.io
  return 'production' if ENV['FLY_APP_NAME'].present?

  # Fall back to Rails.env mapping
  case Rails.env
  when 'development', 'test' then 'development'
  when 'staging' then 'staging'
  else 'production'
  end
end

Database Configuration

Using database.yml with ERB

Reference secrets in database.yml:

# config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("DB_POOL", 5) %>

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test

production:
  <<: *default
  url: <%= ENV['DATABASE_URL'] %>

Store in KeyEnv:

DATABASE_URL=postgres://user:password@host:5432/myapp_production
DB_POOL=25

Individual Database Credentials

For more control, use individual variables:

# config/database.yml
production:
  adapter: postgresql
  encoding: unicode
  host: <%= ENV['DB_HOST'] %>
  port: <%= ENV.fetch('DB_PORT', 5432) %>
  database: <%= ENV['DB_NAME'] %>
  username: <%= ENV['DB_USER'] %>
  password: <%= ENV['DB_PASSWORD'] %>
  pool: <%= ENV.fetch('DB_POOL', 5) %>

Store in KeyEnv:

DB_HOST=your-db-host.amazonaws.com
DB_PORT=5432
DB_NAME=myapp_production
DB_USER=myapp
DB_PASSWORD=secure-password

Storage Configuration

Active Storage with S3

# config/storage.yml
amazon:
  service: S3
  access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
  secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
  region: <%= ENV.fetch('AWS_REGION', 'us-east-1') %>
  bucket: <%= ENV['AWS_S3_BUCKET'] %>

Store in KeyEnv:

AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=secret...
AWS_REGION=us-east-1
AWS_S3_BUCKET=myapp-production

Active Storage with Google Cloud

# config/storage.yml
google:
  service: GCS
  project: <%= ENV['GCS_PROJECT'] %>
  credentials: <%= ENV['GCS_CREDENTIALS'] %>
  bucket: <%= ENV['GCS_BUCKET'] %>

Rails Credentials Integration

Hybrid Approach

Use KeyEnv for deployment secrets and Rails credentials for development:

# config/initializers/keyenv.rb
if ENV['KEYENV_TOKEN'].present?
  require 'keyenv'

  client = KeyEnv.new(token: ENV['KEYENV_TOKEN'])
  client.load_env(
    project_id: ENV['KEYENV_PROJECT'],
    environment: Rails.env
  )
else
  # Fall back to Rails credentials in development
  Rails.logger.info 'KeyEnv: Using Rails credentials (KEYENV_TOKEN not set)'
end

Accessing Secrets

Create a helper that checks ENV first, then falls back to credentials:

# config/initializers/secrets.rb
module Secrets
  class << self
    def fetch(key, default = nil)
      ENV[key.to_s.upcase] || Rails.application.credentials.dig(key) || default
    end

    def [](key)
      fetch(key)
    end
  end
end

Usage:

Secrets.fetch(:stripe_secret_key)
Secrets[:aws_access_key_id]

Configuration Patterns

Action Mailer

# config/environments/production.rb
config.action_mailer.smtp_settings = {
  address: ENV.fetch('SMTP_HOST', 'smtp.gmail.com'),
  port: ENV.fetch('SMTP_PORT', 587).to_i,
  user_name: ENV['SMTP_USERNAME'],
  password: ENV['SMTP_PASSWORD'],
  authentication: :plain,
  enable_starttls_auto: true
}

Redis / Sidekiq

# config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
  config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/0') }
end

Sidekiq.configure_client do |config|
  config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/0') }
end

Action Cable

# config/cable.yml
production:
  adapter: redis
  url: <%= ENV.fetch('REDIS_URL', 'redis://localhost:6379/1') %>
  channel_prefix: myapp_production

Complete Example

Here's a complete Rails configuration using KeyEnv:

# config/initializers/keyenv.rb
if ENV['KEYENV_TOKEN'].present?
  require 'keyenv'

  KEYENV_ENV_MAP = {
    'development' => 'development',
    'test' => 'development',
    'staging' => 'staging',
    'production' => 'production'
  }.freeze

  keyenv_env = ENV.fetch('KEYENV_ENV') { KEYENV_ENV_MAP.fetch(Rails.env, Rails.env) }

  begin
    client = KeyEnv.new(
      token: ENV['KEYENV_TOKEN'],
      timeout: 10,
      cache_ttl: Rails.env.production? ? 300 : 0
    )

    count = client.load_env(
      project_id: ENV['KEYENV_PROJECT'],
      environment: keyenv_env
    )

    Rails.logger.info "KeyEnv: Loaded #{count} secrets for #{keyenv_env}" if defined?(Rails.logger)
  rescue KeyEnv::Error => e
    Rails.logger.error "KeyEnv: Failed to load secrets - #{e.message}" if defined?(Rails.logger)
    raise if Rails.env.production?  # Fail hard in production
  end
end
# config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("DB_POOL", 5) %>

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test

production:
  <<: *default
  url: <%= ENV['DATABASE_URL'] %>
# config/environments/production.rb
Rails.application.configure do
  # Secret key base
  config.secret_key_base = ENV['SECRET_KEY_BASE']

  # Force SSL
  config.force_ssl = true

  # Action Mailer
  config.action_mailer.smtp_settings = {
    address: ENV['SMTP_HOST'],
    port: ENV.fetch('SMTP_PORT', 587).to_i,
    user_name: ENV['SMTP_USERNAME'],
    password: ENV['SMTP_PASSWORD'],
    authentication: :plain,
    enable_starttls_auto: true
  }

  # Logging
  config.log_level = ENV.fetch('LOG_LEVEL', 'info').to_sym
end
# config/initializers/stripe.rb
Stripe.api_key = ENV['STRIPE_SECRET_KEY']
# config/initializers/sentry.rb
if ENV['SENTRY_DSN'].present?
  Sentry.init do |config|
    config.dsn = ENV['SENTRY_DSN']
    config.environment = Rails.env
    config.traces_sample_rate = 0.1
  end
end

Setting Up Your Deployment

Required Environment Variables

Set these in your deployment platform (not in KeyEnv):

VariableDescription
KEYENV_TOKENYour KeyEnv service token
KEYENV_PROJECTYour KeyEnv project ID
KEYENV_ENVOverride environment detection (optional)

Secrets to Store in KeyEnv

Store these secrets in KeyEnv for each environment:

SecretDescriptionExample
SECRET_KEY_BASERails secret keyrails secret output
DATABASE_URLDatabase connectionpostgres://user:pass@host/db
REDIS_URLRedis connectionredis://localhost:6379/0
SMTP_PASSWORDEmail passwordapp-specific-password
AWS_ACCESS_KEY_IDAWS credentialsAKIA...
AWS_SECRET_ACCESS_KEYAWS secret...
STRIPE_SECRET_KEYStripe API keysk_live_...
SENTRY_DSNSentry DSNhttps://[email protected]/...

Generate a Rails Secret Key

rails secret

Local Development

Without KeyEnv

For local development without KeyEnv, the initializer gracefully skips loading:

# config/initializers/keyenv.rb
if ENV['KEYENV_TOKEN'].present?
  # ... KeyEnv loading
end

# Falls back to local environment variables or Rails credentials

Using the CLI

Generate a .env file for local development:

keyenv pull --env development --output .env

Then use dotenv to load it:

# Gemfile (development group)
group :development, :test do
  gem 'dotenv-rails'
end

The dotenv gem loads .env automatically before Rails initializers.

Testing

Test Environment Setup

Skip KeyEnv in tests by not setting KEYENV_TOKEN:

# spec/rails_helper.rb
# Test environment variables
ENV['SECRET_KEY_BASE'] = 'test-secret-key-base'
ENV['DATABASE_URL'] = 'postgres://localhost/myapp_test'

Or use a separate test environment in KeyEnv:

# config/initializers/keyenv.rb
keyenv_env = case Rails.env
             when 'test' then 'test'  # Use KeyEnv test environment
             # ...
             end

RSpec Configuration

# spec/support/keyenv.rb
RSpec.configure do |config|
  config.before(:suite) do
    # Ensure test secrets are available
    ENV['SECRET_KEY_BASE'] ||= 'test-secret'
    ENV['STRIPE_SECRET_KEY'] ||= 'sk_test_fake'
  end
end

Troubleshooting

"KEYENV_TOKEN not set"

In production, ensure KEYENV_TOKEN is set in your deployment platform's environment variables.

"Project not found"

Verify KEYENV_PROJECT matches your project ID in the KeyEnv dashboard.

"Environment not found"

Check that the environment name exists in your KeyEnv project and matches your Rails environment mapping.

Secrets not loading in database.yml

If database credentials aren't available when Rails initializes:

  1. Move KeyEnv loading to config/boot.rb (before database.yml is processed)
  2. Or use DATABASE_URL which Rails handles specially

Debugging

Enable debug logging:

# config/initializers/keyenv.rb
if ENV['KEYENV_TOKEN'].present?
  require 'keyenv'

  client = KeyEnv.new(token: ENV['KEYENV_TOKEN'])

  begin
    secrets = client.export_secrets(
      project_id: ENV['KEYENV_PROJECT'],
      environment: Rails.env
    )
    Rails.logger.debug "KeyEnv: Found #{secrets.count} secrets"
    secrets.each { |s| Rails.logger.debug "KeyEnv: - #{s.key}" }
    client.load_env(project_id: ENV['KEYENV_PROJECT'], environment: Rails.env)
  rescue KeyEnv::Error => e
    Rails.logger.error "KeyEnv: #{e.class} - #{e.message}"
    raise
  end
end

Performance

load_env makes a single API call at startup. Secrets are cached in ENV for the lifetime of the process. There's no runtime overhead after initialization.

For serverless environments, use cache_ttl to avoid repeated API calls on warm starts.

On this page