Skip to content
← DevOps · intermediate · 12 min · 07 / 08

Secrets & Configuration

Environment variables, secret managers, config as code — how to handle sensitive data without leaking it.

secretsenv varsconfigsecurity

The Configuration Spectrum

From least sensitive to most:

Real-World Analogy

Like keeping your bank PIN vs. your display name — your PIN (secret) is stored securely and never shown, while your display name (config) can be shared openly. Mixing them up is like writing your PIN on a sticky note on your monitor.

// 1. Build-time config (baked into image)
//    Feature flags, API URLs, log levels
//    Stored in: config files, committed to git

// 2. Runtime config (environment-specific)
//    Database host, cache TTL, rate limits
//    Stored in: environment variables, config maps

// 3. Secrets (never in code or logs)
//    API keys, database passwords, TLS certs
//    Stored in: secret manager (AWS Secrets Manager, Vault, etc.)

Environment Variables

// The twelve-factor app way: configure via environment
const config = {
  port: parseInt(process.env.PORT || "3000"),
  databaseUrl: process.env.DATABASE_URL!,
  redisUrl: process.env.REDIS_URL || "redis://localhost:6379",
  logLevel: process.env.LOG_LEVEL || "info",
  nodeEnv: process.env.NODE_ENV || "development",
};

// Validate at startup — fail fast if config is missing
function validateConfig(config: Record<string, unknown>): void {
  const required = ["databaseUrl"];

  for (const key of required) {
    if (!config[key]) {
      throw new Error(`Missing required config: ${key}`);
    }
  }
}

validateConfig(config);

Never commit .env files to git. Add .env to .gitignore immediately. Committed secrets stay in git history forever — even if you delete the file, anyone can find it with git log.

Secret Managers

// AWS Secrets Manager / GCP Secret Manager / HashiCorp Vault
// Store secrets centrally, rotate them automatically, audit access

import { SecretsManager } from "@aws-sdk/client-secrets-manager";

const client = new SecretsManager({ region: "us-east-1" });

async function getSecret(name: string): Promise<string> {
  const response = await client.getSecretValue({ SecretId: name });
  return response.SecretString!;
}

// Load secrets at startup
const dbPassword = await getSecret("prod/database/password");
const stripeKey = await getSecret("prod/stripe/api-key");

// Benefits over env vars:
// - Automatic rotation (e.g., rotate DB password every 30 days)
// - Audit log (who accessed which secret, when)
// - Fine-grained access control (IAM policies)
// - Encryption at rest

Kubernetes Secrets

# Create a secret
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  username: cG9zdGdyZXM=     # base64 encoded (NOT encrypted!)
  password: c3VwZXJzZWNyZXQ=

---
# Use in a pod
spec:
  containers:
    - name: api
      env:
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: username
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: password

Kubernetes Secrets are base64-encoded, not encrypted. Anyone with cluster access can read them. For real secret management, use an external secret manager (Vault, AWS Secrets Manager) with a Kubernetes operator like External Secrets.

Secret Rotation

// Secrets should be rotated regularly
// The rotation pattern:
// 1. Generate new secret
// 2. Update the application to accept both old and new
// 3. Switch to new secret
// 4. Revoke old secret

// Database password rotation (dual-password approach):
async function rotateDbPassword(): Promise<void> {
  const newPassword = generateSecurePassword();

  // 1. Set new password (old still works)
  await db.execute(`ALTER USER app_user SET PASSWORD = '${newPassword}'`);

  // 2. Update secret manager
  await secretManager.updateSecret("prod/db/password", newPassword);

  // 3. App picks up new password on next connection pool refresh
  // (or trigger a rolling restart)
}

Key Takeaways

  1. Never commit secrets to git — use .env for local dev, secret managers for production
  2. Validate config at startup — fail fast if required values are missing
  3. Use a secret manager for production — env vars don’t provide rotation, auditing, or encryption
  4. Rotate secrets regularly — automate it so it’s painless