Secrets Management at Scale: Vault vs AWS Secrets Manager vs SOPS, Rotation and Dynamic Secrets
Secrets Management at Scale: Vault vs AWS Secrets Manager vs SOPS, Rotation and Dynamic Secrets
Every team starts with a .env file in a gitignored directory. That works until you have more than one server, more than one environment, or more than one person. Then you need rotation, audit logs, per-service access control, and a way to survive a compromised host without rotating every secret by hand. This guide compares the three tools that solve this at scale: HashiCorp Vault, AWS Secrets Manager, and SOPS, and covers the two patterns that determine which one fits: static secrets with rotation versus dynamic, short-lived secrets.
The Two Secret Models
| Model | Lifecycle | Rotation | Blast Radius |
|---|---|---|---|
| Static secret | Created once, used until rotated | Manual or scheduled; downtime risk if credentials change mid-session | Long-lived credential = wide blast radius if leaked |
| Dynamic secret | Generated on demand, short TTL | Automatic; each lease is a fresh credential | Compromised credential expires in minutes |
Static secrets are database passwords, API keys, and TLS private keys that exist independently of who requested them. Dynamic secrets are credentials a secrets system mints on request, scoped to the requester, with a lease that expires. Dynamic secrets are strictly better for blast radius, but they require the secrets system to have write access to the backing service (it has to create the user or key). That is not always possible, so most teams run a mix.
Tool Comparison
| Dimension | HashiCorp Vault | AWS Secrets Manager | SOPS |
|---|---|---|---|
| Deployment | Self-hosted (or HCP Vault) | Managed AWS service | CLI tool, no server |
| Storage | Encrypted KV store (Raft or external) | AWS-managed, KMS-encrypted | Encrypted files in git |
| Dynamic secrets | First-class (DB, AWS, PKI, SSH, cloud) | Limited (RDS rotation only) | Not supported |
| Rotation | Rotation functions (custom or built-in) | Built-in for RDS/DocumentDB/Redshift; Lambda for custom | Manual; re-encrypt with new key |
| Access control | Policy-based (HCL), identity-based | IAM + resource policies | KMS/AWS IAM + age keys |
| Audit | Built-in audit devices (file, syslog, socket) | CloudTrail | Git history |
| Cost | Self-hosted is free; HCP Vault is paid | 0.05 per 10K API calls | Free (you pay for KMS usage) |
| Best for | Dynamic secrets, multi-cloud, on-prem | AWS-native workloads, RDS rotation | Config files in git, GitOps |
SOPS: Encrypted Secrets in Git
SOPS (Secrets OPerationS) encrypts only the values in a YAML or JSON file, leaving keys readable. The encrypted file lives in git, decrypted at deploy time by someone or something with the KMS key. There is no server, no database, no daemon.
# Encrypt a file with an AWS KMS key
sops --encrypt --kms arn:aws:kms:us-east-1:123456789012:key/abcd-1234 \
--in-place secrets.yaml
# secrets.yaml after SOPS encryption (keys readable, values encrypted)
database:
host: ENC[AES256_GCM,data:8J3k,iv:abc=,tag:def=,type:str]
password: ENC[AES256_GCM,data:Kp9m,iv:ghi=,tag:jkl=,type:str]
api_key: ENC[AES256_GCM,data:Xy2v,iv:mno=,tag:pqr=,type:str]
sops:
kms:
- arn: arn:aws:kms:us-east-1:123456789012:key/abcd-1234
created_at: "2026-07-28T12:00:00Z"
enc: CiQ...
lastmodified: "2026-07-28T12:00:00Z"
mac: ENC[AES256_GCM,data:...,type:str]
SOPS shines for GitOps: the encrypted file is versioned, reviewable in PRs, and decrypted by your CD tool (Argo CD with the KSOPS plugin, Flux with SOPS integration). The weakness is rotation: changing a secret means re-encrypting the file and committing it, and there is no runtime to mint dynamic credentials.
# Flux Kustomization that decrypts SOPS files at reconcile time
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: app-secrets
spec:
path: ./manifests
decryption:
provider: sops
sourceRef:
kind: GitRepository
name: app-repo
AWS Secrets Manager: Managed, AWS-Native
Secrets Manager is a fully managed service. You store a secret (a JSON blob or key-value pair), attach a rotation Lambda, and applications fetch it via the SDK or an IAM-authenticated API call. No servers, no storage to manage.
import boto3
client = boto3.client("secretsmanager", region_name="us-east-1")
def get_secret(secret_name: str) -> dict:
response = client.get_secret_value(SecretId=secret_name)
import json
return json.loads(response["SecretString"])
# Application code
db_creds = get_secret("prod/checkout-db")
conn = psycopg2.connect(
host=db_creds["host"],
user=db_creds["username"],
password=db_creds["password"],
)
Built-in rotation handles RDS, DocumentDB, and Redshift: AWS provides Lambda functions that rotate the password on a schedule, alternating between two users to avoid downtime. For other secrets (API keys, third-party tokens), you write a custom rotation Lambda implementing four steps: create the new secret, test it, update the secret value, then verify the old one is retired.
# Custom rotation Lambda (simplified)
def lambda_handler(event, context):
token = event["token"]
step = event["step"]
secret_name = event["secretId"]
if step == "createSecret":
new_password = generate_password()
client.put_secret_value(
SecretId=secret_name,
SecretString=json.dumps({"password": new_password}),
VersionStage="AWSPENDING",
ClientRequestToken=token,
)
elif step == "setSecret":
# Apply the new password to the backing service
update_third_party_api_key(get_pending_secret(secret_name, token))
elif step == "finishSecret":
# Promote pending to current
client.update_secret_version_stage(
SecretId=secret_name,
VersionStage="AWSCURRENT",
MoveToVersion=token,
RemoveFromVersion=event["clientVersionToken"],
)
The cost model matters at scale: 0.05 per 10,000 API calls. A fleet of 200 microservices polling every minute racks up roughly $1,000/month in API calls alone. Use caching (the AWS SDK has a built-in caching layer) to cut calls by 90%+.
HashiCorp Vault: Dynamic Secrets and Multi-Cloud
Vault is the only tool here that does dynamic secrets well. When a service requests a database credential, Vault creates a real database user with a scoped grant and a TTL, returns it, and revokes it when the lease expires or is renewed.
# Enable the database secrets engine
vault secrets enable database
# Configure a PostgreSQL connection
vault write database/config/checkout-prod \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://{{username}}:{{password}}@db.internal:5432/checkout?sslmode=disable" \
allowed_roles="checkout-app" \
username="vault-admin" \
password="..."
# Define a role: credentials valid 1 hour, max 24 hours
vault write database/roles/checkout-app \
db_name=checkout-prod \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
from hvac import Client
vault = Client(url="https://vault.internal:8200")
vault.auth.kubernetes.login(role="checkout-app", jwt=get_service_account_jwt())
# Each call mints a fresh, scoped, time-limited database user
creds = vault.read("database/creds/checkout-app")
# -> {"username": "v-token-checkout-app-abc123", "password": "...", "lease_id": "...", "lease_duration": 3600}
conn = psycopg2.connect(
host="db.internal",
user=creds["username"],
password=creds["password"],
)
The credential did not exist before this call and will not exist after the lease expires. If an attacker steals it, they have a user that can only SELECT and dies in an hour. Compare that to a static password that works until someone notices and manually rotates it.
Vault also does dynamic AWS IAM credentials, PKI certificates (mint a cert per service with a short TTL instead of one long-lived cert), SSH keys, and cloud KMS key access. The audit device records every request:
# Enable file audit logging
vault audit enable file file_path=/var/log/vault/audit.log
# Every secret access is now logged:
# {"time":"2026-07-28T12:00:00Z","type":"request","auth":{"entity_id":"checkout-app"},"request":{"path":"database/creds/checkout-app"}}
The cost is operational: Vault is a distributed system you must run (or pay for HCP Vault). In production you run 3-5 Vault nodes with Raft storage, an auto-unseal mechanism (KMS or transit), and monitoring. This is real infrastructure.
Kubernetes Integration
All three integrate with Kubernetes, but the patterns differ.
Vault: the Vault Agent or CSI provider injects secrets as files or env vars into pods. The pod authenticates with its service account JWT, no long-lived token needed:
# Pod annotation triggers the agent
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "checkout-app"
vault.hashicorp.com/agent-inject-secret-db: "database/creds/checkout-app"
AWS Secrets Manager: the External Secrets Operator fetches from Secrets Manager and syncs into a Kubernetes Secret that pods mount:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: checkout-db
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secretsmanager
kind: ClusterSecretStore
target:
name: checkout-db-secret
data:
- secretKey: password
remoteRef:
key: prod/checkout-db
property: password
SOPS: decrypted at the CD layer (Flux or Argo CD with KSOPS), producing a Kubernetes Secret before pods start. No runtime dependency on an external service.
Decision Framework
Choose SOPS when:
- Your secrets are config files that change rarely
- You want secrets versioned in git alongside manifests (GitOps)
- You do not need dynamic credentials or automated rotation
- You have no budget or appetite for running a secrets server
Choose AWS Secrets Manager when:
- You are all-in on AWS and want zero infrastructure to manage
- Your main rotation need is RDS/DocumentDB/Redshift passwords
- You are fine with per-secret and per-API-call pricing at your scale
- You do not need dynamic secrets for non-AWS resources
Choose HashiCorp Vault when:
- You need dynamic, short-lived credentials (the strongest blast-radius reduction)
- You operate across multiple clouds or on-prem
- You need fine-grained, policy-based access control beyond IAM
- You have platform engineers who can run a HA Vault cluster
A common production setup: SOPS for deploy-time config, Vault for runtime dynamic secrets (database, cloud API), and AWS Secrets Manager for the RDS rotation you do not want to build yourself. They are not mutually exclusive.
Adoption Checklist
- Inventory every secret: where it lives, who reads it, how often it rotates, what happens if it leaks
- Classify each as static-needs-rotation or dynamic-eligible; prefer dynamic where the backing service allows it
- Pick the tool per classification, not one tool for everything
- Eliminate plaintext secrets in env files, CI variables, and chat history; import them into the chosen system
- Enable audit logging on day one; a secrets system without audit is a liability
- Add caching at the application layer to control API-call costs (Secrets Manager) or Vault request load
- Test rotation in staging before production: a broken rotation Lambda is an outage waiting to happen
- Run a quarterly secret-leak scan (git-secrets, trufflehog) against your repos to catch stragglers
MatterAI builds frontier AI infrastructure for engineering teams — from inference-optimized models to autonomous coding agents and agentic code reviews.
Explore what we're building:
- Orbital IDE — Autonomous AI coding agent with background agents and deep codebase memory
- AI Code Reviews — Agentic pre-commit reviews across GitHub, GitLab, and Bitbucket
- Axon Models — Frontier-grade reasoning models at 70% lower inference cost
Share this Guide:
More Guides
Grafana Stack vs SigNoz: Choosing an OpenTelemetry-Native Observability Platform
Compare the Grafana LGTM stack (Loki, Tempo, Mimir) with SigNoz for OpenTelemetry observability: architecture, storage engines, operational overhead, cost at scale, and migration paths.
10 min readPrompt Injection Defense: Securing LLM Apps Against Jailbreaks, RAG Poisoning, and Tool-Use Exploits
Defend production LLM applications against direct jailbreaks, indirect injection via RAG pipelines, and tool-use exploits with layered defenses, input filtering, and least-privilege tool design.
10 min readRate Limiting and Backpressure: Token Bucket vs Leaky Bucket, Distributed Rate Limiters with Redis
Implement rate limiting and backpressure for high-traffic APIs with token bucket and leaky bucket algorithms, sliding window counters, and atomic distributed rate limiters using Redis and Lua.
9 min readSupply Chain Security: SLSA, SBOMs, Sigstore, and Dependency Pinning in CI/CD
Secure your software supply chain with SLSA provenance levels, SBOM generation, Sigstore keyless signing, and dependency pinning strategies integrated into CI/CD pipelines.
10 min readLLM Integration for AI Agents: A Complete Engineering FAQ
Everything engineers need to know about integrating, testing, and productionizing LLMs in AI agents: model selection, tool calling, structured outputs, error handling, observability, and cost optimization.
22 min readContinue Reading
Grafana Stack vs SigNoz: Choosing an OpenTelemetry-Native Observability Platform
Compare the Grafana LGTM stack (Loki, Tempo, Mimir) with SigNoz for OpenTelemetry observability: architecture, storage engines, operational overhead, cost at scale, and migration paths.
10 min readPrompt Injection Defense: Securing LLM Apps Against Jailbreaks, RAG Poisoning, and Tool-Use Exploits
Defend production LLM applications against direct jailbreaks, indirect injection via RAG pipelines, and tool-use exploits with layered defenses, input filtering, and least-privilege tool design.
10 min readRate Limiting and Backpressure: Token Bucket vs Leaky Bucket, Distributed Rate Limiters with Redis
Implement rate limiting and backpressure for high-traffic APIs with token bucket and leaky bucket algorithms, sliding window counters, and atomic distributed rate limiters using Redis and Lua.
9 min readShip Faster. Ship Safer.
Join thousands of engineering teams using MatterAI to autonomously build, review, and deploy code with enterprise-grade precision.
