Choosing Your Secrets Manager: 10+ Tools Compared for Security and Scale
A comprehensive comparison of 10+ secrets management providers. From AWS Secrets Manager to Infisical, we break down pricing, security, DX, and when to use each. Plus: hybrid approaches and cost analysis for startups vs enterprises.
Ajit Kumar
Creator, evnx
If you've ever shipped a project with a .env file containing production API keys, you know the panic that follows. We've all been there—committing secrets to Git (even accidentally), emailing passwords to teammates, or wrestling with "works on my machine" issues.
Managing environment variables and secrets is one of those problems that seems trivial until it isn't. What starts as a simple text file quickly becomes a security nightmare as your team grows and your infrastructure scales.
In this guide, we'll compare 10+ popular solutions for managing secrets in the cloud, from AWS Secrets Manager to emerging players like Infisical and Doppler. We'll break down real costs, implementation examples, and help you choose the right tool for your stack.
The reality check
If you're still using .env files in production without encryption or access controls, stop reading and scroll to the "When to Migrate" section. Your future self will thank you.
The Problem: Why .env Files Don't Scale
Let's start with the basics. A .env file is a simple key-value store used to configure applications:
# .env
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
API_KEY=sk_live_abc123xyz789
JWT_SECRET=super_secret_key_do_not_share
REDIS_PASSWORD=redis_prod_password_2026This works great for local development. But consider what happens when:
- ›Multiple developers need access (do you Slack the file?)
- ›Production deployments require different values (how do you sync?)
- ›Security audits demand rotation policies (who's tracking this?)
- ›Compliance requirements mandate encryption at rest (is your Git repo encrypted?)
# The moment you realize you've made a mistake
$ git push
Enumerating objects: 15, done.
Counting objects: 100% (15/15), done.
Delta compression using up to 8 threads
Compressing objects: 100% (8/8), done.
Writing objects: 100% (9/9), 1.23 KiB | 1.23 MiB/s, done.
Total 9 (delta 6), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (6/6), completed with 3 local objects.
To github.com:your-org/your-repo.git
8a3f2b1..9c4e5d6 main -> main
# Three minutes later...
Email from GitHub: "AWS credentials detected in your repository"Never commit .env files
Even in private repos, this creates a single point of failure and makes secret rotation nearly impossible. GitHub scans public repos in real-time; private repos are scanned too.
The Landscape: 10 Secrets Management Providers
Here's the complete comparison matrix:
| Provider | Type | Price (Starting) | Setup Time | Best For |
|---|---|---|---|---|
| AWS Secrets Manager | Managed Service | $0.40/secret/mo | 15 min | AWS-native teams |
| Azure Key Vault | Managed Service | $1/month + ops | 20 min | Azure/Microsoft stack |
| GCP Secret Manager | Managed Service | $0.06/secret/mo | 15 min | GCP workloads |
| HashiCorp Vault | Self-hosted/SaaS | Free / $30k/yr | 2-4 hours | Security-first orgs |
| Doppler | SaaS | Free / $25/mo | 5 min | Developer experience |
| Infisical | Open-source/SaaS | Free / $10/mo | 10 min | Modern teams, GitOps |
| 1Password Secrets | SaaS | $3/user/mo | 10 min | Teams already using 1Password |
| CyberArk Conjur | Enterprise | Custom | 1-2 weeks | Large enterprises, compliance |
| GitLab/GitHub Secrets | Built-in | Included | 5 min | CI/CD-focused workflows |
| SOPS (Mozilla) | CLI Tool | Free | 30 min | GitOps, encryption at rest |
Quick decision guide
- ›Solo dev or startup: Start with Infisical or Doppler (free tiers)
- ›AWS-heavy: AWS Secrets Manager
- ›Multi-cloud: Vault or Infisical
- ›Enterprise compliance: CyberArk or Vault Enterprise
- ›GitOps workflow: SOPS + Age/GPG
Deep Dive: The Top Contenders
1. AWS Secrets Manager
AWS Secrets Manager is the managed service option for teams already invested in the AWS ecosystem.
Key Features:
- ›Automatic rotation with Lambda
- ›Integration with RDS, Redshift, DocumentDB
- ›Fine-grained IAM policies
- ›Encryption using KMS
Implementation Example:
// lib/secrets.ts
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
const client = new SecretsManagerClient({
region: process.env.AWS_REGION || "us-east-1"
});
export async function getSecret(secretName: string): Promise<Record<string, string>> {
const command = new GetSecretValueCommand({ SecretId: secretName });
const response = await client.send(command);
if (!response.SecretString) {
throw new Error("Secret is empty or binary");
}
return JSON.parse(response.SecretString);
}
// Usage in your app
export async function getDatabaseConfig() {
const secrets = await getSecret("prod/db/credentials");
return {
host: secrets.host,
user: secrets.username,
password: secrets.password,
database: secrets.dbname
};
}Cost Breakdown:
- ›$0.40 per secret per month
- ›$0.05 per 10,000 API calls
- ›Example: 50 secrets + 100k calls/month = $20 + $0.50 = $20.50/month
Pro tip
Use secret versioning to maintain history. AWS keeps up to 100 versions per secret, which is invaluable for audit trails and rollbacks.
2. Azure Key Vault
Azure Key Vault provides cloud-based secret management for Microsoft Azure workloads.
Key Features:
- ›Hardware Security Modules (HSM) support
- ›Integration with Azure Active Directory
- ›Certificate management
- ›Key management for encryption
Implementation Example:
// lib/azure-secrets.ts
import { DefaultAzureCredential } from "@azure/identity";
import { SecretClient } from "@azure/keyvault-secrets";
const credential = new DefaultAzureCredential();
const vaultName = process.env.AZURE_KEY_VAULT_NAME!;
const url = `https://${vaultName}.vault.azure.net`;
const client = new SecretClient(url, credential);
export async function getSecret(secretName: string): Promise<string> {
const latestSecret = await client.getSecret(secretName);
return latestSecret.value!;
}
export async function setSecret(secretName: string, value: string): Promise<void> {
await client.setSecret(secretName, value);
}
// Usage
export async function getConfig() {
return {
databaseUrl: await getSecret("database-url"),
apiKey: await getSecret("api-key"),
jwtSecret: await getSecret("jwt-secret")
};
}Cost Breakdown:
- ›Standard tier: $1/month per vault
- ›Premium tier (HSM): $6/month per vault
- ›Operations: $0.03 per 10,000 transactions
- ›Example: 1 vault + 50k operations = $1 + $0.15 = $1.15/month
3. Google Cloud Secret Manager
GCP's native secrets management solution with strong integration with Google Cloud services.
Key Features:
- ›Automatic replication across regions
- ›Version management
- ›IAM integration
- ›Audit logging
Implementation Example:
// lib/gcp-secrets.ts
import { SecretManagerServiceClient } from "@google-cloud/secret-manager";
const client = new SecretManagerServiceClient();
const projectId = process.env.GOOGLE_CLOUD_PROJECT!;
export async function accessSecret(secretId: string, version = "latest"): Promise<string> {
const name = `projects/${projectId}/secrets/${secretId}/versions/${version}`;
const [versionResponse] = await client.accessSecretVersion({ name });
return versionResponse.payload?.data?.toString() || "";
}
export async function createSecret(secretId: string, value: string): Promise<void> {
const parent = `projects/${projectId}`;
// Create the secret if it doesn't exist
await client.createSecret({
parent,
secretId,
secret: {
replication: {
automatic: {}
}
}
});
// Add the first version
await client.addSecretVersion({
parent: `projects/${projectId}/secrets/${secretId}`,
payload: {
data: Buffer.from(value)
}
});
}
// Usage with caching
const secretCache = new Map<string, string>();
export async function getCachedSecret(secretId: string): Promise<string> {
if (secretCache.has(secretId)) {
return secretCache.get(secretId)!;
}
const value = await accessSecret(secretId);
secretCache.set(secretId, value);
return value;
}Cost Breakdown:
- ›$0.06 per secret per month
- ›$0.03 per 10,000 API calls
- ›Example: 50 secrets + 100k calls = $3 + $0.30 = $3.30/month
4. HashiCorp Vault
Vault is the gold standard for secrets management, offering dynamic secrets, encryption-as-a-service, and identity-based access control.
Key Features:
- ›Dynamic secrets (generate DB creds on-demand)
- ›Secret leasing and revocation
- ›Multiple auth methods (LDAP, OIDC, Kubernetes, etc.)
- ›Transit secrets engine (encryption as a service)
Implementation Example:
// lib/vault.ts
import Vault from "node-vault";
const vault = Vault({
endpoint: process.env.VAULT_ADDR,
token: process.env.VAULT_TOKEN,
});
export async function getDatabaseCredentials(): Promise<{
username: string;
password: string;
}> {
// Dynamic secrets - these are generated on-demand
const result = await vault.read("database/creds/readonly");
return {
username: result.data.username,
password: result.data.password,
};
}
// Auto-revoke after use
export async function withDbCredentials<T>(
fn: (creds: { username: string; password: string }) => Promise<T>
): Promise<T> {
const creds = await getDatabaseCredentials();
const leaseId = (await vault.read("database/creds/readonly")).lease_id;
try {
return await fn(creds);
} finally {
// Revoke immediately after use
await vault.revoke({ lease_id: leaseId });
}
}
// Example: Using Vault's KV engine
export async function getAppConfig(appName: string): Promise<Record<string, string>> {
const result = await vault.read(`kv/data/${appName}`);
return result.data.data; // KV v2 wraps data in another data object
}Cost Breakdown:
- ›Open Source: Free (but you pay for infrastructure + ops time)
- ›Enterprise: Starting at ~$30,000/year
- ›Hidden costs: 20-40 hours/month for maintenance, updates, monitoring
Operational overhead
Vault is powerful but complex. You're responsible for high availability, backups, unsealing, and upgrades. Only choose self-hosted Vault if you have dedicated DevOps resources.
5. Doppler
Doppler is a developer-first secrets management platform that focuses on ease of use and great DX.
Key Features:
- ›Universal secrets management
- ›Environment sync across platforms
- ›CLI and SDKs for all major languages
- ›Audit logs and compliance
Implementation Example:
// lib/doppler-config.ts
import { DopplerClient } from "@doppler/node";
const doppler = new DopplerClient({
token: process.env.DOPPLER_TOKEN,
});
export async function getSecrets(project = "myapp", environment = "production") {
const response = await doppler.secrets.get({
project,
environment,
});
return response.secrets;
}
// Usage with TypeScript types
interface AppConfig {
DATABASE_URL: string;
API_KEY: string;
JWT_SECRET: string;
REDIS_URL: string;
}
export async function getConfig(): Promise<AppConfig> {
const secrets = await getSecrets();
return {
DATABASE_URL: secrets.DATABASE_URL.value,
API_KEY: secrets.API_KEY.value,
JWT_SECRET: secrets.JWT_SECRET.value,
REDIS_URL: secrets.REDIS_URL.value,
};
}CLI Workflow:
# Install Doppler CLI
curl -Ls --tlsv1.2 --proto "=https" --retry 3 \
https://cli.doppler.com/install.sh | sh
# Login
doppler login
# Setup project
doppler projects setup
# Run with secrets injected
doppler run -- npm run start
# Export to .env (for local dev)
doppler secrets download --format env > .envCost Breakdown:
- ›Free: Up to 5 users, 3 projects, 100 secrets
- ›Starter: $25/month (unlimited secrets, 10 users)
- ›Team: $50/month (SSO, audit logs, 25 users)
- ›Enterprise: Custom pricing
6. Infisical
Infisical is an open-source, end-to-end encrypted platform for secrets management with a strong focus on developer experience and GitOps.
Key Features:
- ›End-to-end encryption
- ›GitOps-friendly
- ›Auto-sync across environments
- ›Open-source (self-host or cloud)
Implementation Example:
// lib/infisical.ts
import { InfisicalClient } from "@infisical/sdk";
const client = new InfisicalClient({
siteUrl: "https://app.infisical.com", // or your self-hosted URL
token: process.env.INFISICAL_TOKEN!,
});
export async function getSecrets(environment = "production") {
const secrets = await client.getSecrets({
environment,
projectId: process.env.INFISICAL_PROJECT_ID!,
});
return secrets.reduce((acc, secret) => {
acc[secret.secretKey] = secret.secretValue;
return acc;
}, {} as Record<string, string>);
}
// Usage with automatic refresh
export class SecretManager {
private cache: Record<string, string> = {};
private lastFetch: number = 0;
private readonly CACHE_TTL = 300000; // 5 minutes
async get(key: string): Promise<string> {
const now = Date.now();
if (now - this.lastFetch > this.CACHE_TTL) {
this.cache = await getSecrets();
this.lastFetch = now;
}
return this.cache[key];
}
invalidate() {
this.lastFetch = 0;
}
}CLI Workflow:
# Install Infisical CLI
npm install -g @infisical/cli
# Login
infisical login
# Initialize project
infisical init
# Run with secrets
infisical run --env=production -- npm run start
# Import from .env
infisical secrets import --env=development --file-path=.env.localCost Breakdown:
- ›Free (Cloud): Up to 5 users, unlimited secrets
- ›Team: $10/user/month (SSO, audit logs)
- ›Enterprise: Custom (self-hosting, advanced compliance)
- ›Self-hosted: Free (open-source)
Why Infisical stands out
Infisical offers end-to-end encryption, meaning even Infisical can't read your secrets. They also have excellent Kubernetes integration and a growing ecosystem of integrations.
7. 1Password Secrets Automation
If your team already uses 1Password, their Secrets Automation feature provides a natural extension for application secrets.
Key Features:
- ›Leverages existing 1Password infrastructure
- ›Connect CLI for local development
- ›Service accounts for applications
- ›Audit trails
Implementation Example:
// lib/1password.ts
import { Client } from "@1password/secrets";
const client = await Client.create({
serviceAccountToken: process.env.OP_SERVICE_ACCOUNT_TOKEN!,
});
export async function getSecret(secretPath: string): Promise<string> {
const secret = await client.getSecret(secretPath);
return secret.value;
}
// Usage
export async function getConfig() {
return {
databaseUrl: await getSecret("apps/myapp/database-url"),
apiKey: await getSecret("apps/myapp/api-key"),
jwtSecret: await getSecret("apps/myapp/jwt-secret"),
};
}CLI Workflow:
# Install Connect CLI
curl -fsSL https://1password.com/downloads/connect-cli.sh | sh
# Login
op signin
# List secrets
op item list --vault "My App Secrets"
# Get secret
op read "op://My App Secrets/database-url/password"
# Run with secrets
op run -- npm run startCost Breakdown:
- ›Requires 1Password Business account: $19.95/user/month
- ›Secrets Automation included in Business plan
- ›Additional cost if you don't already use 1Password
8. CyberArk Conjur
CyberArk Conjur is an enterprise-grade secrets management solution designed for large organizations with strict compliance requirements.
Key Features:
- ›Enterprise security and compliance
- ›Policy-based access control
- ›Integration with CI/CD pipelines
- ›Audit and compliance reporting
Implementation Example:
// lib/conjur.ts
import { ConjurClient } from "@cyberark/conjur";
const client = new ConjurClient({
conjurUrl: process.env.CONJUR_URL!,
conjurAccount: process.env.CONJUR_ACCOUNT!,
credentials: {
login: process.env.CONJUR_LOGIN!,
apiKey: process.env.CONJUR_API_KEY!,
},
});
export async function getSecret(secretId: string): Promise<string> {
const secret = await client.retrieveSecret(secretId);
return secret;
}
// Usage with policy enforcement
export async function getDatabaseCredentials() {
const username = await getSecret("prod/database/username");
const password = await getSecret("prod/database/password");
return { username, password };
}Cost Breakdown:
- ›Custom enterprise pricing
- ›Typically starts at $50,000+/year
- ›Requires dedicated infrastructure and ops team
- ›Best for: Fortune 500, regulated industries (finance, healthcare)
Enterprise complexity
Conjur is powerful but requires significant setup and maintenance. Only consider if you have dedicated security teams and compliance requirements that demand it.
9. GitLab CI/CD Variables & GitHub Secrets
Built-in secrets management for CI/CD workflows, perfect for teams heavily invested in these platforms.
GitLab CI/CD Variables Example:
# .gitlab-ci.yml
variables:
DATABASE_URL: $DATABASE_URL # Protected variable
API_KEY: $API_KEY
stages:
- build
- deploy
build:
stage: build
script:
- npm install
- npm run build
variables:
NODE_ENV: production
deploy:
stage: deploy
script:
- echo "Deploying with API key: $API_KEY"
- ./deploy.sh
environment: production
only:
- mainGitHub Actions Secrets Example:
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
- name: Deploy
run: ./deploy.sh
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}Cost Breakdown:
- ›GitLab: Included in all plans (Free, Premium $19/user/mo, Ultimate $99/user/mo)
- ›GitHub: Included in all plans (Free, Team $4/user/mo, Enterprise $21/user/mo)
- ›Environment-specific secrets require Premium/Team plans or higher
10. SOPS (Secrets OPerationS)
Mozilla's SOPS is a CLI tool for managing encrypted files, perfect for GitOps workflows.
Key Features:
- ›Encrypts values in YAML, JSON, ENV, and INI files
- ›Supports AWS KMS, GCP KMS, Azure Key Vault, HashiCorp Vault, and PGP
- ›Git-friendly (encrypts values, not keys)
- ›Works with existing CI/CD pipelines
Implementation Example:
# config.enc.yaml (encrypted file - safe to commit)
database:
url: ENC[AES256_GCM,data:abc123...,iv:xyz789...,tag:tag123,type:str]
username: ENC[AES256_GCM,data:def456...,iv:uvw012...,tag:tag456,type:str]
password: ENC[AES256_GCM,data:ghi789...,iv:rst345...,tag:tag789,type:str]
api:
key: ENC[AES256_GCM,data:jkl012...,iv:opq678...,tag:tag012,type:str]# Create .sops.yaml configuration
cat > .sops.yaml <<EOF
creation_rules:
- path_regex: .*\.enc\.yaml$
kms: "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
age: age1yhm3g6w8q8z9x0y1w2v3u4t5s6r7q8p9o0n1m2l3k4j5i6h7g8f9e0d
EOF
# Encrypt a file
sops -e config.yaml > config.enc.yaml
# Decrypt a file
sops -d config.enc.yaml > config.yaml
# Edit encrypted file directly
sops config.enc.yaml
# Use in CI/CD
sops -d config.enc.yaml | kubectl apply -f -Cost Breakdown:
- ›Free and open-source
- ›You pay for the KMS service you use (AWS KMS, GCP KMS, etc.)
- ›Example: AWS KMS costs ~$1/month per key + $0.03 per 10,000 operations
GitOps gold standard
SOPS is the go-to choice for GitOps workflows. You can commit encrypted secrets to Git safely, and decrypt them at deployment time.
When to Use Local .env vs Cloud Secrets
Not every project needs a full-blown secrets management solution. Here's a decision framework:
Stick with Local .env When:
- ›Solo projects or prototypes: You're the only one touching the code
- ›Local development: Development databases, mock APIs, feature flags
- ›Public/open-source repos: Where secrets should never exist anyway
- ›Short-lived experiments: Weekend hacks, proof-of-concepts
# .env.local - Safe for local dev
NODE_ENV=development
DEBUG=true
MOCK_API_URL=http://localhost:3001
DATABASE_URL=postgres://localhost:5432/myapp_devMove to Cloud Secrets When:
- ›Team collaboration: 2+ developers need access
- ›Production deployments: Real user data, payment processing
- ›Compliance requirements: SOC2, HIPAA, GDPR
- ›Secret rotation: Security policies require regular key changes
- ›Audit trails: You need to know who accessed what and when
# .env.production - Should NEVER be in your repo
DATABASE_URL=postgres://prod-user:REDACTED@prod-db:5432/app
STRIPE_SECRET_KEY=sk_live_...
AWS_SECRET_ACCESS_KEY=...Red flags that mean migrate now
- ›You're sharing
.envfiles via Slack/Email - ›You have "dev", "staging", and "prod" in separate
.envfiles - ›You can't remember when you last rotated a production API key
- ›New developers ask "where do I get the .env file?"
The Hybrid Approach: Local Dev, Cloud Prod
The most practical approach for most teams is a hybrid model: use local .env files for development, but sync production secrets from a cloud provider.
Here's how to structure it:
project/
├── .env.example # Template (commit this)
├── .env.local # Local overrides (gitignored)
├── .env # Synced from cloud (gitignored)
├── .env.production # Production values (gitignored)
└── lib/
└── config.ts # Smart config loader
// lib/config.ts
import dotenv from "dotenv";
import path from "path";
import { getSecret } from "./secrets"; // Your secrets manager
const env = process.env.NODE_ENV || "development";
// Load base .env file
dotenv.config({
path: path.resolve(process.cwd(), `.env${env === "production" ? ".production" : ""}`)
});
// Load local overrides (never committed)
if (env === "development") {
dotenv.config({
path: path.resolve(process.cwd(), ".env.local"),
override: true
});
}
// In production, fetch sensitive values from cloud
export async function loadProductionSecrets() {
if (env !== "production") return;
const secrets = await getSecret("myapp/production");
// Override with cloud values
process.env.DATABASE_URL = secrets.databaseUrl;
process.env.API_KEY = secrets.apiKey;
process.env.JWT_SECRET = secrets.jwtSecret;
}
// Initialize
if (env === "production") {
loadProductionSecrets().catch(console.error);
}
export const config = {
env,
database: {
url: process.env.DATABASE_URL!,
poolSize: env === "production" ? 20 : 5
}
} as const;Best practice
Never store production secrets in .env files, even if they're gitignored. Use environment-specific files and cloud sync.
How evnx Bridges the Gap
This is where tools like evnx (environment variable sync) come in. Evnx provides a local CLI that syncs with cloud secrets, giving you the best of both worlds.
Key Features of evnx:
- ›Local CLI that feels like working with
.envfiles - ›Automatic sync with AWS Secrets Manager, Vault, or other backends
- ›Encryption at rest for local
.envfiles - ›Team sharing with role-based access control
Workflow:
# Initialize evnx
evnx init
# Link to your cloud provider
evnx link aws --secret-id myapp/development
# Pull secrets (creates encrypted .env)
evnx pull
# Run your app
evnx run npm run dev
# Push local changes (with approval workflow)
evnx push DATABASE_URL=postgres://new-host:5432/db
# Scan for security issues
evnx scan
# [SCAN] Scanning .env files...
# [ERROR] AWS_SECRET_ACCESS_KEY matches high-entropy pattern (256-bit key)
# [ERROR] Pattern: aws_secret (confidence: high)
# [INFO] 1 secret detected. Commit blocked.Integration Example:
// lib/evnx-config.ts
import { EvnxClient } from "@evnx/sdk";
const evnx = new EvnxClient({
backend: "aws-secrets-manager",
secretId: process.env.EVNX_SECRET_ID!,
cacheTtl: 300 // 5 minutes
});
export async function getEnvWithFallback(key: string, fallback: string): Promise<string> {
// Try cloud first, fallback to local .env
const cloudValue = await evnx.get(key);
return cloudValue || process.env[key] || fallback;
}
// Usage
export const config = {
database: {
url: await getEnvWithFallback("DATABASE_URL", "postgres://localhost/dev")
}
};Pre-commit hook
evnx includes a pre-commit hook that scans for leaked secrets before they leave your machine. It takes 180ms on cold start—fast enough that nobody disables it.
Advanced Patterns & Security Considerations
1. Secret Rotation Without Downtime
// lib/rotation.ts
export async function rotateDatabaseCredentials() {
const client = new SecretsManagerClient({});
// Step 1: Create new credentials
const newCreds = await createNewDatabaseUser();
// Step 2: Update secret with both old and new
await client.send(new UpdateSecretCommand({
SecretId: "prod/db/creds",
SecretString: JSON.stringify({
...newCreds,
previousPassword: await getCurrentPassword()
})
}));
// Step 3: Update database permissions
await grantDatabaseAccess(newCreds.username);
// Step 4: Schedule old credential removal
setTimeout(async () => {
await revokeDatabaseAccess(oldUsername);
}, 24 * 60 * 60 * 1000); // 24 hours
}2. Caching Strategy
// lib/secret-cache.ts
import NodeCache from "node-cache";
const secretCache = new NodeCache({ stdTTL: 300 }); // 5 minutes
export async function getCachedSecret(secretName: string) {
const cached = secretCache.get(secretName);
if (cached) return cached;
const secret = await getSecret(secretName);
secretCache.set(secretName, secret);
return secret;
}
// Invalidate on update
export async function updateSecret(name: string, value: any) {
await secretsManager.updateSecret(name, value);
secretCache.del(name); // Clear cache
}3. Audit Logging
// lib/audit.ts
export async function logSecretAccess(
secretName: string,
userId: string,
action: "read" | "write" | "delete"
) {
await auditClient.putRecord({
TableName: "SecretAuditLog",
Item: {
timestamp: new Date().toISOString(),
secretName,
userId,
action,
ipAddress: getClientIp(),
userAgent: getUserAgent()
}
});
}
// Middleware example
export const auditSecretAccess = (secretName: string) => {
return async (req: Request, res: Response, next: NextFunction) => {
await logSecretAccess(secretName, req.user.id, "read");
next();
};
};4. Environment-Specific Encryption
// lib/encrypted-env.ts
import crypto from "crypto";
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY!; // 32 bytes
const IV_LENGTH = 16;
export function encrypt(text: string): string {
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv("aes-256-cbc", Buffer.from(ENCRYPTION_KEY), iv);
let encrypted = cipher.update(text, "utf8", "hex");
encrypted += cipher.final("hex");
return iv.toString("hex") + ":" + encrypted;
}
export function decrypt(text: string): string {
const [ivHex, encrypted] = text.split(":");
const iv = Buffer.from(ivHex, "hex");
const decipher = crypto.createDecipheriv("aes-256-cbc", Buffer.from(ENCRYPTION_KEY), iv);
let decrypted = decipher.update(encrypted, "hex", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
}Cost Analysis: Startups vs Enterprises
Startup Scenario (5 developers, 3 environments)
AWS Secrets Manager:
- ›30 secrets (10 per env) × $0.40 = $12/month
- ›API calls (100k/month) = $0.50
- ›Total: ~$12.50/month
Infisical (Cloud):
- ›Free tier covers up to 5 users
- ›Total: $0/month
Doppler:
- ›Free tier covers up to 5 users, 3 projects
- ›Total: $0/month
HashiCorp Vault (Self-hosted):
- ›Infrastructure (t3.small EC2): $15/month
- ›Engineering time (5 hrs/month × $50/hr): $250
- ›Total: ~$265/month (hidden ops cost!)
dotenv.space:
- ›3 projects × $10 = $30/month
- ›Total: $30/month
Startup winner
For startups: Start with Infisical or Doppler's free tiers. Migrate to AWS Secrets Manager or paid plans when you scale.
Enterprise Scenario (50 developers, 5 environments, compliance requirements)
AWS Secrets Manager:
- ›500 secrets × $0.40 = $200/month
- ›API calls (2M/month) = $10
- ›CloudTrail logging = $50
- ›Total: ~$260/month
HashiCorp Vault Enterprise:
- ›License: $30,000/year = $2,500/month
- ›Dedicated infra: $200/month
- ›FTE for maintenance (0.5 engineer): $5,000/month
- ›Total: ~$7,700/month
CyberArk Conjur:
- ›License + support: ~$5,000/month
- ›Infrastructure: $500/month
- ›Dedicated team (2 engineers): $15,000/month
- ›Total: ~$20,500/month
Infisical Enterprise:
- ›50 users × $10 = $500/month
- ›Self-hosting option: $0 license + infra costs
- ›Total: $500-$2,000/month (depending on hosting)
Doppler Enterprise:
- ›Custom pricing, typically ~$2,000/month
- ›Total: ~$2,000/month
Enterprise considerations
For enterprises, the decision isn't just about cost—it's about compliance, audit requirements, and existing infrastructure. Vault and CyberArk win on features; Infisical and Doppler win on ease of use.
Making the Decision
Here's a quick decision tree:
Are you on AWS?
├─ Yes → AWS Secrets Manager (easy integration)
└─ No → Multi-cloud or SaaS?
├─ Need dynamic secrets, max security → Vault
├─ Want simplicity, small team → Infisical or Doppler
├─ Already use 1Password → 1Password Secrets Automation
├─ GitOps workflow → SOPS
├─ Enterprise compliance → CyberArk Conjur
└─ Need hybrid approach → evnx + your provider
Don't over-engineer
Start simple. You can always migrate to a more complex solution later. Most teams can start with Infisical, Doppler, or dotenv.space and scale up as needed.
Next Steps
Ready to level up your secrets management?
- ›Audit your current setup: Run
grep -r "API_KEY\|PASSWORD\|SECRET" .to find exposed secrets - ›Start small: Migrate one service (like your database credentials) first
- ›Implement rotation: Set a calendar reminder to rotate keys quarterly
- ›Add monitoring: Track secret access patterns for anomalies
- ›Document everything: Your future self (and onboarding devs) will thank you
Further Reading
- ›AWS Secrets Manager Best Practices
- ›HashiCorp Vault Documentation
- ›Infisical Getting Started
- ›Doppler Documentation
- ›12-Factor App: Config
- ›OWASP Secrets Management Cheat Sheet
Final thought
The best secrets management solution is the one your team will actually use. Don't let perfect be the enemy of secure—start with something simple and iterate as you grow.
Have questions about implementing secrets management in your stack? Drop a comment below or reach out on Twitter [@ajit].