The Complete Guide to Rails Credentials and Secrets Management

Managing secrets well is a common hallmark of production grade Rails applications. Get it wrong and you're one misplaced commit away from exposing API keys, database passwords, or encryption tokens. Get it right and your team can deploy confidently, rotate credentials with minimal disruption, and be better prepared for security audits.

I've worked with Rails credentials across dozens of production systems, from startups shipping their first feature to enterprise teams managing multi-environment deployments across AWS and GCP. This guide covers everything I've learned about how Rails handles secrets, the mistakes I see teams make repeatedly, and the practices that actually hold up under pressure.

How Rails Credentials Work

Rails introduced encrypted credentials in Rails 5.2 as the successor to the older secrets.yml and encrypted secrets workflows. The concept is straightforward: your secrets live in an encrypted YAML file that's safe to commit to version control. Only someone with the master key can decrypt it.

As of Rails 8.1.2 (released January 2026), the credentials system has matured significantly. Rails 8.0 removed the legacy config.read_encrypted_secrets toggle, making encrypted credentials the standard built-in encrypted secrets mechanism in Rails, typically combined with environment variables for infrastructure-level configuration.

The basics

When you run rails new, Rails generates two files:

  • config/credentials.yml.enc, your encrypted secrets file
  • config/master.key, the key used to encrypt and decrypt it

The .enc file is safe to commit. The .key file is not. Rails adds config/master.key to .gitignore automatically.

To edit your credentials:

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

Rails opens the decrypted credentials for editing (often via a temporary plaintext file) and re-encrypts when you save and close. Treat any plaintext artifacts, temporary files, editor swap files, clipboard history, as sensitive, especially on shared machines.

Inside, you'll find standard YAML:

# config/credentials.yml.enc (decrypted)
secret_key_base: 14b6462b8dc4366e1266baab0...

aws:
  access_key_id: AKIAIOSFODNN7EXAMPLE
  secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

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

Access them in your application code:

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

Per-environment credentials

Since Rails 6, you can create environment-specific credential files. This is critical for any team running separate staging and production configurations.

# Create production-specific credentials
EDITOR="code --wait" bin/rails credentials:edit --environment production

# Create staging-specific credentials
EDITOR="code --wait" bin/rails credentials:edit --environment staging

This generates:

  • config/credentials/production.yml.enc + config/credentials/production.key
  • config/credentials/staging.yml.enc + config/credentials/staging.key

By default, Rails uses config/credentials/#{Rails.env}.yml.enc if it exists; otherwise it falls back to the global config/credentials.yml.enc. This means you can keep shared configuration in the global file and override per environment where needed.

What's coming in Rails 8.2: combined credentials

There is ongoing work on Rails edge toward a unified credentials API, often discussed as Rails.app.creds. Until Rails 8.2 ships as stable, treat the names and behaviour below as provisional, link to the official API docs once available. The intent is to check ENV first, then fall back to encrypted credentials, eliminating one of the most common pain points, the boilerplate of checking both sources:

# Before (Rails 8.1 and earlier)
api_key = ENV["STRIPE_API_KEY"] || Rails.application.credentials.dig(:stripe, :api_key)

# After (Rails 8.2, API subject to change)
api_key = Rails.app.creds.require(:stripe_api_key)

The require method raises if the value is missing from both sources. There's also option for values with defaults:

redis_url = Rails.app.creds.option(:redis_url, default: "redis://localhost:6379")

Under the hood, Rails.app.creds builds on a chained configuration approach (ActiveSupport::CombinedConfiguration), which checks multiple credential backends in order. This may be extensible to external secret managers:

# config/initializers/credentials.rb (example, verify against final Rails 8.2 API)
Rails.app.creds = ActiveSupport::CombinedConfiguration.new(
  Rails.app.envs,            # Check ENV first
  VaultConfiguration.new,     # Then HashiCorp Vault
  Rails.app.credentials       # Finally, encrypted credentials
)

This is a significant step forward for teams that need to bridge between environment variables and encrypted credentials, especially during migrations or when working with managed infrastructure that injects secrets via ENV.

Common Mistakes (and How to Avoid Them)

After reviewing and auditing credentials setups for teams of all sizes, these are the mistakes I encounter most frequently.

1. Committing the master key

This happens more often than you'd expect. A developer removes config/master.key from .gitignore, commits, pushes, and now your encryption key is in your Git history, even if you immediately revert.

The fix: If this happens, treat the key as compromised. Generate new credentials with a new master key and rotate every secret that was in the old file. Rails credentials use AES-128-GCM (as defined in ActiveSupport::EncryptedFile), it's not getting brute-forced, but an exposed key means all your encrypted secrets are readable. Check your Git history for any exposure, and consider using tools like git-secrets or trufflehog to scan for accidental leaks in CI.

2. Sharing keys over insecure channels

Slack messages, emails, and shared Google Docs are not appropriate channels for master keys. Once a key hits a third-party service, you've lost control of who can access it and whether it's been logged or cached.

The fix: Use a password manager that supports team sharing, 1Password, Bitwarden, or your organisation's vault solution. Share the key there, and document in your project's README exactly where team members can find it.

3. Losing the master key entirely

If you lose config/master.key and don't have RAILS_MASTER_KEY set anywhere, your encrypted credentials are permanently inaccessible. There is no recovery mechanism. Rails credentials use AES-128-GCM authenticated encryption, without the key, the file cannot be decrypted in practice.

The fix: Store your master key in at least two secure locations: your team's password manager and your deployment infrastructure's secret store. Document both locations.

4. Using the same credentials across all environments

Running production API keys in development and test environments increases your attack surface unnecessarily and makes it easy to accidentally trigger production side effects during testing.

The fix: Use per-environment credential files. Keep development and test credentials pointing to sandbox or mock services. Production credentials should only exist in the production-specific file.

5. Not understanding the decryption key resolution

Rails gets the decryption key from ENV['RAILS_MASTER_KEY'] if set. Otherwise, it reads from config.credentials.key_path (by default config/credentials/#{Rails.env}.key if present, else config/master.key).

If you're using per-environment credentials but only setting RAILS_MASTER_KEY, Rails will try to decrypt the environment-specific file with that key. If the keys don't match, you'll get a decryption error that can be confusing to debug.

The fix: Be explicit about which key maps to which file. In CI, set environment-specific variables like RAILS_MASTER_KEY to the correct key for the environment you're testing or deploying.

Integrating With AWS and GCP Secrets Managers

Rails credentials work well for many teams, but as applications scale, you may need to integrate with external secret managers for compliance, auditing, or infrastructure reasons.

AWS Secrets Manager

A common pattern is to fetch secrets from AWS Secrets Manager at boot time and merge them into your application configuration:

# config/initializers/aws_secrets.rb
if Rails.env.production?
  require "aws-sdk-secretsmanager"

  client = Aws::SecretsManager::Client.new(region: "eu-west-2")

  begin
	response = client.get_secret_value(secret_id: "myapp/production")
	secrets = JSON.parse(response.secret_string)

	# Make secrets available via ENV-like access
	secrets.each { |key, value| ENV[key] ||= value }
  rescue Aws::SecretsManager::Errors::ServiceError => e
	Rails.logger.error("Failed to fetch secrets: #{e.message}")
	raise "Cannot start without secrets" # Fail closed
  end
end

Production considerations: Add timeouts, retries, and fail-closed behaviour. Avoid overwriting ENV values that are already set. For high-traffic applications, consider caching fetched secrets rather than hitting the API on every boot, and prefer a dedicated config object over mutating ENV in multi-threaded contexts.

If you adopt Rails 8.2's combined credentials when it ships, you may be able to plug AWS directly into the credential backend chain without changing application code.

GCP Secret Manager

The approach with Google Cloud is similar:

# config/initializers/gcp_secrets.rb
if Rails.env.production?
  require "google/cloud/secret_manager"

  client = Google::Cloud::SecretManager.secret_manager_service
  project_id = "your-project-id"

  begin
	secret_name = "projects/#{project_id}/secrets/myapp-production/versions/latest"
	response = client.access_secret_version(name: secret_name)
	secrets = JSON.parse(response.payload.data)

	secrets.each { |key, value| ENV[key] ||= value }
  rescue Google::Cloud::Error => e
	Rails.logger.error("Failed to fetch GCP secrets: #{e.message}")
	raise "Cannot start without secrets"
  end
end

When to use external managers vs Rails credentials

Rails credentials are sufficient for most small to mid-sized teams. Consider external secret managers when you need automatic rotation policies, granular access audit logging, compliance with SOC2/PCI DSS/HIPAA requirements around secret access, or when multiple services (not just your Rails app) need access to the same secrets.

The two approaches are not mutually exclusive. Many teams use Rails credentials for application-specific configuration and an external manager for shared infrastructure secrets like database passwords and third-party API keys.

CI/CD Workflows for Secrets

Your CI/CD pipeline needs access to your secrets to run tests and deploy, but how you provide that access matters enormously.

GitHub Actions

Store your master key as a GitHub Actions secret and inject it as an environment variable:

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
	branches: [main]

jobs:
  deploy:
	runs-on: ubuntu-latest
	env:
	  RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
	  RAILS_ENV: production

	steps:
	  - uses: actions/checkout@v4
	  - uses: ruby/setup-ruby@v1
		with:
		  ruby-version: "3.4"
		  bundler-cache: true

	  - name: Run tests
		run: bin/rails test

	  - name: Deploy
		run: bin/kamal deploy

Kamal 2 secrets management

If you're deploying with Kamal 2 (the default deployment tool for Rails 8), secrets are managed through the .kamal/secrets file. This replaced the older .env approach and is typically committed to version control. It's designed to contain references to secrets sourced from your environment or external vaults, but avoid hardcoding literal secret values there:

# .kamal/secrets
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
RAILS_MASTER_KEY=$(cat config/master.key)
DB_PASSWORD=$DB_PASSWORD

Kamal also supports fetching secrets directly from password managers:

# .kamal/secrets (using 1Password)
SECRETS=$(kamal secrets fetch --adapter 1password --account myaccount --from MyVault/MyApp RAILS_MASTER_KEY DB_PASSWORD)
RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY $SECRETS)
DB_PASSWORD=$(kamal secrets extract DB_PASSWORD $SECRETS)

Kamal includes built-in helpers for fetching secrets from common password managers (currently 1Password, LastPass, and Bitwarden). For other backends like AWS Secrets Manager or Doppler, teams often use custom scripts or community adapters, verify against the version of Kamal you're running. In typical setups, secrets are resolved as part of the deploy workflow rather than a separate manual push step, but verify your Kamal version and deploy pipeline.

One important detail: secrets in .kamal/secrets are injected as environment variables into your Docker containers. Anyone with root or privileged access to your server, or container exec permissions, can read them via env or printenv inside the container. This is standard for containerised deployments, but it's worth understanding when designing your security model.

CircleCI, GitLab CI, and others

The pattern is the same across CI providers: store the master key in the platform's secret management, inject it as RAILS_MASTER_KEY, and ensure it's never logged or exposed in build output.

Key rules for CI/CD secrets:

  • Never echo or print secret values in build logs
  • Use the CI platform's built-in secret masking where available
  • Rotate keys if a build log containing secrets is ever exposed
  • Restrict who can modify CI secrets to the same group that manages production access

Security Best Practices

Rotate regularly

Treat your master key like a password. Rotate it periodically, at minimum when someone with access leaves the team. The process is straightforward:

  1. Decrypt your current credentials and copy the plaintext (be mindful of editor swap files and clipboard managers)
  2. Delete the old .enc and .key files
  3. Run bin/rails credentials:edit to generate new files with a new key
  4. Paste your secrets back in and close the editor
  5. Distribute the new key through your secure channel
  6. Update CI/CD and deployment configurations
  7. Clear your clipboard history if applicable

Audit access

Know who has access to your master key at all times. Maintain a list, review it quarterly, and revoke access when it's no longer needed.

Use config.require_master_key

In your production environment config, ensure Rails fails loudly if the master key is missing rather than silently falling back:

# config/environments/production.rb
config.require_master_key = true

This prevents your application from booting without credentials, which is far preferable to discovering the problem at runtime when a feature that depends on a missing API key fails.

Filter credentials from logs

Rails ships with a default filter list in the generated filter_parameter_logging.rb initializer (see the Rails configuration guide for details). Extend it for your application's specific secrets:

# config/initializers/filter_parameter_logging.rb
Rails.application.config.filter_parameters += [
  :api_key, :private_key, :webhook_secret
]

Don't store secrets in Docker images

If you're building Docker images for deployment, ensure your Dockerfile doesn't copy master keys or credential keys into the image. In modern Rails-generated Docker setups (Rails 7.1+), asset precompilation uses SECRET_KEY_BASE_DUMMY=1 to avoid needing production secrets at build time, but custom Dockerfiles may not include this.

Check your .dockerignore:

config/master.key
config/credentials/*.key
.kamal/secrets

Consider encryption at rest for your database

Rails credentials protect secrets in your codebase, but once they're loaded into memory and used, for example, to connect to your database, the connection itself needs to be secure. Use SSL/TLS for database connections, encrypt sensitive columns with Active Record Encryption (introduced in Rails 7.0), and ensure your infrastructure encrypts data at rest.

Version Compatibility Quick Reference

Rails Version Credentials Support Notes
5.2 Introduced encrypted credentials (global) secrets.yml still supported alongside
6.0 – 7.x Multi-environment credentials Per-env .yml.enc files via --environment flag
8.0 Multi-environment credentials read_encrypted_secrets removed
8.1.2 (current) Multi-environment credentials Stable, production-ready
8.2 (edge, provisional) Combined credentials API ENV + credentials unified lookup (not yet released)

Ruby 4.0 was released on December 25, 2025. Rails 8.0 and 8.1 require Ruby 3.2 or newer. Rails credentials rely on Active Support's encryption features and should work on supported Ruby versions, however, there are known compatibility discussions around Ruby 4.0 (see rails/rails#56457), so confirm your Rails version's Ruby support policy before standardising on Ruby 4.0 in production. Ruby 3.4 is widely used with Rails 8.x and is a conservative choice if you want to avoid Ruby 4.0's newness.

Final Thoughts

Rails credentials are not complicated, but they do require intentionality. The encryption is solid (AES-128-GCM via ActiveSupport::EncryptedFile), the tooling is mature, and with Kamal 2's secrets management and the upcoming Rails.app.creds in 8.2, the ecosystem continues to get better at making secure practices the path of least resistance.

In practice, many production teams use a combination: Rails credentials for values they want versioned alongside the codebase, and environment variables (or an external secret manager) for infrastructure-level secrets. Neither approach is exclusive, the key is choosing deliberately and documenting your team's conventions.

The most common problems I see aren't technical, they're organisational. Teams that struggle with credentials typically lack a clear policy for who has access, how keys are shared, and what happens when someone leaves. Solve those problems first, and the technical implementation follows naturally.

If your team is running a production Rails application and you're not confident in your secrets management, or you're preparing for a security audit and need your credentials setup reviewed, I can help.


Need a credentials and security audit for your Rails application?

I help teams review, modernise, and secure their Rails credentials and deployment pipelines, without disrupting production. Whether you're migrating from secrets.yml, integrating with AWS/GCP, or setting up Kamal 2 for the first time, I can get your secrets management to a place where your team can deploy with confidence.

Get in touch →

Looking for a fractional Rails engineer or CTO?

I take on a limited number of part-time clients.

Get in touch