Rails Credentials: Managing Secrets in Production Without Losing Your Mind
Rails ships with a built-in encrypted credentials system that handles secrets without third-party tools. Run bin/rails credentials:edit to open your encrypted vault, store API keys and database passwords, then access them anywhere with Rails.application.credentials. No ENV variable sprawl, no .env files committed by accident, no external secrets manager required for most apps.
That’s the pitch, anyway. In practice, teams hit real friction: multi-environment setups, CI/CD key distribution, credential rotation without downtime, and the dreaded “we lost the master key” scenario. This guide covers the practical side of Rails credentials in production, tested against Rails 7.1 and 8.0.
How Rails Credentials Actually Work
Rails credentials use AES-256-GCM encryption. Your secrets live in config/credentials.yml.enc — a single encrypted file checked into version control. The decryption key lives in config/master.key (or the RAILS_MASTER_KEY environment variable), which is .gitignored by default.
When you run:
EDITOR=vim bin/rails credentials:edit
Rails decrypts the file into a temporary plaintext YAML file, opens your editor, and re-encrypts when you save and close. The plaintext never touches disk permanently.
The credentials object is available everywhere in your app:
# config/credentials.yml.enc (decrypted)
aws:
access_key_id: AKIAIOSFODNN7EXAMPLE
secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
stripe:
secret_key: sk_live_4eC39HqLyjWDarjtT1zdp7dc
database:
password: production_db_password_here
# Anywhere in your app
Rails.application.credentials.aws.access_key_id
# => "AKIAIOSFODNN7EXAMPLE"
Rails.application.credentials.dig(:stripe, :secret_key)
# => "sk_live_4eC39HqLyjWDarjtT1zdp7dc"
The dig method is safer for nested keys — it returns nil instead of raising NoMethodError when a key is missing.
Per-Environment Credentials (Use Them)
The single biggest improvement you can make is splitting credentials by environment. Since Rails 7.1, this is well-supported:
bin/rails credentials:edit --environment production
bin/rails credentials:edit --environment staging
This creates separate files:
config/credentials/production.yml.enc
config/credentials/production.key
config/credentials/staging.yml.enc
config/credentials/staging.key
Rails automatically loads the correct file based on RAILS_ENV. Environment-specific credentials take precedence over the base config/credentials.yml.enc.
Why this matters in practice:
- Staging can’t accidentally use production API keys. I’ve seen this happen twice at different companies. Both times it caused real billing issues.
- Different team members can have different access levels. Your junior developers get the staging key. Production key stays with ops.
- Key rotation becomes independent. Rotating the staging key doesn’t require touching production.
Setting It Up
# Create production credentials
EDITOR=vim bin/rails credentials:edit --environment production
# Create staging credentials
EDITOR=vim bin/rails credentials:edit --environment staging
# Keep base credentials for shared, non-sensitive defaults
EDITOR=vim bin/rails credentials:edit
In your production environment, set RAILS_MASTER_KEY to the contents of config/credentials/production.key:
# On your server or in your deployment config
export RAILS_MASTER_KEY="a1b2c3d4e5f6..."
Distributing Keys to CI/CD
Your CI pipeline needs the master key to run tests that touch credentials. Here’s how to handle this across common platforms.
GitHub Actions
Store the key as a repository secret:
# .github/workflows/test.yml
env:
RAILS_MASTER_KEY: $
Kamal 2
Kamal reads from .kamal/secrets, which sources environment variables. The cleanest approach:
# .kamal/secrets
RAILS_MASTER_KEY=$(cat config/credentials/production.key)
Or reference a secrets manager:
# .kamal/secrets (using 1Password CLI)
RAILS_MASTER_KEY=$(op read "op://Vault/Rails/master_key")
Docker
Pass the key at runtime, never bake it into the image:
# Bad — key is in the image layer
# COPY config/credentials/production.key config/credentials/
# Good — key comes from environment at runtime
# No COPY needed, Rails reads RAILS_MASTER_KEY from ENV
docker run -e RAILS_MASTER_KEY="a1b2c3d4..." myapp
Rotating Credentials Without Downtime
Credential rotation is one of those things nobody plans for until an employee leaves or a key leaks. Here’s a process that works:
Step 1: Decrypt current credentials and save the plaintext:
bin/rails credentials:show --environment production > /tmp/creds_backup.yml
Step 2: Delete the old encrypted file:
rm config/credentials/production.yml.enc
rm config/credentials/production.key
Step 3: Re-create with a new key:
EDITOR=vim bin/rails credentials:edit --environment production
Rails generates a fresh key. Paste your secrets back in (updating any that need changing).
Step 4: Distribute the new key to all servers and CI systems.
Step 5: Deploy. Since the encrypted file and key change together in one deploy, there’s no window where they’re mismatched.
Step 6: Securely delete the plaintext backup:
shred -u /tmp/creds_backup.yml # Linux
# or
rm -P /tmp/creds_backup.yml # macOS
The entire rotation takes about 10 minutes for a typical app. The deploy itself has zero downtime because the new encrypted file and new key arrive together.
The “We Lost the Master Key” Scenario
If you lose config/credentials/production.key and nobody has RAILS_MASTER_KEY recorded anywhere, those credentials are gone. AES-256-GCM isn’t something you brute-force.
Prevention:
- Store keys in a team password manager (1Password, Bitwarden)
- At minimum two people should have access to production keys
- Document where keys are stored in your team runbook (not in the repo)
Recovery when the key is actually lost:
- You’ll need to regenerate all secrets (new API keys, new database passwords, everything)
- Delete the old
.yml.encfile - Create fresh credentials with
bin/rails credentials:edit --environment production - Enter all the new secrets
- Update every external service with the new keys
It’s painful. That’s why you store the key in a password manager.
Accessing Credentials Safely in Code
A few patterns I’ve found useful in production Rails apps:
Required credentials with clear errors
# config/initializers/stripe.rb
Stripe.api_key = Rails.application.credentials.stripe&.secret_key ||
raise("Missing credentials: stripe.secret_key")
This fails fast at boot instead of giving you mysterious nil errors in production at 2 AM.
Credential wrapper for complex configs
# app/lib/app_credentials.rb
module AppCredentials
def self.stripe_key
Rails.application.credentials.dig(:stripe, :secret_key) ||
ENV["STRIPE_SECRET_KEY"] ||
raise("No Stripe key configured")
end
def self.database_url
Rails.application.credentials.dig(:database, :url) ||
ENV["DATABASE_URL"]
end
end
This gives you a single place to check credential sources, and a fallback chain (encrypted credentials → ENV → error). Useful during migration from ENV-based configs.
Testing with credentials
# test/test_helper.rb
# Override credentials in tests without touching encrypted files
Rails.application.credentials.stubs(:stripe).returns(
OpenStruct.new(secret_key: "sk_test_fake")
)
Or better, use per-environment credentials with a test environment that has safe dummy values.
When NOT to Use Rails Credentials
Rails credentials aren’t the right tool for every situation:
- Frequently changing values (feature flags, runtime config): Use a database-backed config or something like Flipper. Re-encrypting and redeploying for every toggle change is wasteful.
- Large teams with granular access control: If you need audit logs of who accessed which secret, or fine-grained permissions, use HashiCorp Vault or AWS Secrets Manager.
- Secrets shared across non-Rails services: If your Python microservice also needs the same API key, a centralized secrets manager makes more sense than duplicating keys.
- Ephemeral or auto-rotating secrets: AWS IAM roles, short-lived tokens — these don’t belong in a static encrypted file.
For a typical Rails monolith with a small-to-medium team, built-in credentials handle 90% of use cases cleanly.
If you’re pairing credentials with a deployment pipeline, check out deploying Rails 8 with Kamal 2 for the full production setup. For encrypting data at the application level rather than configuration level, see ActiveRecord Encryption in Rails. And if you’re building feature toggles alongside your credentials setup, feature flags in Rails production covers a complementary pattern.
FAQ
What happens if RAILS_MASTER_KEY is wrong?
Rails raises ActiveSupport::MessageEncryptor::InvalidMessage at boot. Your app won’t start, which is the correct behavior — better to fail loudly than to run with missing secrets. Check that the key matches the encrypted file for your current RAILS_ENV.
Can I use both ENV variables and Rails credentials?
Yes, and many teams do during migration. The wrapper pattern shown above lets you check credentials first, fall back to ENV. Over time, move everything into credentials and remove the ENV fallback. Just don’t store the same secret in both places permanently — that doubles your rotation surface.
How do Rails credentials compare to dotenv or Figaro?
Dotenv (.env files) and Figaro (application.yml) store secrets in plaintext files that you .gitignore. The risk: someone commits them accidentally, and they end up in git history forever. Rails credentials are encrypted and safe to commit. The trade-off is that credentials require the master key for any edit, while .env files are plain text you can edit with any tool. For production apps, the encryption is worth the minor inconvenience.
Is it safe to commit credentials.yml.enc to a public repo?
Technically yes — the file is AES-256-GCM encrypted and useless without the key. In practice, security-conscious teams prefer private repos anyway. The encryption is strong, but “defense in depth” means not relying on a single layer. If your master key ever leaks (and you don’t notice immediately), a public encrypted file becomes a liability.
How do I share credentials with a new team member?
Send them the appropriate master key through a secure channel — your team password manager, an encrypted message, or in person. Never send keys through Slack, email, or any channel that logs message history permanently. For per-environment setups, only share the keys they actually need.
About the Author
Roger Heykoop is a senior Ruby on Rails developer with 19+ years of Rails experience and 35+ years in software development. He specializes in Rails modernization, performance optimization, and AI-assisted development.
Get in TouchRelated Articles
Need Expert Rails Development?
Let's discuss how we can help you build or modernize your Rails application with 19+ years of expertise
Schedule a Free Consultation