Skip to content
ExponaBLOG
RUBY ON RAILS · DAY 036 / 100

Rails credentials: Ruby on Rails lessons from production

Secrets without environment sprawl.

Expona Admin7 min read

Most Rails teams discover the hard way that hardcoding secrets or relying on environment variables scattered across deployment servers creates a maintenance nightmare and a security liability. Rails credentials solve this by encrypting sensitive data at the application level, storing it in git, and requiring only a master key outside version control. This approach, when combined with per-environment encryption, gives you both convenience and safety.

Understanding Rails credentials: Ruby on Rails lessons from production

Rails credentials represent a built-in encryption system that became the Rails way starting in Rails 5.1. Before credentials, teams typically managed secrets through environment variables, dotenv files, or third-party vault systems—each approach introducing operational overhead or security gaps. Rails credentials encrypt your secrets into a single file that lives safely in git, making it portable and audit-friendly while keeping the decryption key separate.

The credentials system works by encrypting sensitive data with a master key. That master key never enters version control. Instead, your deployment pipeline (CI/CD, orchestration, container runtime) injects the master key as an environment variable or mounts it as a file. Your application decrypts the credentials file at boot time, and sensitive values become accessible through a clean Rails API.

This approach scales from small teams to enterprises. You're not managing dozens of individual secrets scattered across environment configurations. Everything is in one encrypted file, versioned alongside your code, easy to review and rollback if needed.

bin/rails credentials:edit

The credentials editing command is your primary interface with the system. When you run bin/rails credentials:edit, Rails decrypts the current credentials file, opens it in your default editor (or the editor specified by EDITOR environment variable), and re-encrypts the file when you save and close the editor.

Before you can edit credentials, Rails needs a master key. On a fresh Rails app, it generates one automatically and stores it in config/master.key. That file is gitignored by default. If config/master.key doesn't exist, Rails will prompt you or you can create it manually.

A typical credentials file looks like YAML:

ruby
aws:
  access_key_id: YOUR_ACCESS_KEY
  secret_access_key: YOUR_SECRET_KEY

stripe:
  secret_key: sk_live_...
  publishable_key: pk_live_...

database:
  replication_user: repl_user
  replication_password: secure_password

sendgrid_api_key: SG_...

You edit this file in plain text, save it, and the credentials system re-encrypts it automatically. The encrypted version gets stored in config/credentials.yml.enc, which you do commit to git. The plain text version never touches disk permanently.

In a team environment, every developer needs access to the master key to decrypt and edit credentials locally. That key is typically shared through a secure channel (1Password, HashiCorp Vault, AWS Secrets Manager, or your organization's secret management system) rather than committed to git. For staging and production, the master key is injected by your deployment pipeline.

Encrypted per environment

Rails credentials support environment-specific encryption. By default, bin/rails credentials:edit edits the credentials for the current Rails environment. You can also explicitly specify an environment:

ruby
bin/rails credentials:edit --environment production
bin/rails credentials:edit --environment staging
bin/rails credentials:edit --environment development

When you do this, Rails creates separate encrypted files: config/credentials/production.yml.enc, config/credentials/staging.yml.enc, and so on. Each environment has its own master key as well: config/credentials/production.key, config/credentials/staging.key.

The per-environment approach is powerful in production scenarios. Your staging environment uses staging API keys and database credentials. Your production environment uses production API keys and database credentials. A developer can safely have access to staging credentials but not production credentials. Your CI/CD pipeline can inject only the production key in the production deployment step.

Here's how you might structure this in a real project:

ruby
# config/credentials.yml.enc (default/development)
# Contains fake or test keys

# config/credentials/production.yml.enc
# Contains real production secrets, encrypted with production.key

# config/credentials/staging.yml.enc
# Contains staging secrets, encrypted with staging.key

In your deployment configuration, you only mount or inject the master key for the appropriate environment. GitHub Actions, GitLab CI, or your Kubernetes secrets can store these keys separately and inject them only when deploying to that environment.

Master key outside git

The master key is the crown jewel of the credentials system. Protect it accordingly. The key file (config/master.key for the default environment, or config/credentials/production.key for production) should never be committed to git. Rails automatically adds config/master.key to .gitignore.

For production deployments, you have several options:

  1. Environment variable: Store the master key content as a base64-encoded environment variable. Rails checks the RAILS_MASTER_KEY environment variable at boot.
  2. Secret management system: Use AWS Secrets Manager, HashiCorp Vault, or your cloud provider's native secret store. Have your deployment script fetch the key and set it as an environment variable.
  3. Mounted file: In Kubernetes, mount the master key as a secret volume. Rails will read it from config/master.key.
  4. CI/CD secrets: Store the master key in GitHub Secrets, GitLab CI variables, or CircleCI context, and export it during the deployment job.

A typical GitHub Actions workflow looks like this:

ruby
name: Deploy to production
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      RAILS_MASTER_KEY: ${{ secrets.RAILS_PRODUCTION_MASTER_KEY }}
    steps:
      - uses: actions/checkout@v2
      - name: Deploy
        run: |
          bundle install
          rails db:migrate
          # Your deployment script

The key never appears in logs or version control. It's only available to the process at runtime. If someone compromises your git repository, they get the encrypted credentials file but not the key—so the file is useless without the key.

Access with Rails.application.credentials

In your Rails application code, you access decrypted credentials through the Rails.application.credentials object. This is a simple hash-like interface:

ruby
# In config/initializers/aws.rb
Aws.config.update(
  region: 'us-east-1',
  credentials: Aws::Credentials.new(
    Rails.application.credentials.aws[:access_key_id],
    Rails.application.credentials.aws[:secret_access_key]
  )
)

# In app/models/payment.rb
class Payment < ApplicationRecord
  def self.stripe_client
    Stripe.api_key = Rails.application.credentials.stripe[:secret_key]
    Stripe
  end
end

# In app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
  def welcome_email
    @sendgrid_api_key = Rails.application.credentials.sendgrid_api_key
    # Send email
  end
end

You can also use the shorthand Rails.application.credentials.aws directly as a hash and chain keys. If a key doesn't exist, Rails raises an error, which helps catch configuration mistakes early.

In a multi-environment setup, the credentials object automatically loads the environment-specific encrypted file. When your app starts in production, Rails reads RAILS_MASTER_KEY from the environment, decrypts config/credentials/production.yml.enc, and makes those values available through Rails.application.credentials. Your code doesn't change; the environment determines which credentials file gets loaded.

For better code organization, you can also fetch credentials early and store them as constants or in an initializer:

ruby
# config/initializers/secrets.rb
module Secrets
  STRIPE_KEY = Rails.application.credentials.stripe[:secret_key]
  AWS_ACCESS_KEY = Rails.application.credentials.aws[:access_key_id]
  AWS_SECRET_KEY = Rails.application.credentials.aws[:secret_access_key]
  SENDGRID_API_KEY = Rails.application.credentials.sendgrid_api_key
end

# Then in your code
Stripe.api_key = Secrets::STRIPE_KEY

Common mistakes

Committing the master key: If you accidentally commit config/master.key, rotate it immediately. Treat it like a compromised password. Generate a new key, re-encrypt your credentials with the new key, and update the master key everywhere it's stored.

Forgetting to inject the master key in production: A frequent gotcha is deploying to production without setting RAILS_MASTER_KEY as an environment variable. Your app will crash at boot with a "missing key file" error. Always verify that the deployment pipeline sets this variable before the Rails app starts.

Mixing plain text secrets in code: Even with credentials in place, developers sometimes hardcode API keys or database passwords directly in their config files or code for convenience. Use linters or pre-commit hooks to catch this—tools like detect-secrets or GitHub's native secret scanning can flag these automatically.

Not rotating credentials regularly: Credentials aren't "set and forget." Treat API keys and database passwords like any security credential—rotate them periodically, especially if team membership changes or if you suspect any exposure.

Storing credentials in multiple systems: Once you've adopted Rails credentials, don't also maintain environment variables or dotenv files for the same secrets. Single source of truth. The credentials file is your canonical store.

Key takeaways

  • Rails credentials encrypt sensitive data in a file that can safely live in version control, with decryption handled by a master key kept outside git.
  • Use bin/rails credentials:edit to manage secrets in plain text temporarily; they're automatically re-encrypted when you save.
  • Environment-specific credentials files let you isolate production secrets from staging and development, controlling access per environment.
  • The master key should be injected at deployment time via environment variables or secret management systems, never committed to git.
  • Access decrypted credentials in code via Rails.application.credentials, which automatically loads the correct environment-specific file.
  • Audit your team's secret-handling practices regularly; credentials are a tool, but proper workflow discipline keeps them effective.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles