Rails environment variables vs credentials.yml.enc
Rails keeps secrets in encrypted credentials opened by a master key, and lets ENV override some of them. When to use which, and how to run Rails with Crunu.
Part of the guide
Ruby on RailsAt a glance
- Env files, highest priority first
- No .env files: values come from the process environment or the platform.
- Public prefix
- None
No names are copied into browser code by a prefix.
- Values applied
- At runtime
Values are read from the environment when the app runs.
- Built-in secret store
- Encrypted credentials: config/credentials.yml.enc opened with a master key
- Crunu command
crunu run --env rails-app/development -- bin/rails serverSee the systemd integration
Common pitfalls
- ENV SECRET_KEY_BASE takes precedence over the value in credentials, so a forgotten variable silently replaces the key.
- Changing secret_key_base expires every active session and can break encrypted cookies and other data derived from it.
- Precompiling assets needs a secret_key_base. Use SECRET_KEY_BASE_DUMMY=1 in the build instead of the real key.
- Whoever ever held the master key can decrypt every version of credentials.yml.enc in Git history, not only the current one.
- Rails 8.1 has no built-in .env loader. ENV holds only what the process was started with.
On this page
Rails reads environment variables through Ruby's ENV, which holds whatever the process was started with. Rails 8.1 doesn't load a .env file on its own. For secrets, Rails has its own store: encrypted credentials in config/credentials.yml.enc, or config/credentials/production.yml.enc per environment, opened with a master key from config/master.key or the RAILS_MASTER_KEY variable. A few settings check ENV first and credentials second, most importantly secret_key_base. All of it is read when the app boots, so new values need a restart. This page follows the Rails security guide and the Rails 8.1 API, checked October 30, 2026, with Rails 8.1.4 current.
Two sources, and which one wins
| Value | Looked up in | Notes |
|---|---|---|
secret_key_base | 1. ENV["SECRET_KEY_BASE"] 2. credentials | in development and test, generated into tmp/local_secret.txt if missing |
| credentials key | 1. ENV["RAILS_MASTER_KEY"] 2. key file | config/credentials/ENV.key, else config/master.key |
| credentials file | 1. config/credentials/ENV.yml.enc 2. config/credentials.yml.enc | ENV here means the Rails environment name |
| your own settings | wherever your code reads them | ENV.fetch or Rails.application.credentials |
There is no file chain like .env.local over .env. If you want one, a gem has to provide it. Everything else is a decision your code makes, one setting at a time.
How credentials work
bin/rails credentials:edit decrypts the credentials file into your editor and encrypts it again when you close it. It creates the file and a master key if neither exists. Add --environment production to edit the production file, which gets its own key. The encrypted file is meant to be committed: the guide says it can be stored in version control as long as the master key is kept safe, and it says in so many words not to commit the master key.
Code reads values through Rails.application.credentials, for example Rails.application.credentials.stripe_secret_key, or a nested path like credentials.aws.access_key_id. The bang form, stripe_secret_key!, raises KeyError when the value is blank, which is the safer choice for anything required.
By default the credentials file contains secret_key_base, the input for the key generator behind every message verifier and encryptor in the app, including signed and encrypted cookies. Generate a new value with bin/rails secret.
What changing secret_key_base breaks
The guide spells it out: changing secret_key_base expires all active sessions and requires every user to log in again, and encrypted or signed cookies and Active Storage files can be affected too. Two consequences follow:
- An environment variable can change it by accident.
ENV["SECRET_KEY_BASE"]beats the credentials value. A variable left over in a server's environment, a container platform's settings or a CI job quietly replaces the key in credentials, and the symptom is that everyone is logged out after a deploy. - Builds don't need the real key.
rails assets:precompileboots the app, so it needs somesecret_key_base. Rails documentsSECRET_KEY_BASE_DUMMY=1for exactly this case, for example in a Dockerfile, which generates a random throwaway key for the build. The real key then only exists where the app runs.
Credentials or environment variables?
Both are reasonable, and the trade-offs are about people more than about Ruby:
| Encrypted credentials | Environment variables | |
|---|---|---|
| Where values live | in the repository, encrypted | outside the repository |
| What a server needs | one key per Rails environment | every value |
| Changing one value | edit, commit, deploy | change the variable, restart |
| Removing a person's access | new master key, re-encrypt, reissue values | depends on the store you use |
| Old versions | stay in Git history | depend on the store |
The fourth and fifth rows are the ones teams underestimate. A master key opens every version of the file that was ever committed with it. When someone who had the production key leaves, generating a new key protects only future versions. Every value they could read is still readable from history and has to be reissued at its provider.
Before you change anything, take stock
A Rails app that has been around for a while usually reads secrets from three places at once: credentials, ENV lookups added later, and gem defaults that read their own variables. Moving any of it is easier after a short inventory:
- List every key in each credentials file, by name only. Note which ones code still reads. Unused keys are values you'd otherwise keep reissuing for nothing.
- Search the code for
ENV[andENV.fetch. Each hit is a variable some server must provide. Compare the list with what your hosts actually set. - Check every place that can set
SECRET_KEY_BASE. Server environments, container settings and CI jobs. If one sets it, that value wins over credentials, and you want to know before a deploy changes it. - Find out who holds each master key. Laptops, CI variables, password managers, the old deploy server. That list is your offboarding scope for credentials.
The result tells you which values belong in credentials, which in the environment, and which can simply be deleted.
Run Rails with values from Crunu
crunu run starts your command with the variables of a Crunu environment in its process environment, so Rails sees them in ENV. There are two ways to combine that with credentials.
Keep credentials, move only the key. Store RAILS_MASTER_KEY for each Rails environment in the matching Crunu environment. The server never has a key file on disk, and Crunu controls who can read the key. Everything about credentials stays the same, including the offboarding caveat above.
Move the values themselves. Read secrets with ENV.fetch("STRIPE_SECRET_KEY") in initializers or in config/environments/production.rb, and keep them in Crunu. Each environment keeps its own version history, access is granted per environment, and when someone leaves, Crunu's offboarding report shows which versions of which environments they received and lists the values to reissue.
- Put
RAILS_ENVand eitherRAILS_MASTER_KEYor the individual secrets into Crunu environments such asrails-app/developmentandrails-app/production. - Develop with
crunu run --env rails-app/development -- bin/rails serverafter a one-timecrunu login. - Build images with
SECRET_KEY_BASE_DUMMY=1for asset precompilation, so no real secret is needed during the build. - On a Linux server with the agent, start Puma with
crunu run --env rails-app/production --restart on-change -- bundle exec puma -C config/puma.rb.
RAILS_ENV belongs in the environment because crunu run passes only a short allowlist from your shell, such as PATH and HOME. PATH keeps rbenv or asdf shims working. Variables like BUNDLE_GEMFILE or SSH_AUTH_SOCK, which Bundler may need for private Git gems, are not passed, so add them or use --inherit-env for that command.
With --restart on-change, crunu restarts Puma after a value changes, which is how a new database password or API key reaches the app. Don't let it rotate SECRET_KEY_BASE casually: the restart applies the new key, and every user is signed out.
Related reading
- Laravel has encrypted
.envfiles with a similar trade-off. - Django for a key with built-in fallbacks.
- Generate a Rails secret_key_base in your browser.
- Developer offboarding: which secrets to rotate and how to share .env files with your team.
Frequently asked questions
Something else? Write to support@crunu.com
Should I use Rails credentials or environment variables?
Credentials keep encrypted values in the repository and need only one key per environment on the server. Environment variables keep values out of the repository entirely and let each value change on its own. Many apps combine them: credentials for most values, ENV for what differs per deployment.
Where should RAILS_MASTER_KEY live?
Anywhere except the repository. The Rails guide says not to commit the master key. On servers, pass it as the RAILS_MASTER_KEY environment variable, for example from Crunu, or as the key file with restricted permissions.
Do per-environment credentials need their own key?
Yes. config/credentials/production.yml.enc is opened with config/credentials/production.key or RAILS_MASTER_KEY. Rails falls back to credentials.yml.enc and master.key only when the environment file doesn't exist.
How do I generate a new secret_key_base?
Run bin/rails secret. Changing the key expires sessions, so plan the switch.
Sources
Every source this page relies on, with the date we last checked it.
- Rails Guides: Securing Rails applications · checked October 30, 2026
- Rails 8.1 API: Rails::Application · checked October 30, 2026