Secret Management Tools: Vault vs AWS Secrets Manager vs Doppler


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.