Credentials

Deploying secret API keys and other secret configuration to a production environment used to be a hassle. You don’t want to commit secrets unencrypted to your repository, and you also want to share them with other developers. Rails 5.2 introduced the concept of encrypted credentials to solve exactly this problem; Rails 6 added per-environment credentials on top.

You still have to store one central encryption key on your server and on all development systems, but that’s it. All other secrets/credentials are encrypted with that key and can be stored safely in your code repository.

We start with a new Rails application:

$ rails new shop
$ cd shop

Setup

In a fresh Rails 8 app you’ll find the master key that encrypts your credentials in config/master.key. That file is added to .gitignore automatically. Save this key in your team password manager (1Password, Bitwarden, Vaultwarden — whatever you use) so that your team can access it.

If you lose the key, nobody (including you) can decrypt your credentials.

Anyone who has the key can decrypt your credentials, so keep it safe.

You can also ship the key via the environment variable RAILS_MASTER_KEY. In production that is usually the preferred approach because config/master.key never hits the server’s filesystem — see Environment Variables in Production.

Editing Credentials

The encrypted credentials live in config/credentials.yml.enc. Because the file is encrypted you can’t open it in your editor directly. Use bin/rails credentials:edit:

$ bin/rails credentials:edit

Rails launches $EDITOR with a temporary plaintext copy. When you save and close, Rails re-encrypts the file and writes it back to disk. If $EDITOR is not set you can specify one inline:

$ EDITOR=vim bin/rails credentials:edit

or once in your shell profile: export EDITOR=code -w.

Credentials are written in YAML:

# aws:
#   access_key_id: 123
#   secret_access_key: 345

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: 9846dad34a3168...68d634f

test: foobar

Per-Environment Credentials

Since Rails 6 each environment can have its own credentials file. The development/test/production split is a major improvement over the old single-file model — you no longer have to share production secrets with every contributor.

Edit environment-specific credentials with --environment:

$ bin/rails credentials:edit --environment production

This creates (or opens) config/credentials/production.yml.enc and its matching key config/credentials/production.key. The environment-specific key takes precedence over config/master.key when that environment is booted; if there is no environment-specific key Rails falls back to RAILS_MASTER_KEY or config/master.key.

Typical team workflow:

  • config/credentials.yml.enc + config/master.key for shared secrets used in development and test (stripe test keys, webhook signing secrets, SMTP creds for a staging inbox, …​).

  • config/credentials/production.yml.enc
    config/credentials/production.key for the production-only secrets. Only the ops/deploy team has the production key.

Accessing a Credential

You can access a credential via Rails.application.credentials.<key>:

$ bin/rails console
Loading development environment (Rails 8.1.3)
irb(main):001> Rails.application.credentials.test
=> "foobar"
irb(main):002> Rails.application.credentials.dig(:aws, :access_key_id)
=> "123"
irb(main):003> exit

credentials.dig(…​) is safe even if a nested key is missing (returns nil). For a required value use .fetch:

Rails.application.credentials.fetch(:stripe).fetch(:secret_key)

.fetch raises KeyError if the value is missing, which is usually what you want at boot time.

Using the Credentials on the Production Web Server

Set RAILS_MASTER_KEY (or the per-environment key, for example RAILS_PRODUCTION_KEY) in the environment of the production process. With Kamal (the Rails 8 default) you list it under env.secret: in config/deploy.yml and store the real value in .kamal/secrets. With Heroku you set it via heroku config:set RAILS_MASTER_KEY=xxxx. With a hand-rolled Docker / systemd / Nginx setup, put it in the unit file or /etc/environment — wherever fits your setup.

Never deploy config/master.key as a file to the production server filesystem. Use the environment variable. If anyone gains read access to the server they still shouldn’t have the decryption key.

Rotating the Key

If a key leaks (or you suspect it did) rotate immediately:

  1. Generate a new random key: bin/rails credentials:edit --environment production with the old key set, copy the secret values out.

  2. Delete config/credentials/production.yml.enc and config/credentials/production.key.

  3. Re-run bin/rails credentials:edit --environment production to create new files, paste the values back in.

  4. Ship the new key to your server via environment variable and redeploy.