/ Tags: RAILS / Categories: RAILS

Rails Credentials — Managing Secrets Without Leaking Them

Every Rails app has secrets — API keys, database passwords, third-party service tokens. The bad old pattern was storing these in environment variables set manually on each server, or worse, hardcoded in config files that somehow ended up in version control. Rails credentials, introduced in Rails 5.2 and refined since, give you a structured, encrypted, version-controllable way to manage secrets that works cleanly across environments and teams.

How Credentials Work


Rails credentials stores your secrets in an encrypted file — config/credentials.yml.enc — that you commit to version control. It’s encrypted with a key stored in config/master.key (which you never commit). Anyone with the key can decrypt and edit the file. Anyone without it sees only ciphertext.

The workflow: edit secrets in a decrypted YAML view, Rails re-encrypts on save, you commit the encrypted file. Your team shares the key out of band (password manager, CI environment variable). The secrets travel in the repo safely.

Editing Credentials


Setup:

EDITOR="code --wait" bin/rails credentials:edit

This opens the decrypted credentials in your editor. Save and close — Rails re-encrypts:

Example:

# config/credentials.yml.enc (decrypted view)
secret_key_base: your_secret_key_base_here

aws:
  access_key_id: AKIAIOSFODNN7EXAMPLE
  secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
  region: us-east-1

sendgrid:
  api_key: SG.xxxxx

stripe:
  publishable_key: pk_live_xxxxx
  secret_key: sk_live_xxxxx

Nested YAML — structure it however makes sense for your app. Access it in code:

Example:

Rails.application.credentials.aws[:access_key_id]
Rails.application.credentials.dig(:aws, :access_key_id)
Rails.application.credentials.stripe[:secret_key]

dig is safer for deeply nested paths — it returns nil instead of raising if a key is missing.

Per-Environment Credentials


Rails 6+ supports separate credential files per environment, each with its own key:

Setup:

EDITOR="code --wait" bin/rails credentials:edit --environment production
EDITOR="code --wait" bin/rails credentials:edit --environment staging

This creates config/credentials/production.yml.enc and config/credentials/production.key. Environment-specific credentials take precedence over the global config/credentials.yml.enc when running in that environment.

Example:

# Production-specific credentials
database:
  password: prod_super_secret_password

redis:
  url: redis://prod-redis.internal:6379

Example:

# Access works the same way in any environment
Rails.application.credentials.database[:password]

The benefit: different teams can manage staging and production secrets independently. Your staging key doesn’t unlock production secrets.

Sharing Keys Safely


The master key (config/master.key) should never be committed to version control — Rails adds it to .gitignore by default. How to share it with your team and CI:

  • 1Password / Bitwarden: store the key as a shared secure note
  • CI environment variable: set RAILS_MASTER_KEY in your CI system (GitHub Actions secrets, Heroku config vars, Render environment variables)
  • Environment-specific keys: config/credentials/production.key — set RAILS_MASTER_KEY for production only in your deployment environment

Setup:

# In CI or production, set via environment variable
export RAILS_MASTER_KEY=your_master_key_here

Rails checks config/master.key first, then RAILS_MASTER_KEY. This means your key stays out of the filesystem in containerized environments where the repo is the container image.

Require Credentials at Boot


If a required credential is missing, you want to know at boot time — not when a user triggers the code path that needs it. Rails lets you require credentials:

Example:

# config/initializers/credentials_check.rb
Rails.application.config.after_initialize do
  required_keys = [
    [:aws, :access_key_id],
    [:aws, :secret_access_key],
    [:stripe, :secret_key]
  ]

  required_keys.each do |path|
    value = Rails.application.credentials.dig(*path)
    if value.blank?
      raise "Missing required credential: #{path.join('.')}"
    end
  end
end

This makes a missing credential a boot error, not a runtime surprise. Deploy fails loudly rather than silently serving broken features.

Pro-Tip: Don’t store non-secret configuration in credentials. API endpoints, feature flags, timeouts, and other non-sensitive config belong in regular Rails config files (config/application.rb, environment files, or a config gem like config). Credentials are for secrets only — things that would cause a security incident if leaked. Mixing config and secrets makes both harder to manage.

Rotating Credentials


If a key is compromised or you’re rotating secrets as part of a security practice:

  1. Generate new values with your service provider
  2. Edit credentials and update the values: EDITOR="code --wait" bin/rails credentials:edit
  3. Deploy the updated encrypted file
  4. Revoke the old values in the service provider

The key itself can be rotated by re-encrypting with a new key:

Setup:

bin/rails credentials:edit   # edit (even without changes) and save with new key

Regenerate config/master.key, share the new key with your team, and recommit the re-encrypted file. Old deployments with the old key will fail until they receive the new one — plan the rotation accordingly.

Conclusion


Rails credentials solve a real problem cleanly: secrets that need to be versioned without being exposed, accessible to the application without being hardcoded, and manageable across environments without manual server configuration. The per-environment file structure added in Rails 6 makes it practical for teams at any scale. If you’re still managing secrets through unencrypted env files or hardcoded values somewhere in your codebase, moving to credentials is worth the hour it takes to migrate.

FAQs


Q1: What if I lose the master key?
There is no recovery. The encrypted file is unreadable without the key. Keep the key backed up in at least two places — your password manager and your CI system. Some teams keep a printed copy in a physical safe for critical production credentials.

Q2: Can multiple developers edit credentials simultaneously?
No — it’s a single encrypted file. Simultaneous edits create a conflict that must be resolved by one person re-editing. In practice, this is rarely an issue. Communicate before editing credentials in a shared environment.

Q3: Should I use credentials or environment variables?
Rails credentials are the Rails-native approach and have advantages: they’re versioned, structured, and typed. Environment variables are more portable across frameworks. Many teams use credentials for Rails-specific secrets and environment variables for infrastructure-level configuration (database URLs, server addresses). Both work; pick one and be consistent.

Q4: How do I access credentials in tests?
The test environment uses config/credentials.yml.enc with the master key, same as development. For CI, set the RAILS_MASTER_KEY environment variable. For test-specific values, override in config/credentials/test.yml.enc.

Q5: Are credentials encrypted at rest on disk?
Yes. config/credentials.yml.enc is AES-256-GCM encrypted. The decrypted content only exists in memory during credentials:edit or at runtime when Rails loads the application. It’s never written to disk in plaintext by Rails itself.

cdrrazan

Rajan Bhattarai

Full Stack Software Developer! 💻 🏡 Grad. Student, MCS. 🎓 Class of '23. GitKraken Ambassador 🇳🇵 2021/22. Works with Ruby / Rails. Photography when no coding. Also tweets a lot at TW / @cdrrazan!

Read More