env.dev

Environment Variables in Ruby: ENV, dotenv & Rails Credentials

Read env vars in Ruby with ENV.fetch, load .env files with the dotenv gem (the original of every dotenv port), and pick between dotenv-rails and Rails encrypted credentials.

Last updated:

Ruby exposes environment variables through the ENV global, a hash-like object whose keys and values are always strings. Read with ENV['KEY'] for nullable access or ENV.fetch('KEY') when a missing value should crash the process. The dotenv gem — released by Brandon Keepers in 2012 and the original of every dotenv port across languages — is the universal way to load .env files; in Rails, drop in dotenv-rails and it auto-loads on boot. Rails 5.2+ also ships credentials.yml.enc, which is the right choice for production secrets when you control deployment. This guide covers all three plus the Rails-specific bootstrap order that catches every new contributor.

How do you read an environment variable in Ruby?

ENV behaves like a frozen-keys hash whose values are always strings. Bracket access returns nil for unset keys; the fetch method raises KeyErrorunless a default is supplied — use it for required variables so failure surfaces immediately.

ruby
# Nullable access
db_url = ENV['DATABASE_URL']         # nil if unset

# With a default
port = ENV.fetch('PORT', '3000').to_i

# Required — raises KeyError if missing
api_key = ENV.fetch('API_KEY')

# Iterate
ENV.each { |k, v| puts "#{k}=#{v}" }

# Test for presence without reading the value
ENV.key?('DATABASE_URL')

All values are strings, even numeric ones. .to_i coerces to integer ("returning 0 if the string isn't numeric, no exception"), .to_f to float, and %w-style splitting handles list-shaped values.

How do you set or unset variables at runtime?

Ruby lets you mutate ENV from inside the process — unlike Java, but like Node.js or Python. The mutation is visible to child processes you spawn afterwards but does not propagate back to your shell:

ruby
ENV['MY_VAR'] = 'value'
ENV.delete('MY_VAR')
ENV.update('PORT' => '4000', 'LOG_LEVEL' => 'debug')

# Spawn a subprocess that inherits the current ENV
system('echo $MY_VAR')

How do you load a .env file with the dotenv gem?

Brandon Keepers' dotenv gem is the original .env loader and the one every other ecosystem ported. It does not override variables that already exist in the shell, which means real environment variables always win — the right default for "local file fills in the blanks":

ruby
# Gemfile
gem 'dotenv'

# Plain Ruby — load before reading any env vars
require 'dotenv/load'

# Or with explicit paths and override semantics
require 'dotenv'
Dotenv.load('.env.development.local', '.env.development', '.env')
# Earlier files take precedence; .env is the catch-all

For a complete reference on .env file syntax, quoting, and multi-line values, see the .env file guide.

How does dotenv-rails fit into Rails boot?

In Rails, add dotenv-rails instead of plain dotenv. It hooks into the Rails initializer chain and loads files in this order: .env.<env>.local, .env.local (skipped in test), .env.<env>, then .env. Earlier files take precedence.

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

The footgun: dotenv-rails loads after application.rb by default, so any code in config/application.rb that reads ENV sees pre-dotenv values. Use Dotenv::Railtie.load at the top of application.rb to force the early load:

ruby
# config/application.rb
require_relative 'boot'
require 'rails/all'
Bundler.require(*Rails.groups)

Dotenv::Railtie.load   # ← force early load, before module Application

module MyApp
  class Application < Rails::Application
    # config.x.api_key now sees the .env value
    config.x.api_key = ENV.fetch('API_KEY')
  end
end

When should you use Rails credentials instead?

Rails 5.2 introduced encrypted credentials (config/credentials.yml.enc + a master.key), and Rails 6 added per-environment credentials files. For production secrets that you control end-to-end and ship in the repo, this is usually the better choice than environment variables — the encrypted file is committed, and only the master.key needs to live outside version control:

bash
bin/rails credentials:edit                       # default credentials
bin/rails credentials:edit --environment production
ruby
# In code, never via ENV
Rails.application.credentials.dig(:aws, :access_key_id)
Rails.application.credentials.stripe!.secret_key  # raises if missing

Use environment variables for things that legitimately differ per deployment (database URLs, log levels) and credentials for secrets bundled with the application.

Practical patterns and gotchas

  • Always strings. ENV['DEBUG'] = true stores the literal string "true". Boolean parsing in Ruby has the same trap as Python: !!"false" is true because the string is non-empty.
  • Use ENV.fetch for required variables. The KeyError it raises tells you exactly which variable is missing, with a stack trace that points to the call site — much better than a downstream NoMethodError on nil:NilClass.
  • .env.local for personal overrides, committed .env with placeholders for shared defaults. The standard .gitignore rule is to ignore .env and .env.* except .env.example.
  • Don't commit dotenv-rails to production. Put it in the development, test group of your Gemfile; production should source variables from the platform (Heroku config, Kubernetes Secrets, AWS Parameter Store).
  • Generate or validate your .env files with the .env builder and the .env validator.

For language-agnostic security and validation rules, see environment variable best practices.

Was this helpful?

Read next

Node.js Env Variables: process.env, dotenv & --env-file

How to use environment variables in Node.js: process.env, dotenv, the Node 20.6+ --env-file flag, NODE_ENV, type-safe validation with zod.

Continue →

Frequently Asked Questions

Should I use ENV[] or ENV.fetch?

Use ENV.fetch for required variables — it raises KeyError with the missing name if the variable is unset, which fails fast and points right at the problem. Use ENV[] for optional values that legitimately default to nil.

When should I use Rails credentials instead of env vars?

For production secrets you control end-to-end and ship in the repo. credentials.yml.enc + a master.key gives you encryption at rest and a single secret to manage. Use env vars for things that legitimately differ per deployment (DB URLs, log levels).

Why does my .env value not show up in config/application.rb?

dotenv-rails loads after application.rb by default. Add `Dotenv::Railtie.load` at the top of application.rb to force the load early, before the module Application block reads ENV.

Are ENV values always strings?

Yes. ENV behaves like a frozen-keys hash whose values are always strings. Coerce explicitly with .to_i / .to_f. Be careful with booleans: !!"false" is true because the string is non-empty.

Stay up to date

Get notified about new guides, tools, and cheatsheets.