Back to Blog
Guide7 min read2026-05-01

Secrets Management: How to Handle Credentials in Production

Stop hardcoding credentials. Learn the right way to manage API keys, database passwords, and secrets across all environments.

Secrets Management: How to Handle Credentials in Production

Hardcoded credentials are one of the most common and most avoidable security vulnerabilities. GitHub's secret scanning detects millions of exposed credentials per year — API keys, database passwords, and private keys committed to public (and private) repositories. Once a secret is in git history, it's effectively compromised.

This guide covers the correct approaches to secrets management for every stage of your application lifecycle.

What Counts as a Secret?

  • Database passwords and connection strings
  • API keys (Stripe, Mailgun, Twilio, etc.)
  • JWT signing secrets
  • OAuth client secrets
  • SSH private keys
  • TLS certificates and private keys
  • Cloud provider access keys (AWS, GCP)
  • Encryption keys

If it grants access to something, it's a secret.

The Cardinal Rules

  1. 1Never commit secrets to git — not even in private repositories
  2. 2Never log secrets — even to internal systems
  3. 3Never hardcode secrets — not in source code, not in config files, not in Dockerfiles
  4. 4Rotate secrets regularly — and immediately after suspected compromise

Step 1: Use Environment Variables for Local Development

The simplest baseline: store secrets in a .env file locally and load them with a library like dotenv.

# .env (LOCAL ONLY — never commit this file)
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=your-local-dev-secret
STRIPE_SECRET_KEY=sk_test_xxxx
require('dotenv').config();

const dbUrl = process.env.DATABASE_URL;
const jwtSecret = process.env.JWT_SECRET;

Always add .env to .gitignore:

echo ".env" >> .gitignore

Commit a .env.example with placeholder values to document what's needed — never actual values.

Step 2: Scan for Accidentally Committed Secrets

Install git-secrets or use GitHub's built-in secret scanning:

# Install git-secrets
brew install git-secrets
git secrets --install
git secrets --register-aws

# Scan existing history
git secrets --scan-history

If you find a committed secret: rotate it immediately (treat it as compromised), then remove it from history with git filter-branch or git filter-repo.

Step 3: Use a Secrets Manager in Production

Environment variables on a server are fine as a transport mechanism, but the secrets still need to live somewhere secure before injection. Use a dedicated secrets manager:

AWS Secrets Manager:

const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');

const client = new SecretsManagerClient({ region: 'us-east-1' });
const response = await client.send(
  new GetSecretValueCommand({ SecretId: 'prod/myapp/database' })
);
const secret = JSON.parse(response.SecretString);

HashiCorp Vault:

# Write a secret
vault kv put secret/myapp/database url="postgresql://..."

# Read in application
vault kv get -field=url secret/myapp/database

Step 4: Inject Secrets at Runtime on PandaStack

PandaStack provides built-in encrypted secrets management. Set environment variables via the CLI and they are stored encrypted, injected at container startup, and never exposed in logs or image layers:

npm install -g @pandastack/cli

# Set secrets for an app
panda env:set DATABASE_URL="postgresql://..." --app my-app
panda env:set STRIPE_SECRET_KEY="sk_live_..." --app my-app

# List (shows keys, not values)
panda env:list --app my-app

Or manage secrets in the dashboard at [dashboard.pandastack.io](https://dashboard.pandastack.io).

Step 5: Scope Secrets to Environments

Use different secrets for development, staging, and production. Never use production secrets locally:

# Development
panda env:set STRIPE_SECRET_KEY="sk_test_..." --app my-app --env development

# Production
panda env:set STRIPE_SECRET_KEY="sk_live_..." --app my-app --env production

Step 6: Rotate Secrets Regularly

Define a rotation schedule and automate it:

# Example: rotate database password
NEW_PASSWORD=$(openssl rand -base64 32)

# Update in database
psql -c "ALTER USER app_user PASSWORD '$NEW_PASSWORD';"

# Update in secrets manager
panda env:set DATABASE_PASSWORD="$NEW_PASSWORD" --app my-app

# Redeploy to pick up new value
panda deploy --app my-app

Step 7: Audit Secret Access

Log every time a secret is accessed or modified. This is a built-in feature of dedicated secrets managers — a critical capability for compliance (SOC 2, PCI DSS, HIPAA).

Step 8: Use Least-Privilege API Keys

When generating API keys for third-party services, grant only the permissions the application needs:

  • Stripe: read-only key for reporting dashboards, full key only for payment processing services
  • AWS: IAM role with only the S3 buckets and actions the app requires
  • GitHub: fine-grained token scoped to specific repositories

Summary

Good secrets management is: never commit secrets, use .env locally with .gitignore, scan for leaked secrets, use a secrets manager in production, inject at runtime with encryption, scope to environments, rotate regularly, and audit access. PandaStack handles this for your deployed apps — see [docs.pandastack.io](https://docs.pandastack.io) for the full guide.

Ready to deploy?

Start free on PandaStack — no credit card required.

Start free on PandaStack

More in Guide

Browse all Guide articles →

See also