Secret Management: Vault, AWS Secrets Manager, .env
On this page
Every application needs secrets: database passwords, API keys, TLS certificates, signing tokens. The question is never whether you have secrets to manage, but where they live and who can read them. Get this wrong and a leaked credential can compromise your entire system. Get it right and you gain auditability, rotation, and least-privilege access almost for free.
This post walks through the three tools most teams actually reach for — the humble .env file, AWS Secrets Manager, and HashiCorp Vault — and gives you a framework for choosing between them.
The Core Problem
Secrets are hostile to the tools we use every day. Version control wants to track every change forever. Logs want to capture everything. Container images bake in whatever is present at build time. Each of these is a place a secret can leak and stay leaked.
Good secret management is really about three properties:
- Confidentiality — only authorized identities can read a secret.
- Auditability — you know who read what, and when.
- Rotation — you can change a secret without downtime, ideally automatically.
The tools below sit on a spectrum from "solves confidentiality only" to "solves all three, dynamically."
.env Files: Simple, Local, and Dangerous at Scale
A .env file is a flat list of KEY=value pairs loaded into your process environment at startup. Libraries like dotenv (Node), python-dotenv, and godotenv make it a one-liner.
DATABASE_URL=postgres://user:pass@localhost:5432/app
STRIPE_SECRET_KEY=sk_test_abc123
JWT_SIGNING_SECRET=super-secret-value
Where .env shines:
- Local development, where you want zero infrastructure.
- Small projects and prototypes.
- Twelve-Factor App style config, where environment variables are the interface.
Where it hurts:
- It's a plaintext file. Anyone with disk access reads everything.
- No audit trail. You cannot tell who read the file or when.
- No rotation. Changing a secret means editing files across every host.
- It gets committed. The single most common secret leak is a
.envfile accidentally pushed to Git.
.env Best Practices
If you use .env — and almost everyone does locally — enforce these rules:
- Always add
.envto.gitignore. Do it before you create the file. - Commit a
.env.examplewith keys but no values so teammates know what's required. - Never put production secrets in a
.envfile on a shared or long-lived server. - Run a secret scanner (
gitleaks,trufflehog, or GitHub's push protection) in CI to catch accidental commits.
# .gitignore
.env
.env.local
.env.*.local
.env is a fine interface — environment variables are universal. It's a poor store of record for anything sensitive beyond your laptop.
AWS Secrets Manager: Managed Secrets for the AWS Ecosystem
AWS Secrets Manager is a fully managed service that stores, encrypts (via KMS), and rotates secrets. If your infrastructure already lives in AWS, it's the path of least resistance.
Key features:
- Encryption at rest with KMS, and TLS in transit.
- Fine-grained access via IAM policies — an EC2 instance or Lambda assumes a role that grants read access to specific secrets.
- Automatic rotation with built-in Lambda functions for RDS, Redshift, and DocumentDB, or custom rotation logic.
- Audit logging through CloudTrail — every
GetSecretValuecall is recorded.
Retrieving a secret from application code:
import boto3
import json
def get_secret(name):
client = boto3.client("secretsmanager", region_name="us-east-1")
response = client.get_secret_value(SecretId=name)
return json.loads(response["SecretString"])
db = get_secret("prod/app/database")
connect(user=db["username"], password=db["password"])
Trade-offs to weigh:
- Cost. Roughly $0.40 per secret per month plus per-API-call charges. Thousands of secrets add up.
- Lock-in. It's tightly coupled to AWS IAM and KMS. Multi-cloud teams find this limiting.
- Cold-start latency. Fetching secrets on every Lambda invocation adds milliseconds; use caching or the Secrets Manager extension.
A common pattern is to cache secrets in memory for the process lifetime and refresh on a timer or on a specific auth failure, rather than calling the API on every request.
HashiCorp Vault: The Power Tool
Vault is the most capable — and most complex — option. It's a dedicated secrets platform that runs as its own service (self-hosted or via HCP Vault) and is cloud-agnostic.
What sets Vault apart:
- Dynamic secrets. Instead of storing a static database password, Vault generates short-lived credentials on demand and revokes them automatically when the lease expires. A compromised credential is only useful for minutes.
- Encryption as a service. The transit engine lets apps encrypt/decrypt data without ever handling keys.
- Pluggable auth. Authenticate via Kubernetes service accounts, AWS IAM, OIDC, LDAP, AppRole, and more.
- Rich policy language for least-privilege access.
- Detailed audit devices logging every request.
A dynamic database secret flow looks like this:
# App authenticates (e.g. via Kubernetes auth), then requests creds
vault read database/creds/readonly-role
# Vault returns a username/password valid for, say, 1 hour
# When the lease ends, Vault drops the DB user automatically
The cost of that power:
- Operational overhead. You run, seal/unseal, back up, and upgrade Vault. It's a critical, highly-available piece of infrastructure.
- Learning curve. Policies, auth methods, secret engines, and leasing take time to master.
- It can become a single point of failure if not deployed with proper HA and disaster recovery.
Vault earns its keep in large organizations, multi-cloud environments, regulated industries, and anywhere dynamic secrets meaningfully shrink your attack surface.
How to Choose
Use this rough decision guide:
| Situation | Recommended tool |
|---|---|
| Local development | .env (gitignored) |
| Small app fully on AWS | AWS Secrets Manager |
| Serverless / AWS-native at scale | AWS Secrets Manager + caching |
| Multi-cloud or hybrid | HashiCorp Vault |
| Need dynamic, short-lived credentials | HashiCorp Vault |
| Regulated, high-security environment | Vault (or Secrets Manager with strict IAM) |
These aren't mutually exclusive. A very common, healthy setup is: .env locally, and Secrets Manager or Vault in staging and production — with the same environment-variable interface in your app so the code doesn't change. An init container or entrypoint script fetches secrets from the store and exports them as environment variables before your app boots.
Universal Best Practices
No matter which tool you pick:
- Never commit secrets to source control. Enforce with automated scanning and pre-commit hooks.
- Apply least privilege. Each service reads only the secrets it needs, nothing more.
- Rotate regularly, and make rotation painless enough that you actually do it.
- Audit access. If you can't answer "who read this secret last week?", you have a gap.
- Separate secrets by environment. Dev, staging, and prod must never share credentials.
- Have a leak response plan. Assume a secret will leak eventually — know how fast you can rotate it.
- Prefer references over values in CI/CD. Store the secret in the manager and pass a reference or use OIDC federation, rather than copying the raw value into pipeline variables.
FAQ
Is it ever okay to use .env in production?
For a hobby project on a single locked-down box, maybe. For anything with real users, real data, or a team — no. You lose audit trails and rotation, and the leak risk is high. Use a managed store and inject secrets as environment variables at runtime instead.
Can I use AWS Secrets Manager outside of AWS? Technically yes, via the API with access keys, but it's awkward. Its value comes from tight IAM integration. If you're multi-cloud, Vault or a cloud-agnostic tool fits better.
What's the difference between AWS Secrets Manager and AWS Parameter Store?
Parameter Store (SSM) is cheaper and fine for config plus basic SecureString secrets, but it lacks built-in automatic rotation and has lower throughput. Secrets Manager adds managed rotation, cross-account access, and higher limits. Many teams use Parameter Store for config and Secrets Manager for true secrets.
Do I still need Vault if I'm all-in on AWS? Often not. Secrets Manager covers most AWS-native needs. Reach for Vault when you need dynamic secrets across many systems, encryption-as-a-service, or a single secrets layer spanning multiple clouds and on-prem.
How do I keep secrets out of my Docker images?
Never COPY a .env or use build-time ARG for secrets — they persist in image layers. Inject secrets at runtime via environment variables, mounted files, or by fetching from your secrets manager in the entrypoint. Use BuildKit secret mounts if you need a secret only during the build.
What should I do if a secret leaks? Rotate it immediately — assume it's already compromised. Revoke the old value, issue a new one, and check audit logs for unauthorized use. This is exactly why short-lived dynamic secrets (Vault) are so valuable: the window of exposure is tiny.
Conclusion
Secret management is a spectrum, not a single choice. Start with gitignored .env files for local development, graduate to AWS Secrets Manager when you're running on AWS and want managed encryption and rotation, and adopt HashiCorp Vault when you need dynamic credentials, multi-cloud reach, or the tightest possible attack surface. Whichever you choose, keep secrets out of Git, enforce least privilege, and make rotation routine — the tool matters less than the discipline around it.