35+ Years Experience Netherlands Based ⚡ Fast Response Times Ruby on Rails Experts AI-Powered Development Fixed Pricing Available Senior Architects Dutch & English 35+ Years Experience Netherlands Based ⚡ Fast Response Times Ruby on Rails Experts AI-Powered Development Fixed Pricing Available Senior Architects Dutch & English
Rails Credentials: Managing Secrets in Production Without Losing Your Mind

Rails Credentials: Managing Secrets in Production Without Losing Your Mind

TTB Software
rails, security

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:

  1. Staging can’t accidentally use production API keys. I’ve seen this happen twice at different companies. Both times it caused real billing issues.
  2. Different team members can have different access levels. Your junior developers get the staging key. Production key stays with ops.
  3. 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:

  1. You’ll need to regenerate all secrets (new API keys, new database passwords, everything)
  2. Delete the old .yml.enc file
  3. Create fresh credentials with bin/rails credentials:edit --environment production
  4. Enter all the new secrets
  5. 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.

#rails #credentials #secrets #encryption #production #security
T

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 Touch

Share this article

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