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 {
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
-
HashiCorp Vault : Best for multi-cloud, on-premise, or regulated environments requiring dynamic secrets and encryption-as-a-service. Highest operational overhead.
-
AWS Secrets Manager : Best for AWS-native workloads where simplicity and managed rotation are priorities. Limited outside AWS.
-
Doppler : Best for developer-focused teams wanting a simple, cross-platform secret management solution with minimal operational overhead.
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.
Enjoy this article? Share your thoughts, questions, or experiences in the comments below — your insights help other readers too.
Join the discussion ↓