Introduction
Managing database credentials, API keys, and TLS certificates is one of the most critical security challenges in modern infrastructure. Hard-coded secrets in configuration files or environment variables are a leading cause of data breaches. Dedicated secret management tools provide encryption, access control, rotation, and audit trails. This article compares HashiCorp Vault, AWS Secrets Manager, and Doppler across the dimensions that matter in production.
HashiCorp Vault
Vault offers the most comprehensive feature set with dynamic secrets, encryption-as-a-service, and multi-cloud support:
# Vault configuration
storage "raft" {
path = "/vault/data"
node_id = "node1"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = false
tls_cert_file = "/vault/certs/cert.pem"
tls_key_file = "/vault/certs/key.pem"
}
seal "awskms" {
region = "us-east-1"
kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/abc123"
}
api_addr = "https://vault.example.com:8200"
cluster_addr = "https://vault.example.com:8201"
Dynamic Database Secrets
Vault can generate temporary database credentials on demand, eliminating long-lived credentials:
# Configure database backend
path "database/creds/my-role" {
capabilities = ["read"]
}
# Generate ephemeral PostgreSQL credentials
vault read database/creds/payment-app
# Key Value
# --- -----
# lease_id database/creds/payment-app/abc123
# lease_duration 1h
# lease_renewable true
# password aB3x...kL9p
# username v-token-payment-app-x7Yz...
Application integration:
// Vault sidecar pattern
package main
import (
"github.com/hashicorp/vault/api"
)
type DynamicCredentials struct {
client *api.Client
}
func (d *DynamicCredentials) GetCredentials() (string, string, error) {
secret, err := d.client.Logical().Read("database/creds/payment-app")
if err != nil {
return "", "", err
}
username := secret.Data["username"].(string)
password := secret.Data["password"].(string)
// Schedule renewal before lease expires
go d.renewLease(secret.LeaseDuration)
return username, password, nil
}
func (d *DynamicCredentials) renewLease(duration int) {
// Renew at 50% of lease duration
time.Sleep(time.Duration(duration/2) * time.Second)
d.client.Sys().Renew("database/creds/payment-app", duration)
}
AWS Secrets Manager
Secrets Manager integrates natively with the AWS ecosystem:
# CloudFormation: Create a secret with rotation
Resources:
DatabaseSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: payment/db-credentials
Description: "Payment database credentials"
GenerateSecretString:
SecretStringTemplate: '{"username": "payment_app"}'
GenerateStringKey: "password"
PasswordLength: 32
ExcludeCharacters: "@%*"
RotationSchedule:
RotationLambdaARN: !GetAtt RotationLambda.Arn
RotationSchedule:
Duration: "7d"
Tags:
- Key: Environment
Value: Production
Application retrieval using the SDK:
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
const client = new SecretsManagerClient({ region: "us-east-1" });
async function getDbConfig(): Promise<DbConfig> {
const response = await client.send(new GetSecretValueCommand({
SecretId: "payment/db-credentials",
}));
const secret = JSON.parse(response.SecretString!);
return {
host: process.env.DB_HOST,
username: secret.username,
password: secret.password,
database: process.env.DB_NAME,
};
}
Doppler
Doppler provides a developer-friendly approach with workspace-based secret management:
# CLI workflow
doppler setup --project payment-service --config prd
# Fetch secrets locally
doppler secrets substitute < config.yaml > config.resolved.yaml
# Run an application with secrets injected
doppler run -- npm start
# doppler.yaml for project
setup:
project: payment-service
configs:
- dev
- stg
- prd
environments:
- name: dev
secrets:
DB_CONNECTION_STRING: postgres://dev_user:dev_pass@localhost:5432/payment
STRIPE_API_KEY: sk_test_***
JWT_SECRET: dev-secret-key
- name: prd
secrets:
DB_CONNECTION_STRING: doppler://payment-service/prd/db_connection_string
STRIPE_API_KEY: doppler://payment-service/prd/stripe_api_key
CI/CD integration:
# GitHub Actions with Doppler
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dopplerhq/cli-action@v3
- name: Build with secrets
run: |
doppler secrets substitute --project payment-service --config prd \
--token ${{ secrets.DOPPLER_TOKEN }} \
< docker-compose.yml > docker-compose.resolved.yml
- name: Deploy
run: docker compose -f docker-compose.resolved.yml up -d
Secret Rotation
| Tool | Rotation Method | Automation | Audit Trail |
|---|---|---|---|
| Vault | Dynamic secrets (auto-expire) | Built-in via leases | Audit device |
| AWS Secrets Manager | Lambda-based rotation | Scheduled rotation | CloudTrail |
| Doppler | API key re-generation | Manual + API | Change log |
Vault's dynamic secrets are the gold standard because credentials automatically expire, eliminating the need for rotation entirely. AWS Secrets Manager supports automated rotation but requires custom Lambda functions. Doppler relies on manual intervention or API-driven rotation.
Encryption and Architecture
# Vault: Enterprise auto-unseal with AWS KMS
seal "awskms" {
region = "us-east-1"
kms_key_id = "arn:aws:kms:...:key/abc123"
}
# How a high-availability Vault cluster works:
# 1. Vault starts in sealed state
# 2. Unseal key split across 5 keys (3 quorum required)
# 3. OR: auto-unseal via cloud KMS
# 4. Vault encrypts data with AES-256-GCM
# 5. Encryption key wrapped by master key
# 6. Master key wrapped by unseal key
Disaster Recovery
# Vault: DR replication across regions
path "sys/replication/dr/primary/enable" {
capabilities = ["update", "sudo"]
}
# Perform DR failover
# vault operator raft snapshot restore snapshot.snap
# vault operator unseal
AWS Secrets Manager and Doppler benefit from the cloud provider's redundancy naturally. Vault requires explicit replication configuration for multi-region availability.
Integration Patterns
| Pattern | Vault | AWS Secrets Manager | Doppler |
|---|---|---|---|
| Sidecar injector | Vault Agent Injector (K8s) | ASCP (plugin) | CLI wrapper |
| SDK access | Go, Python, Java, Node | All AWS SDKs | Any (CLI) |
| Kubernetes | CSI driver, Injector | Secrets Store CSI | Doppler K8s Operator |
Decision Guide
Start with a tool that matches your current scale. Vault's complexity is only justified when you need its dynamic secret capabilities or operate across multiple cloud providers.