
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Failing to properly manage secrets and config in Ruby apps is one of the most common causes of security breaches and compliance failures I see during audits. While Rails provides excellent built-in tooling like encrypted credentials, many teams still accidentally commit API keys or rely on fragile environment variable injection in production. This guide establishes a secure, scalable hierarchy for handling configuration that satisfies both developer ergonomics and SOC 2 evidence requirements.
How do you manage secrets and config in Ruby apps using encrypted credentials?
Rails Encrypted Credentials remain the gold standard for managing application-level secrets that need to be version-controlled alongside your code. Unlike plain YAML files, these are AES-256-GCM encrypted and can only be decrypted with a master key. This approach solves the "works on my machine" problem while keeping sensitive data out of git history.
Setting up environment-specific credentials
In modern Rails (7+), you should use per-environment credentials rather than a single monolithic file. This limits the blast radius if a specific environment's master key is compromised and allows different team members to access only what they need.
# Generate production-specific credentials
EDITOR=vim bin/rails credentials:edit --environment production
# The structure inside config/credentials/production.yml.enc
aws:
access_key_id: AKIAIOSFODNN7EXAMPLE
secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
stripe:
api_key: sk_live_51HG4...
database:
replica_password: super_secure_replica_pass Access these values in your application code safely. Always provide fallbacks or explicit error handling to prevent cryptic nil errors during deployment.
# Safe access pattern with validation
class PaymentService
def initialize
@api_key = Rails.application.credentials.dig(:stripe, :api_key)
raise MissingCredentialError, "Stripe API key missing" unless @api_key.present?
end
end A common mistake I encounter during secrets management reviews is teams storing the master key in the same repository as the encrypted file. The master key must live in a separate secure location—typically a secrets manager or distributed via a secure channel to authorized developers only.
When should you use environment variables versus encrypted files?
The decision between environment variables and encrypted credentials isn't binary; it's contextual. Environment variables excel at platform-specific configuration that changes between deployments without code changes, while encrypted files suit application secrets that travel with the codebase.
| Criteria | Environment Variables | Encrypted Credentials | External Vault |
|---|---|---|---|
| Best For | Platform config (DB host, port, region) | App secrets (API keys, signing tokens) | High-value secrets, dynamic creds, PII |
| Version Control | No (injected at runtime) | Yes (encrypted blob committed) | No (fetched at runtime) |
| Rotation Ease | Requires redeploy/restart | Requires redeploy after re-encryption | Transparent, no app restart needed |
| Audit Trail | Platform-dependent | Git log shows who edited | Full access logging, policy enforcement |
| SOC 2 Evidence | Weak without external logging | Moderate (git-based) | Strong (native audit logs) |
| Developer Friction | Low (.env files locally) | Medium (requires master key setup) | Higher (Vault auth, token mgmt) |
In practice, I recommend a hybrid approach. Use environment variables for infrastructure endpoints and non-sensitive toggles. Use encrypted credentials for third-party API keys that don't require frequent rotation. Reserve external vaults for database passwords, TLS certificates, and any secret that requires automatic rotation or fine-grained access policies.
How do you integrate HashiCorp Vault with Ruby applications?
For organizations requiring SOC 2 compliance or handling regulated data, an external secrets manager like HashiCorp Vault becomes necessary. Vault provides dynamic secrets, leasing, and detailed audit trails that flat files simply cannot offer. Integration with Ruby is straightforward using the official vault gem.
Implementing the Vault client in Rails
Create a dedicated service object to handle Vault interactions. This isolates the complexity of authentication, lease management, and error handling from your business logic.
# app/services/vault_client.rb
class VaultClient
def self.database_credentials(role: "app-readonly")
vault = Vault::Client.new(
address: ENV.fetch("VAULT_ADDR"),
token: fetch_approle_token
)
secret = vault.logical.read("database/creds/#{role}")
raise VaultError, "Failed to fetch DB creds" unless secret
{
username: secret.data[:username],
password: secret.data[:password],
lease_id: secret.lease_id,
ttl: secret.lease_duration
}
rescue Vault::HTTPConnectionError => e
Rails.logger.error("Vault connection failed: #{e.message}")
raise
end
private_class_method def self.fetch_approle_token
# Authenticate via AppRole and cache token in memory
# Never store this token on disk
end
end For production resilience, implement lease renewal in a background thread or Sidekiq job. When a lease expires without renewal, Vault automatically revokes the credential at the database level, preventing unauthorized access even if the application is compromised.
What are the security risks of using dotenv in production Ruby apps?
The dotenv gem is ubiquitous in Ruby development, but using it in production introduces significant risk. While convenient for local development, relying on .env files in production violates core principles of secure configuration management and creates audit blind spots.
- File permission leakage:
.envfiles often inherit default umask permissions, making them readable by other users or processes on shared hosting environments. - No encryption at rest: Secrets sit in plaintext on disk. Any backup, snapshot, or forensic image captures them in cleartext.
- Absent audit trail: There is no native mechanism to track who read or modified a
.envfile, making SOC 2 evidence collection impossible. - Deployment coupling: Updating a secret requires redeploying or restarting the application, increasing change failure rate and rollback complexity.
- Git history contamination: Even if currently gitignored, accidental commits happen. Once in history, secrets remain exposed indefinitely unless rewritten.
If you must use environment variables in production, inject them through your platform's native mechanism (Kubernetes Secrets, AWS Parameter Store, systemd EnvironmentFile with restricted permissions) rather than loading from a file. For guidance on safe pipeline handling, review handling secrets in CI/CD pipelines safely.
How do you rotate secrets without downtime in Ruby applications?
Secret rotation is where theory meets operational reality. Many teams avoid rotation because they fear downtime, but unrotated secrets are a ticking clock for breach impact. The key is designing your application to support graceful credential transitions.
Implementing dual-credential support
Before rotating any secret, ensure your application can accept both old and new values simultaneously. This eliminates the coordination problem between deployment and secret update.
# config/initializers/stripe.rb
# Support both current and previous key during rotation window
STRIPE_KEYS = [
Rails.application.credentials.dig(:stripe, :api_key),
Rails.application.credentials.dig(:stripe, :api_key_previous)
].compact
# Webhook signature verification accepts either key
Stripe::Webhook::Signature.verify_header(
payload,
sig_header,
STRIPE_KEYS,
tolerance: Stripe::Webhook::DEFAULT_TOLERANCE
) For database credentials managed by Vault, rotation is automatic and transparent. The application requests a new lease before the old one expires, and Vault handles the user creation and revocation at the database level. Your Ruby code never needs to know the actual password—it only manages the lease lifecycle.
Establish a rotation schedule aligned with your compliance framework. SOC 2 typically expects quarterly rotation for high-privilege credentials, while ISO 27001 focuses more on event-driven rotation after personnel changes or suspected compromise. Document this policy and automate evidence collection to satisfy auditors without manual effort.
Secure Configuration Management Next Steps
To effectively manage secrets and config in Ruby apps, start by auditing your current state: scan git history for leaked credentials, inventory all environment variables, and classify secrets by sensitivity tier. Implement encrypted credentials immediately for application secrets, migrate production infrastructure secrets to a vault within 90 days, and establish automated rotation for all high-value credentials. If your team needs help designing a compliant secrets architecture or preparing for an upcoming audit, reach out to discuss your specific requirements. Secure configuration isn't a feature you add later—it's the foundation your production reliability depends on.