Infisical vs Doppler vs HashiCorp Vault: Secrets Management Compared (2026)
TL;DR: Infisical is the open-source secrets manager — self-hosted or cloud, developer-friendly SDKs, automatic secret rotation, and native integrations with every CI/CD platform. Doppler is the cloud-first secrets platform — universal dashboard, environment syncing, and the simplest developer onboarding for teams managing secrets across multiple services. HashiCorp Vault is the enterprise secrets engine — dynamic secrets, encryption-as-a-service, PKI, and granular access policies for complex infrastructure. In 2026: Infisical for open-source with full control, Doppler for the fastest team onboarding and simplest workflow, Vault for enterprise infrastructure with dynamic secrets and encryption needs.
Key Takeaways
- Infisical: Open-source (MIT), self-hosted or cloud. Node.js/Python/Go/Java SDKs, secret versioning, automatic rotation, Kubernetes operator. Best for teams wanting open-source secrets management with modern DX
- Doppler: Cloud-only, team-focused. Universal dashboard, CLI syncing, 30+ integrations. Best for teams that need simple, reliable secret syncing across every environment and platform
- HashiCorp Vault: Open-source (BSL) + enterprise. Dynamic secrets, transit encryption, PKI certificates, identity-based access. Best for large organizations needing dynamic secrets, encryption-as-a-service, and fine-grained access control
Infisical — Open-Source Secrets Manager
Infisical gives you a self-hosted or cloud secrets manager with developer-friendly SDKs, automatic rotation, and end-to-end encryption.
SDK Integration
// @infisical/sdk — fetch secrets at runtime
import { InfisicalSDK } from "@infisical/sdk";
const infisical = new InfisicalSDK({
siteUrl: "https://secrets.yourcompany.com", // self-hosted
});
// Authenticate with machine identity (Universal Auth)
await infisical.auth().universalAuth.login({
clientId: process.env.INFISICAL_CLIENT_ID!,
clientSecret: process.env.INFISICAL_CLIENT_SECRET!,
});
// Fetch all secrets for an environment
const secrets = await infisical.secrets().listSecrets({
environment: "production",
projectId: "proj_abc123",
secretPath: "/",
});
for (const secret of secrets.secrets) {
console.log(`${secret.secretKey}: ${secret.secretValue}`);
}
// Fetch a single secret
const dbUrl = await infisical.secrets().getSecret({
environment: "production",
projectId: "proj_abc123",
secretPath: "/",
secretName: "DATABASE_URL",
});
console.log(dbUrl.secret.secretValue);
Secret Creation and Versioning
// Create a secret
await infisical.secrets().createSecret({
environment: "production",
projectId: "proj_abc123",
secretPath: "/payments",
secretName: "STRIPE_SECRET_KEY",
secretValue: "sk_live_...",
secretComment: "Production Stripe key — rotated quarterly",
});
// Update a secret (creates a new version)
await infisical.secrets().updateSecret({
environment: "production",
projectId: "proj_abc123",
secretPath: "/payments",
secretName: "STRIPE_SECRET_KEY",
secretValue: "sk_live_new_...",
});
// Secret versioning — automatic, query history via API
// Every update creates an immutable version
// Roll back via dashboard or API
Automatic Secret Rotation
// Configure automatic rotation via API
await fetch("https://secrets.yourcompany.com/api/v1/secret-rotations", {
method: "POST",
headers: {
Authorization: `Bearer ${INFISICAL_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
workspaceId: "proj_abc123",
secretPath: "/database",
environment: "production",
provider: "postgresql",
inputs: {
host: "db.yourcompany.com",
port: 5432,
database: "production",
username1: "app_user_a",
username2: "app_user_b",
ca: "-----BEGIN CERTIFICATE-----...",
},
interval: 86400, // Rotate every 24 hours
// Infisical rotates between two users for zero-downtime
}),
});
Kubernetes Operator
# InfisicalSecret CRD — sync secrets to K8s
apiVersion: secrets.infisical.com/v1alpha1
kind: InfisicalSecret
metadata:
name: app-secrets
namespace: production
spec:
hostAPI: https://secrets.yourcompany.com
authentication:
universalAuth:
credentialsRef:
secretName: infisical-machine-identity
secretNamespace: production
managedSecretReference:
secretName: app-env
secretNamespace: production
secretType: Opaque
projectId: proj_abc123
envSlug: production
secretsPath: /
resyncInterval: 60 # Re-sync every 60 seconds
---
# Use in deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
template:
spec:
containers:
- name: api
envFrom:
- secretRef:
name: app-env # Populated by InfisicalSecret
CLI for Local Development
# Install Infisical CLI
brew install infisical/get-cli/infisical
# Login and initialize project
infisical login
infisical init
# Run commands with injected secrets
infisical run -- npm start
# Run with specific environment
infisical run --env=staging -- npm start
# Export secrets to .env file (gitignored)
infisical export --env=development > .env
# Scan git history for leaked secrets
infisical scan git-history
Doppler — Cloud-First Secrets Platform
Doppler provides a universal secrets dashboard with automatic syncing to every platform — CLI, CI/CD, cloud providers, and container orchestrators.
CLI Integration
# Install Doppler CLI
brew install dopplerhq/cli/doppler
# Authenticate and configure project
doppler login
doppler setup # interactive project + config selection
# Run with secrets injected as environment variables
doppler run -- npm start
# Run with specific config (environment)
doppler run --config=staging -- npm start
# Access individual secrets
doppler secrets get DATABASE_URL --plain
# Set a secret
doppler secrets set STRIPE_KEY "sk_live_..."
# Set multiple secrets at once
doppler secrets set API_KEY="abc123" API_SECRET="xyz789"
# Download secrets as .env
doppler secrets download --no-file --format=env > .env
SDK Integration
// Doppler REST API — no SDK needed, simple fetch
async function getSecrets(project: string, config: string): Promise<Record<string, string>> {
const response = await fetch(
`https://api.doppler.com/v3/configs/config/secrets`,
{
headers: {
Authorization: `Bearer ${process.env.DOPPLER_TOKEN}`,
"Content-Type": "application/json",
},
method: "GET",
// Query params
}
);
const data = await response.json();
const secrets: Record<string, string> = {};
for (const [key, value] of Object.entries(data.secrets)) {
secrets[key] = (value as any).computed;
}
return secrets;
}
// Typically you don't need the API — Doppler injects via CLI
// doppler run -- node server.js
// All secrets available as process.env.SECRET_NAME
Dynamic Secrets and References
# Secret references — DRY across configs
# In "production" config, reference shared secrets:
doppler secrets set DATABASE_URL '${shared.DATABASE_HOST}/production'
# Doppler resolves references at runtime:
# shared.DATABASE_HOST = "postgres://user:pass@db.company.com"
# → DATABASE_URL = "postgres://user:pass@db.company.com/production"
# Cross-project references
doppler secrets set AUTH_KEY '${projects.auth-service.production.JWT_SECRET}'
CI/CD Integration
# GitHub Actions — inject secrets from Doppler
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Doppler CLI
uses: dopplerhq/cli-action@v3
- name: Deploy with secrets
run: doppler run -- ./deploy.sh
env:
DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN }}
# Or fetch specific secrets
- name: Get secrets
id: secrets
run: |
echo "db_url=$(doppler secrets get DATABASE_URL --plain)" >> $GITHUB_OUTPUT
env:
DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN }}
Kubernetes Integration
# Doppler Kubernetes Operator
apiVersion: secrets.doppler.com/v1alpha1
kind: DopplerSecret
metadata:
name: app-secrets
namespace: production
spec:
tokenSecret:
name: doppler-token
managedSecret:
name: app-env
namespace: production
type: Opaque
resyncSeconds: 60
---
# Automatic restart on secret change
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
annotations:
secrets.doppler.com/reload: "true" # Auto-restart on change
spec:
template:
spec:
containers:
- name: api
envFrom:
- secretRef:
name: app-env
Webhooks for Secret Changes
// Doppler fires webhooks when secrets change
app.post("/webhooks/doppler", (req, res) => {
const event = req.body;
if (event.type === "secrets.update") {
console.log(`Project: ${event.project}`);
console.log(`Config: ${event.config}`);
console.log(`Changed by: ${event.user.email}`);
console.log(`Changed secrets: ${event.changed_secrets.join(", ")}`);
// Trigger redeployment or cache invalidation
triggerRedeploy(event.config);
}
res.status(200).send("OK");
});
HashiCorp Vault — Enterprise Secrets Engine
HashiCorp Vault provides dynamic secrets, encryption-as-a-service, PKI, and identity-based access control for complex infrastructure.
Basic Secret Operations
// node-vault SDK
import vault from "node-vault";
const client = vault({
endpoint: "https://vault.yourcompany.com",
token: process.env.VAULT_TOKEN, // or use AppRole auth
});
// Write a secret (KV v2 engine)
await client.write("secret/data/api-service", {
data: {
database_url: "postgres://user:pass@db.company.com/production",
stripe_key: "sk_live_...",
jwt_secret: "super-secret-key",
},
});
// Read a secret
const result = await client.read("secret/data/api-service");
const secrets = result.data.data;
console.log(secrets.database_url);
// KV v2 — automatic versioning
const v1 = await client.read("secret/data/api-service", { version: 1 });
const latest = await client.read("secret/data/api-service");
console.log(`Current version: ${latest.data.metadata.version}`);
AppRole Authentication
// AppRole — machine-to-machine auth (no human token needed)
const client = vault({ endpoint: "https://vault.yourcompany.com" });
// Login with AppRole credentials
const loginResult = await client.approleLogin({
role_id: process.env.VAULT_ROLE_ID!,
secret_id: process.env.VAULT_SECRET_ID!,
});
client.token = loginResult.auth.client_token;
// Token auto-renews based on TTL
// Kubernetes auth — pods authenticate via service account
const k8sClient = vault({ endpoint: "https://vault.yourcompany.com" });
const jwt = await fs.readFile(
"/var/run/secrets/kubernetes.io/serviceaccount/token",
"utf8"
);
const k8sLogin = await k8sClient.kubernetesLogin({
role: "api-service",
jwt,
});
k8sClient.token = k8sLogin.auth.client_token;
Dynamic Database Credentials
// Vault generates temporary database credentials on demand
// Configure the database secrets engine (one-time setup via CLI):
// vault write database/config/postgres \
// plugin_name=postgresql-database-plugin \
// connection_url="postgresql://{{username}}:{{password}}@db:5432/app" \
// allowed_roles="api-service" \
// username="vault_admin" password="admin_password"
// vault write database/roles/api-service \
// db_name=postgres \
// creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
// default_ttl="1h" max_ttl="24h"
// Application requests dynamic credentials:
const creds = await client.read("database/creds/api-service");
console.log(`Username: ${creds.data.username}`); // v-approle-api-ser-abc123
console.log(`Password: ${creds.data.password}`); // auto-generated
console.log(`TTL: ${creds.lease_duration}s`); // 3600 (1 hour)
// Use dynamic credentials for database connection
const pool = new Pool({
host: "db.yourcompany.com",
database: "app",
user: creds.data.username,
password: creds.data.password,
});
// Credentials auto-expire — Vault revokes them after TTL
// Renew lease if needed:
await client.write(`sys/leases/renew`, {
lease_id: creds.lease_id,
increment: 3600,
});
Transit Encryption (Encryption-as-a-Service)
// Transit engine — encrypt/decrypt without exposing keys
// Keys never leave Vault — only ciphertext crosses the network
// Encrypt sensitive data
const encrypted = await client.write("transit/encrypt/user-data", {
plaintext: Buffer.from(JSON.stringify({
ssn: "123-45-6789",
credit_card: "4111111111111111",
})).toString("base64"),
});
const ciphertext = encrypted.data.ciphertext;
// vault:v1:8SDd3WHDOjf7mq69CyCqYjBXAiQQA...
// Store ciphertext in your database safely
// Decrypt when needed
const decrypted = await client.write("transit/decrypt/user-data", {
ciphertext,
});
const plaintext = JSON.parse(
Buffer.from(decrypted.data.plaintext, "base64").toString()
);
console.log(plaintext.ssn); // 123-45-6789
// Key rotation — transparent to application
// vault write -f transit/keys/user-data/rotate
// Old ciphertext still decryptable, new encryptions use latest key
// Rewrap existing ciphertext to use latest key version:
const rewrapped = await client.write("transit/rewrap/user-data", {
ciphertext: oldCiphertext,
});
ACL Policies
# Vault policy — fine-grained access control
# API service — read-only access to its own secrets
path "secret/data/api-service/*" {
capabilities = ["read", "list"]
}
# Dynamic database credentials
path "database/creds/api-service" {
capabilities = ["read"]
}
# Transit encryption — encrypt and decrypt only
path "transit/encrypt/user-data" {
capabilities = ["update"]
}
path "transit/decrypt/user-data" {
capabilities = ["update"]
}
# Deny access to other services' secrets
path "secret/data/payment-service/*" {
capabilities = ["deny"]
}
Vault Agent Sidecar (Kubernetes)
# Vault Agent injector — auto-inject secrets into pods
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "api-service"
vault.hashicorp.com/agent-inject-secret-db: "database/creds/api-service"
vault.hashicorp.com/agent-inject-template-db: |
{{- with secret "database/creds/api-service" -}}
postgresql://{{ .Data.username }}:{{ .Data.password }}@db:5432/app
{{- end }}
vault.hashicorp.com/agent-inject-secret-config: "secret/data/api-service"
vault.hashicorp.com/agent-inject-template-config: |
{{- with secret "secret/data/api-service" -}}
export STRIPE_KEY="{{ .Data.data.stripe_key }}"
export JWT_SECRET="{{ .Data.data.jwt_secret }}"
{{- end }}
spec:
serviceAccountName: api-service
containers:
- name: api
command: ["/bin/sh", "-c", "source /vault/secrets/config && node server.js"]
# Secrets written to /vault/secrets/db and /vault/secrets/config
Feature Comparison
| Feature | Infisical | Doppler | HashiCorp Vault |
|---|---|---|---|
| Deployment | Self-hosted or cloud | Cloud only | Self-hosted or cloud (HCP) |
| License | MIT (open-source) | Proprietary | BSL (source-available) |
| Secret Storage | Encrypted KV | Encrypted KV | Multiple engines (KV, transit, PKI, DB) |
| Dynamic Secrets | Rotation (DB) | ❌ | Full (DB, AWS, GCP, Azure, PKI) |
| Encryption-as-a-Service | ❌ | ❌ | Transit engine |
| PKI/Certificates | ❌ | ❌ | Built-in CA |
| Secret Versioning | ✅ | ✅ (audit log) | ✅ (KV v2) |
| Secret Rotation | Auto-rotation | Manual + webhooks | Dynamic (auto-expire) |
| CLI | infisical run | doppler run | vault CLI |
| SDK Languages | Node, Python, Go, Java, .NET | REST API (no SDK) | Node, Python, Go, Java, Ruby |
| K8s Operator | ✅ InfisicalSecret CRD | ✅ DopplerSecret CRD | ✅ Vault Agent Injector + CSI |
| CI/CD Integrations | GitHub, GitLab, CircleCI, etc. | 30+ native integrations | GitHub, GitLab, Jenkins, etc. |
| Access Control | RBAC + environments | RBAC + environments | ACL policies + identity |
| Secret References | Imports across envs | Cross-project refs | Policy-based paths |
| Audit Log | ✅ | ✅ | ✅ (detailed) |
| E2E Encryption | ✅ (client-side) | In transit + at rest | In transit + at rest |
| Complexity | Low-medium | Low | High |
| Ideal For | Dev teams, startups | All team sizes | Enterprise infrastructure |
When to Use Each
Choose Infisical if:
- You want open-source secrets management with MIT license
- Self-hosting for data residency or compliance is required
- You need automatic secret rotation for databases
- End-to-end encryption (client-side) is important
- You want SDKs in multiple languages for runtime secret fetching
Choose Doppler if:
- You want the simplest onboarding —
doppler setupand you're done - Your team manages secrets across many services and environments
- You need 30+ native integrations without custom code
- Secret references across projects save you from duplication
- You prefer cloud-managed with no infrastructure to maintain
Choose HashiCorp Vault if:
- You need dynamic secrets (auto-generated, auto-expired DB credentials)
- Encryption-as-a-service (transit engine) is a requirement
- You need PKI certificate management
- Fine-grained ACL policies with identity-based access control matter
- You're running complex infrastructure across multiple clouds
- You need secret engines beyond KV (AWS, GCP, Azure, SSH, TOTP)
Methodology
Feature comparison based on Infisical (self-hosted + cloud), Doppler, and HashiCorp Vault 1.x documentation as of March 2026. Code examples use official SDKs where available (Infisical SDK, node-vault) and REST API calls. Dynamic secrets and transit encryption are unique Vault capabilities not offered by Infisical or Doppler. Evaluated on: deployment flexibility, secret types, rotation capabilities, developer experience, Kubernetes integration, and access control granularity.