Environment Variables and Secrets Management in Production
How to manage API keys, database passwords, and secrets across environments without leaking them. Patterns, tools, and the mistakes everyone makes.
RaidFrame Team
January 30, 2026 · 5 min read
TL;DR — Never commit secrets to git. Use environment variables for configuration and a secrets manager for sensitive credentials. Rotate secrets regularly. Use different values per environment. On RaidFrame, secrets are encrypted at rest, injected at runtime, and can be rotated without downtime.
The hierarchy of configuration
| Type | Example | Where to Store |
|---|---|---|
| Code | Business logic, algorithms | Git |
| Config | Port numbers, log levels, feature flags | Environment variables |
| Secrets | API keys, database passwords, signing keys | Secrets manager |
| Infrastructure | Instance size, scaling rules | raidframe.yaml |
The mistakes everyone makes
Committing .env to git
# .gitignore — add this NOW if it's not there
.env
.env.local
.env.production
*.pem
*.keyIf you've already committed secrets, they're in git history forever. Rotate every secret that was exposed, even if you removed the file.
Sharing secrets over Slack/email
That message lives in Slack's database, backups, and anyone with channel access can see it. Use a secrets manager or rf env set instead.
Same secrets in all environments
Production and staging should never share API keys. A bug in staging shouldn't charge real customers or send real emails.
rf env set STRIPE_KEY=sk_live_xxx --env production
rf env set STRIPE_KEY=sk_test_xxx --env staging
rf env set STRIPE_KEY=sk_test_xxx --env previewHardcoding secrets in Dockerfiles
# NEVER do this
ENV API_KEY=sk_live_xxx
# Do this instead
# (set at runtime via rf env set)Try RaidFrame free
Deploy your first app in 60 seconds. No credit card required.
Managing secrets on RaidFrame
Set secrets
rf secrets set DATABASE_PASSWORD=supersecure123 STRIPE_KEY=sk_live_xxxSecrets are encrypted with AES-256-GCM, stored per-environment, and injected at runtime. They never appear in build logs, Docker images, or rf env list output.
View secrets
rf env listKEY VALUE SET BY
DATABASE_URL postgresql://... system (auto-injected)
REDIS_URL redis://... system (auto-injected)
STRIPE_KEY **** [email protected]
API_SECRET **** [email protected]
NODE_ENV production raidframe.yamlSecret values are masked. Use rf env get STRIPE_KEY to reveal (requires appropriate role).
Pull to local development
rf env pull --env staging > .env.localDownloads all environment variables (including revealed secrets) to a local .env.local file. This file is gitignored by default.
Push from local
rf env push --file .env.production --env productionSync between environments
rf env copy --from staging --to production --keys STRIPE_KEY,WEBHOOK_SECRETSecret rotation
Manual rotation
rf secrets rotate DATABASE_PASSWORDRotating DATABASE_PASSWORD...
1. Generated new password
2. Updated database user credentials
3. Updated environment variable
4. Restarted services (rolling, zero downtime)
5. Verified connectivity
✓ Rotated successfully
Old credential invalidated after 30s grace periodAutomatic rotation
security:
secrets_rotation:
DATABASE_PASSWORD:
interval: 30d
auto: true
REDIS_PASSWORD:
interval: 30d
auto: truePatterns for specific secrets
Database credentials
Don't set DATABASE_URL manually. When you rf add postgres, the connection string is auto-injected and rotated automatically.
API keys for third-party services
rf secrets set STRIPE_KEY=sk_live_xxx RESEND_KEY=re_xxx SENTRY_DSN=https://xxxSigning keys (JWT, cookies)
Generate a strong random key:
rf secrets set JWT_SECRET=$(openssl rand -hex 32)
rf secrets set COOKIE_SECRET=$(openssl rand -hex 32)Encryption keys
For application-level encryption (encrypting user data at rest):
rf secrets set ENCRYPTION_KEY=$(openssl rand -base64 32)Rotate by supporting multiple keys — decrypt with old key, encrypt with new key, migrate over time.
Audit trail
Every secret access is logged:
rf audit list --action "env.*" --last 7dTIME ACTOR ACTION KEY
14:23 [email protected] env.set STRIPE_KEY (production)
14:10 ci-pipeline env.get DATABASE_URL (production)
13:45 [email protected] env.pull staging (12 vars)FAQ
Should I use a .env file in production?
No. Use your platform's environment variable management. .env files are for local development only. On RaidFrame, use rf env set or raidframe.yaml.
How do I share secrets with my team?
Don't share secrets directly. Give team members access to the project on RaidFrame and they can rf env pull to get the values they need, scoped to their role.
What if I accidentally commit a secret?
- Rotate the secret immediately (
rf secrets rotate KEYor regenerate in the provider's dashboard) - Remove the file from git history (
git filter-branchorBFG Repo-Cleaner) - Force-push the cleaned history
- Assume the secret is compromised — don't just remove it, replace it
How do secrets work in CI/CD?
Create a scoped API token and set it as a CI secret:
rf auth token create --name "CI Pipeline" --scope deploy,env:readUse $RAIDFRAME_TOKEN in your CI environment. The pipeline can deploy and read env vars but not modify them.
Should I encrypt secrets in my application too?
Only if you're storing user-sensitive data (PII, payment info). For API keys and infrastructure secrets, the platform-level encryption is sufficient.
Related reading
Ship faster with RaidFrame
Auto-scaling compute, managed databases, global CDN, and zero-config CI/CD. Free tier included.