The .env File Is Not a Secrets Manager, and Yet Here We Are
It starts innocently. One .env file. One database password. You add it to .gitignore and congratulate yourself on your operational security.
Six months later you have .env, .env.local, .env.staging, .env.production, and .env.backup-old scattered across four repos. You’ve copy-pasted DB_PASSWORD=SuperSecret123 so many times it’s practically a muscle memory. Two former contractors have it. You’ve forgotten which version is current. One of the repos might be public.
This is secrets sprawl, and it happens to everyone. The question isn’t whether to fix it — the question is which tool won’t make the cure worse than the disease.
The two serious contenders for self-hosters and small teams are HashiCorp Vault (the industry standard that will make you feel like you work at a bank) and Infisical (the modern alternative that won’t make you question your career choices).
The Secrets Sprawl Problem, Properly Quantified
Before getting into the tools, let’s agree on what “proper secrets management” actually needs to do:
- Centralized storage: One source of truth, not 14
.envfiles - Access control: Not everyone needs every secret
- Audit logging: Who accessed what, when
- Rotation: Ability to change secrets without editing files across repos
- Environment separation: Dev, staging, and production secrets don’t mix
- Integration: Works with Docker, CI/CD, your app’s runtime
A shared password manager technically ticks some of these boxes. It doesn’t tick “integration with your deployment pipeline at 2am,” which is when you’ll need it.
HashiCorp Vault: The Gold Standard (With a Learning Curve Like a Cliff)
Vault is what you use when security is a first-class concern and you have time to set it up properly. Financial services, healthcare, enterprises — Vault is everywhere at that scale for good reason.
What Vault Actually Does
The core model: Vault stores secrets (static or dynamic), you authenticate (via tokens, AppRole, Kubernetes service accounts, AWS IAM, and about fifteen other methods), and you get back the secret. Everything is audited.
The genuinely impressive feature is dynamic secrets. Instead of storing a database password, Vault talks directly to your database and generates a temporary credential with an expiry. Your app gets a username and password that exists for 24 hours, then gets revoked automatically. Even if someone exfiltrates the secret, it’s already dead.
Docker Compose Setup
services:
vault:
image: hashicorp/vault:latest
restart: unless-stopped
ports:
- "8200:8200"
environment:
VAULT_DEV_ROOT_TOKEN_ID: "dev-only-token"
VAULT_DEV_LISTEN_ADDRESS: "0.0.0.0:8200"
cap_add:
- IPC_LOCK
volumes:
- vault-data:/vault/data
- ./vault/config:/vault/config
command: vault server -config=/vault/config/vault.hcl
volumes:
vault-data:
The IPC_LOCK capability is required — Vault uses it to prevent secrets from being swapped to disk.
For development, the VAULT_DEV_* variables spin up Vault in dev mode (not for production — it stores everything in memory and resets on restart). Production needs a proper vault.hcl config with storage backend (Raft is the recommended integrated option), TLS, and an unsealing strategy.
The Unseal Problem
This is where Vault’s model gets interesting. On startup, Vault is “sealed” — even with the right credentials, no secrets come out. You have to unseal it with unseal keys (or auto-unseal via AWS KMS, Azure Key Vault, etc.). The default setup uses Shamir’s Secret Sharing — 5 keys generated at init, 3 needed to unseal.
For a homelab, this is either fascinating (you’ve split your unseal keys across three YubiKeys like you’re launching nuclear missiles) or annoying (you rebooted the server and now everything is locked until you remember where you put those keys). Production environments usually configure auto-unseal with a cloud KMS.
Basic Secret Operations
# Store a secret
vault kv put secret/myapp/database \
DB_HOST=postgres.internal \
DB_PASSWORD=actual-secure-password \
DB_NAME=myapp
# Read a secret
vault kv get secret/myapp/database
# Read just one field
vault kv get -field=DB_PASSWORD secret/myapp/database
App Integration (AppRole Auth)
# Create a policy
vault policy write myapp-policy - <<EOF
path "secret/data/myapp/*" {
capabilities = ["read"]
}
EOF
# Create an AppRole
vault auth enable approle
vault write auth/approle/role/myapp \
policies=myapp-policy \
token_ttl=1h
# Get role ID and secret ID
vault read auth/approle/role/myapp/role-id
vault write -f auth/approle/role/myapp/secret-id
Your application uses those two values to authenticate and get a time-limited token.
Infisical: Modern, Opinionated, and Actually Has a UI
Infisical is what happens when someone looks at Vault and asks “what if we kept the good parts but made it usable by humans who have other jobs?”
It launched as a proper open-source alternative to commercial platforms like HashiCorp Vault Enterprise (the paid version) and Doppler. The self-hosted version is genuinely free. It has a UI that doesn’t look like it was designed for people who think terminal UIs are an aesthetic.
What Infisical Does Differently
- Web UI first: Real dashboard, not just a CLI
- GitHub/GitLab sync: Automatically sync secrets to repository secrets for CI/CD
- SDK integrations: First-class libraries for Node, Python, Go, Java
- Environment-aware: Dev/staging/production is a core concept, not something you bolt on
- Secret versioning: Every change tracked, easy rollback
- Machine identities: Cleaner model than Vault’s AppRole for service authentication
Docker Compose Setup
services:
infisical:
image: infisical/infisical:latest
restart: unless-stopped
ports:
- "8080:8080"
environment:
ENCRYPTION_KEY: your-32-char-encryption-key-here
AUTH_SECRET: your-jwt-secret-here
MONGO_URL: mongodb://mongo:27017/infisical
SITE_URL: https://infisical.yourdomain.com
depends_on:
- mongo
- redis
mongo:
image: mongo:latest
restart: unless-stopped
volumes:
- mongo-data:/data/db
redis:
image: redis:alpine
restart: unless-stopped
volumes:
mongo-data:
Note: Infisical recently migrated from MongoDB to PostgreSQL in newer versions. Check the current docs — the above may vary by version.
SDK Usage: The Part That Actually Matters
This is where Infisical genuinely shines. Fetching secrets at runtime takes about ten lines of code.
Node.js:
import InfisicalClient from "@infisical/sdk";
const client = new InfisicalClient({
clientId: process.env.INFISICAL_CLIENT_ID,
clientSecret: process.env.INFISICAL_CLIENT_SECRET,
});
const dbPassword = await client.getSecret({
secretName: "DB_PASSWORD",
projectId: "your-project-id",
environment: "production",
});
console.log(dbPassword.secretValue);
Python:
from infisical_sdk import InfisicalSDKClient
client = InfisicalSDKClient(
client_id=os.environ["INFISICAL_CLIENT_ID"],
client_secret=os.environ["INFISICAL_CLIENT_SECRET"]
)
secret = client.getSecret(
secret_name="DB_PASSWORD",
project_id="your-project-id",
environment="production"
)
The only values that need to be in your environment are the client ID and secret — and those are machine identity credentials, not your actual application secrets.
Head-to-Head Comparison
| Feature | HashiCorp Vault | Infisical |
|---|---|---|
| Setup complexity | High | Low-Medium |
| UI | Basic | Full-featured |
| Dynamic secrets | Yes (databases, PKI, etc.) | No |
| Secret versioning | Yes | Yes |
| Access control | Very granular (policies) | RBAC |
| Audit logging | Excellent | Good |
| SDK support | Community libraries | Official SDKs |
| GitHub/GitLab sync | Via CI step | Native |
| Self-hostable | Yes | Yes |
| License | BSL 1.1 (post-2023) | MIT |
| Learning curve | Steep | Moderate |
| Community size | Large | Growing |
| Auto-unseal complexity | Requires KMS | Not applicable |
| Best for | Enterprise, complex security needs | Teams, developer experience |
When Vault Is Worth the Complexity
Vault earns its complexity in specific scenarios:
Dynamic database credentials: If you run a large application and want zero long-lived database passwords, Vault’s database secrets engine is unmatched. Every service gets a credential that auto-expires. Compromise one service and the attacker’s window is 24 hours.
PKI infrastructure: Vault can run an internal Certificate Authority. Issue certificates, rotate them, revoke them — all programmatically. For a microservices setup with mutual TLS, this is transformative.
Multi-cloud secrets: Vault integrates with AWS IAM, Azure AD, GCP IAM for secretless authentication — your EC2 instance’s IAM role authenticates to Vault and gets secrets without any hardcoded credentials.
Strict compliance requirements: SOC 2, PCI-DSS, HIPAA environments where audit trails and access control policies need to be airtight and demonstrably so.
If you’re a team of two running a SaaS with a Postgres database and a handful of API keys, Vault’s complexity is genuinely not worth it for the marginal security gain.
When Infisical Is the Right Call
Infisical is the right answer for:
- Teams of 1-20 people who don’t have a dedicated security engineer
- Developer experience matters — you want people to actually use it
- You’re using GitHub Actions, GitLab CI, or similar and want native secret sync
- You need environments (dev/staging/prod) to just work without extensive policy configuration
- You’re migrating away from
.envfiles and want something you’ll actually finish migrating to
The onboarding experience is genuinely the difference. With Vault, “we should set up secrets management” often becomes a multi-week project. With Infisical, you can have secrets out of your .env files and into the platform in an afternoon.
The Migration Path From .env Files
Regardless of which you pick, the migration process is roughly the same:
- Audit your
.envfiles across all repos (yes, all of them) - Categorize: which secrets are per-environment, which are shared
- Import to your secrets manager (Infisical has an import tool; Vault requires scripting)
- Update your applications to fetch secrets at startup
- Remove
.envfiles from repos, update.gitignore - Rotate all the secrets you just imported (they were compromised the moment they existed in
.envfiles, sorry)
Step 6 is the one people skip and then regret.
The HashiCorp License Situation
It’s worth mentioning: HashiCorp switched Vault from MPL 2.0 to Business Source License 1.1 in 2023. BSL is not an open-source license by OSI definition. It restricts use in competing commercial products. For self-hosters and most companies, this doesn’t change anything — you can use Vault freely. But the community forked Vault into OpenBao (maintained by the Linux Foundation) if you want the fully open alternative.
Infisical remains MIT licensed.
The Practical Recommendation
Start with Infisical. It solves 90% of the secrets management problem with 20% of the operational overhead. Get your team off .env files, into proper environments, with secret versioning and audit logs.
When you hit the wall — you need dynamic database credentials, you’re running dozens of services with complex access control policies, or you have compliance requirements that need Vault’s audit capabilities — then invest in learning Vault properly.
The worst outcome is spending three weeks setting up Vault, finding it too complex, and going back to .env files because at least those work. The best outcome is solving the problem with a tool your team will actually use.
Your database password doesn’t need to be in 14 files. Pick the tool that makes it easy to fix that, and fix it.