Operationssecretsenvironment variablessecurity

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.

R

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

TypeExampleWhere to Store
CodeBusiness logic, algorithmsGit
ConfigPort numbers, log levels, feature flagsEnvironment variables
SecretsAPI keys, database passwords, signing keysSecrets manager
InfrastructureInstance size, scaling rulesraidframe.yaml

The mistakes everyone makes

Committing .env to git

# .gitignore — add this NOW if it's not there
.env
.env.local
.env.production
*.pem
*.key

If 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 preview

Hardcoding 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.

Start free

Managing secrets on RaidFrame

Set secrets

rf secrets set DATABASE_PASSWORD=supersecure123 STRIPE_KEY=sk_live_xxx

Secrets 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 list
KEY                    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.yaml

Secret values are masked. Use rf env get STRIPE_KEY to reveal (requires appropriate role).

Pull to local development

rf env pull --env staging > .env.local

Downloads 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 production

Sync between environments

rf env copy --from staging --to production --keys STRIPE_KEY,WEBHOOK_SECRET

Secret rotation

Manual rotation

rf secrets rotate DATABASE_PASSWORD
Rotating 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 period

Automatic rotation

security:
  secrets_rotation:
    DATABASE_PASSWORD:
      interval: 30d
      auto: true
    REDIS_PASSWORD:
      interval: 30d
      auto: true

Patterns 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://xxx

Signing 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 7d
TIME         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?

  1. Rotate the secret immediately (rf secrets rotate KEY or regenerate in the provider's dashboard)
  2. Remove the file from git history (git filter-branch or BFG Repo-Cleaner)
  3. Force-push the cleaned history
  4. 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:read

Use $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.

secretsenvironment variablessecurityDevOpsproduction

Ship faster with RaidFrame

Auto-scaling compute, managed databases, global CDN, and zero-config CI/CD. Free tier included.