Secrets Management for Developers: Environment Variables, Vault, and Encryption | SoniNow Blog

Limited TimeLearn More

secretssecurityvaultencryptiondevops

Secrets Management for Developers: Environment Variables, Vault, and Encryption

Published

2026-06-23

Read Time

5 mins

Secrets Management for Developers: Environment Variables, Vault, and Encryption

Hard-coded API keys in source code remain one of the most common security vulnerabilities. The problem isn't malice—it's convenience. When environment variables feel like friction, developers look for shortcuts. A good secrets management strategy makes the secure path the easy path, with automated rotation, access auditing, and zero-downtime credential changes.

Environment Variables: The Baseline

Environment variables are the minimum viable secrets management for any application. They separate configuration from code and keep secrets out of version control:

# .env file (never commit this)
DATABASE_URL=postgresql://user:password@host:5432/db
REDIS_URL=redis://:password@host:6379/0
STRIPE_API_KEY=sk_live_abc123...
GITHUB_TOKEN=ghp_def456...

The .env pattern works well for local development. Add .env to .gitignore and commit a .env.example with placeholder values and documentation:

# .env.example (committed to repo)
DATABASE_URL=postgresql://user:password@localhost:5432/app_dev
REDIS_URL=redis://localhost:6379/0
STRIPE_API_KEY=sk_test_...

For production, inject environment variables through your deployment platform: Kubernetes Secrets mounted as environment variables, platform-native secrets managers (AWS, GCP, Azure), or CI/CD secrets configured in GitHub Actions or GitLab CI.

The limitation: environment variables are static. Rotating a credential means restarting the process. There's no audit trail, no access control beyond file permissions, and no automatic rotation.

HashiCorp Vault: Dynamic Secrets and Access Control

Vault addresses environment variables' limitations with dynamic secrets, leasing, and fine-grained access policies. Applications authenticate to Vault, retrieve short-lived credentials, and renew or re-lease them automatically:

# Vault policy: app-server
path "database/creds/app" {
  capabilities = ["read"]
}

path "kv/data/app/*" {
  capabilities = ["read", "list"]
}

path "transit/encrypt/app" {
  capabilities = ["create", "update"]
}
// Application retrieves dynamic credentials at startup
const vault = require('node-vault')();

async function getDatabaseCredentials() {
  const result = await vault.read('database/creds/app');
  // Returns temporary PostgreSQL credentials with TTL
  const { username, password } = result.data;
  return { username, password };
}

async function renewLease() {
  // Re-authenticate before the TTL expires
  const result = await vault.read('database/creds/app');
  updateConnectionPool(result.data);
}

// Renew credentials every 20 minutes (lease is typically 1 hour)
setInterval(renewLease, 20 * 60 * 1000);

Vault's database secrets engine creates unique, temporary credentials for each application instance. If a credential is compromised, it expires automatically within the lease TTL (typically 1–24 hours). This eliminates the risk of long-lived database passwords.

The dynamic secrets model transforms secrets management: instead of "what if this API key leaks?", the question becomes "how quickly does it expire?".

Cloud-Native Secret Managers

AWS Secrets Manager, GCP Secret Manager, and Azure Key Vault provide managed secret storage with automatic rotation and access auditing. They integrate natively with cloud services and SDKs:

// AWS SDK v3 - retrieve and rotate secrets
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';

const client = new SecretsManagerClient({ region: 'us-east-1' });

async function getApiKey(secretName: string): Promise<string> {
  const command = new GetSecretValueCommand({ SecretId: secretName });
  const response = await client.send(command);
  return JSON.parse(response.SecretString!).api_key;
}

// Automatic rotation with Lambda
// Secrets Manager triggers a Lambda function on a schedule
// Lambda generates a new key and updates the secret
# CloudFormation - RDS password in Secrets Manager
Resources:
  DBSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: production/db-password
      Description: Master database password
      GenerateSecretString:
        SecretStringTemplate: '{"username": "admin"}'
        GenerateStringKey: "password"
        PasswordLength: 32
        ExcludeCharacters: '"@/\'

Enable automatic rotation with CloudWatch Events or scheduled triggers. RDS-backed secrets can auto-rotate the master password; API keys rotate through Lambda functions that call the external service's API.

Encrypted Configuration Files

For systems that can't connect to a secrets manager at startup, encrypted config files provide an alternative. Use age, SOPS, or git-crypt to encrypt secrets in the repository:

# secrets.yaml (encrypted with SOPS)
api_key: ENC[AES256_GCM,data:abc123...,iv:def456...,tag:ghi789...]
database_url: ENC[AES256_GCM,data:jkl012...,iv:mno345...,tag:pqr678...]
sops:
  kms:
    - arn: arn:aws:kms:us-east-1:123456789012:key/abc-123
      created_at: "2026-06-23T17:00:00Z"
      enc: "..."
  age:
    - recipient: age1abc123...
      enc: "..."
# Decrypt at deploy time using CI/CD
sops decrypt secrets.yaml > /tmp/app-config.yaml

SOPS (Secrets OPerationS) encrypts individual values in YAML, JSON, ENV, or binary files. Decryption keys come from cloud KMS, age keypairs, or PGP. The encrypted file is safe to commit to the repository, and decryption happens automatically during deployment.

Secrets Rotation Strategies

Rotate secrets on a schedule—90 days for database passwords, 180 days for API keys—or immediately after a potential compromise. Automated rotation requires:

  1. New credential generation: Create the new secret before invalidating the old one
  2. Dual-credential window: Both old and new credentials work simultaneously during rollout
  3. Application update: Incrementally update application instances to use the new secret
  4. Old credential invalidation: Remove the old secret after all instances are updated
# Application credential rotation with dual-window
class DatabaseCredentials:
    def __init__(self):
        self.current = self._load_credential("DB_PASSWORD")
        self.rotating = os.environ.get("DB_PASSWORD_NEXT")

    def get_connection_string(self):
        """Try current credential, fall back to rotating credential."""
        try:
            return self._connect(self.current)
        except AuthenticationError:
            if self.rotating:
                return self._connect(self.rotating)
            raise

The dual-credential pattern enables zero-downtime rotation. Applications connect with the primary credential and fall back to the rotating credential after a failure. This handles the transition period when some application instances have been updated and others haven't.

Audit and Monitoring

Every secrets manager should log access: who retrieved which secret, from what IP, at what time. Vault's audit logs, CloudTrail for Secrets Manager, and Key Vault diagnostics provide this data. Set alerts on:

  • Secret access from unexpected locations or IPs
  • Multiple failed authentication attempts against Vault
  • Secret retrieval outside of normal application startup windows
  • Manual secret retrieval by a human operator (should be rare)

Secure Your Secrets with SoniNow

Secrets management is a foundational security practice that every production system needs. Our engineers at SoniNow design and implement secrets management workflows that keep credentials safe, rotated, and auditable.