- Published on
Secrets Management in 2026 — Vault, AWS Secrets Manager, Infisical, and Doppler Compared
- Authors

- Name
- Sanjeev Sharma
- @webcoderspeed1
Introduction
.env files are the biggest security vulnerability in most backends. They get committed to git, leaked in logs, and copied insecurely across environments. Teams that stay with .env eventually suffer breaches.
In 2026, there are excellent alternatives: HashiCorp Vault (complex but powerful), AWS Secrets Manager (AWS-native), Infisical (open-source, self-hostable), and Doppler (developer-friendly). This post compares them and shows exactly how to migrate away from .env.
- Why .env Files Are Still Killing Teams
- HashiCorp Vault: Dynamic Secrets
- AWS Secrets Manager: Native AWS Integration
- Infisical: Open-Source, Self-Hostable
- Doppler: Developer-Friendly SaaS
- 1Password Secrets Automation
- Injecting Secrets into Kubernetes
- Secrets in CI/CD
- Comparison Table
- Migrating from .env
- Checklist
- Conclusion
Why .env Files Are Still Killing Teams
Problems with .env:
- Git commits: A leaked
.envin git history is permanent. - CI/CD logs: Secrets visible in build logs and error messages.
- Server copies: Hardcoding in deployment scripts.
- Lack of rotation: Secrets stay the same forever.
- No audit trail: No way to know who accessed what.
- Manual sync: Copy-pasting secrets between environments.
Real example:
# Developer commits by mistake
git add . && git commit -m "Add config"
# .env is in the commit!
# Push to GitHub
git push origin main
# Attacker finds .env in git history
# Gets database password, API keys, everything
# Extracts data, sells it, vanishes
# By the time you notice, it''s too late
Even with .gitignore, mistakes happen. Never commit secrets.
HashiCorp Vault: Dynamic Secrets
Vault is enterprise-grade. It supports dynamic credentials: passwords that expire after hours, not months.
Why dynamic secrets?
If a database password is leaked and expires in 2 hours, damage is limited. If it''s static for 6 months, attacker has 6 months to exploit.
Install Vault:
brew install vault
vault server -dev # Development mode
Configure PostgreSQL secret engine:
vault secrets enable database
vault write database/config/postgresql \
plugin_name=postgresql-database-plugin \
allowed_roles="readonly" \
connection_url="postgresql://{{username}}:{{password}}@postgres:5432/mydb" \
username="vault_admin" \
password="vault_password"
vault write database/roles/readonly \
db_name=postgresql \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD ''{{password}}'' IN ROLE readonly;" \
default_ttl="1h" \
max_ttl="24h"
Read dynamic credential:
vault read database/creds/readonly
# Output:
# Key Value
# --- -----
# lease_duration 3600s
# password a3cd-43-h7x
# username v-token-readonly-abc123
In Node.js:
import * as Vault from 'node-vault';
const vault = new Vault.default({
endpoint: 'http://localhost:8200',
token: process.env.VAULT_TOKEN,
});
async function getDatabasePassword() {
const secret = await vault.read('database/creds/readonly');
return {
username: secret.data.data.username,
password: secret.data.data.password,
};
}
const db = new PG.Pool({
host: 'postgres',
port: 5432,
database: 'mydb',
user: (await getDatabasePassword()).username,
password: (await getDatabasePassword()).password,
});
Password is rotated automatically. Vault creates new credentials, invalidates old ones.
AWS Secrets Manager: Native AWS Integration
Simpler than Vault if you''re already on AWS.
Create a secret:
aws secretsmanager create-secret \
--name prod/database-password \
--secret-string '{"username":"admin","password":"secure_password"}'
Read secret in Node.js:
import {
SecretsManagerClient,
GetSecretValueCommand,
} from '@aws-sdk/client-secrets-manager';
const client = new SecretsManagerClient({ region: 'us-east-1' });
async function getSecret(secretName: string) {
const command = new GetSecretValueCommand({ SecretId: secretName });
const response = await client.send(command);
return JSON.parse(response.SecretString!);
}
const dbCreds = await getSecret('prod/database-password');
const db = new PG.Pool({
host: 'postgres',
user: dbCreds.username,
password: dbCreds.password,
});
Automatic rotation:
# Rotation function (Lambda)
exports.lambda_handler = async (event) => {
const secretId = event.ClientRequestToken;
const secret = await secretsManager.getSecretValue({ SecretId: secretId });
// Generate new password
const newPassword = generatePassword();
// Update database
await updateDatabasePassword(secret.username, newPassword);
// Store new password in Secrets Manager
await secretsManager.putSecretValue({
SecretId: secretId,
SecretString: JSON.stringify({ ...secret, password: newPassword }),
ClientRequestToken: event.ClientRequestToken,
});
};
// Attach to secret for automatic rotation
aws secretsmanager rotate-secret \
--secret-id prod/database-password \
--rotation-lambda-arn arn:aws:lambda:...
Cost: ~$0.40 per secret per month + API calls. Scales linearly.
Infisical: Open-Source, Self-Hostable
Infisical is a self-hosted alternative to Doppler. No vendor lock-in.
Docker Compose:
version: '3.8'
services:
infisical-postgres:
image: postgres:15
environment:
POSTGRES_DB: infisical
POSTGRES_PASSWORD: password
volumes:
- infisical-data:/var/lib/postgresql/data
infisical-app:
image: infisical/infisical:latest
depends_on:
- infisical-postgres
environment:
DATABASE_URL: postgres://postgres:password@infisical-postgres:5432/infisical
JWT_SIGNUP_SECRET: your_secret_key
ports:
- '80:3000'
volumes:
- infisical-app-data:/app/data
volumes:
infisical-data:
infisical-app-data:
Login and get credentials:
infisical login --domain http://localhost
infisical secrets --path /prod get DATABASE_PASSWORD
# Output: your_password_here
In Node.js:
import { InfisicalClient } from '@infisical/sdk';
const client = new InfisicalClient({
siteURL: 'https://infisical.example.com',
auth: {
universalAuthToken: process.env.INFISICAL_TOKEN,
},
});
async function getSecret(key: string) {
const secret = await client.getSecret({
secretName: key,
projectId: 'PROJECT_ID',
environment: 'prod',
});
return secret.secretValue;
}
const dbPassword = await getSecret('DATABASE_PASSWORD');
Cost: Free (self-hosted). Requires infrastructure.
Doppler: Developer-Friendly SaaS
Doppler prioritises developer experience. Simple CLI, fast sync, cheap.
Install CLI:
brew install doppler/cli/doppler
doppler login
Create project and environment:
doppler projects create myapp
doppler environments create prod
doppler config create backend --environment prod
Set secrets:
doppler secrets set DATABASE_PASSWORD "secure_password" --config backend
doppler secrets set API_KEY "key_123" --config backend
In Node.js:
import { DopplerSDK } from '@doppler/sdk';
const doppler = new DopplerSDK({
token: process.env.DOPPLER_TOKEN,
});
async function getSecrets() {
const secrets = await doppler.secrets.list({
project: 'myapp',
config: 'backend',
});
return secrets.secrets;
}
const allSecrets = await getSecrets();
const dbPassword = allSecrets.DATABASE_PASSWORD.computed;
Cost: ~$15-30/mo for small teams. Scales to $300+/mo for large teams.
1Password Secrets Automation
1Password can manage secrets and rotate them.
# Get secret via CLI
op read "op://myapp/database/password"
# In scripts
DB_PASSWORD=$(op read "op://myapp/database/password")
Works great if your team already uses 1Password. Limited for non-1Password users.
Injecting Secrets into Kubernetes
Use External Secrets Operator to sync secrets from vault/Doppler to Kubernetes.
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: doppler
namespace: default
spec:
provider:
doppler:
auth:
secretRef:
dopplerToken:
name: doppler-secret
key: token
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: default
spec:
refreshInterval: 1h
secretStoreRef:
name: doppler
kind: SecretStore
target:
name: app-secrets
creationPolicy: Owner
data:
- secretKey: database_password
remoteRef:
key: DATABASE_PASSWORD
- secretKey: api_key
remoteRef:
key: API_KEY
Pod receives Kubernetes secret automatically. No manual env var copying.
Secrets in CI/CD
GitHub Actions, GitLab CI, and other platforms support secret management.
GitHub Actions:
name: Deploy
on: [push]
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm test
- run: npm run deploy
Secrets are masked in logs. Safe for CI.
Better: use Vault from CI:
- name: Get secrets from Vault
run: |
export VAULT_TOKEN=$(curl -s -X POST \
https://vault.example.com/v1/auth/jwt/login \
-d '{"jwt":"${{ secrets.VAULT_JWT }}"}' \
| jq -r '.auth.client_token')
DATABASE_URL=$(vault kv get -field=url secret/database)
echo "DATABASE_URL=$DATABASE_URL" >> $GITHUB_ENV
Secrets never stored in GitHub. Rotated at Vault.
Comparison Table
| Feature | Vault | AWS Secrets Manager | Infisical | Doppler |
|---|---|---|---|---|
| Dynamic secrets | Yes | Limited | No | No |
| Self-hostable | Yes | No | Yes | No |
| Cost | High | Low (~$0.40/mo) | Free (self-hosted) | Medium (~$15/mo) |
| Ease of use | Hard | Medium | Easy | Very Easy |
| Audit logs | Yes | Yes | Yes | Yes |
| Rotation | Yes | Yes | Yes | Yes |
| Multi-environment | Yes | Yes | Yes | Yes |
| Team support | Yes | Yes | Yes | Yes |
Choose Vault if: You need dynamic secrets, have DevOps expertise, and want maximum control.
Choose AWS Secrets Manager if: All your infrastructure is on AWS and you want simplicity.
Choose Infisical if: You want to self-host and have no vendor lock-in.
Choose Doppler if: You want simplicity and team features without infrastructure overhead.
Migrating from .env
// Before: read from .env
const dbPassword = process.env.DATABASE_PASSWORD;
// After: read from Doppler
import { DopplerSDK } from '@doppler/sdk';
const doppler = new DopplerSDK({
token: process.env.DOPPLER_TOKEN,
});
const secrets = await doppler.secrets.list({ project: 'myapp', config: 'backend' });
const dbPassword = secrets.secrets.DATABASE_PASSWORD.computed;
// Or use Doppler CLI to inject at startup
// doppler run -- node app.js
// Secrets available as env vars
Checklist
- Stop committing
.envfiles - Enable
.envin.gitignore - Choose secret management tool (Vault, AWS, Infisical, Doppler)
- Migrate existing secrets to the new tool
- Rotate all existing secrets (assume they leaked)
- Implement automatic secret rotation
- Enable audit logging
- Update CI/CD to fetch secrets from vault
- Document secret access for your team
- Schedule quarterly access reviews
Conclusion
.env files are a liability. Move to a proper secret management tool in 2026. Doppler is the fastest path for teams without DevOps expertise. Vault is the most powerful for teams that can maintain it. Infisical splits the difference. Whatever you choose, stop trusting files. Use proper infrastructure, rotate secrets automatically, and log every access.