Secrets Management in Production: Vault, AWS Secrets Manager, and SOPS
Hardcoded credentials in configuration files and environment variables are the number one cause of security breaches in cloud-native applications. Secrets management tools like HashiCorp Vault, AWS Secrets Manager, and Mozilla SOPS solve this by centralizing, encrypting, and automating access to sensitive data. Therefore, this guide covers practical implementation patterns for each tool and helps you choose the right one for your infrastructure.
Why Secrets Management Matters
The traditional approach — storing database passwords in .env files, API keys in CI/CD variables, and TLS certificates on disk — creates several problems. Secrets sprawl across dozens of systems with no audit trail. Rotation requires manual updates across every service. A single leaked .env file exposes everything. Moreover, compliance frameworks like SOC 2, PCI DSS, and HIPAA require centralized secrets management with rotation and audit logging.
A proper secrets management system provides four capabilities: centralized storage with encryption at rest, access control with audit logging, automatic rotation, and dynamic secrets that are created on-demand and expire automatically. Additionally, it should integrate with your deployment pipeline so applications never see secrets in plaintext configuration files.
HashiCorp Vault: The Full-Featured Solution
Vault is the most flexible secrets management tool, supporting static secrets, dynamic credentials, PKI certificates, and encryption as a service. It runs as a standalone server (or cluster) and provides HTTP API, CLI, and UI access. However, this flexibility comes with operational complexity — running Vault in production requires careful attention to storage backends, unsealing, and high availability.
# Initialize Vault with auto-unseal (AWS KMS)
vault operator init -recovery-shares=5 -recovery-threshold=3
# Enable secrets engines
vault secrets enable -path=database database
vault secrets enable -path=kv-v2 kv-v2
# Configure dynamic database credentials
vault write database/config/myapp-db \
plugin_name=postgresql-database-plugin \
allowed_roles="myapp-readonly,myapp-readwrite" \
connection_url="postgresql://{{username}}:{{password}}@db.internal:5432/myapp" \
username="vault_admin" \
password="vault_admin_password"
# Create a role that generates time-limited credentials
vault write database/roles/myapp-readonly \
db_name=myapp-db \
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"
# Application requests credentials — gets unique, time-limited creds
vault read database/creds/myapp-readonly
# Key Value
# lease_id database/creds/myapp-readonly/abc123
# lease_duration 1h
# username v-token-myapp-r-xyz789
# password A1b2C3d4E5-random-generatedDynamic secrets are Vault’s killer feature. Instead of sharing a single database password across all application instances, each instance gets its own credentials that expire automatically. If an instance is compromised, only its credentials are affected, and they expire within the hour. Consequently, credential rotation happens automatically without any application changes.
AWS Secrets Manager: Cloud-Native Simplicity
If your infrastructure is primarily AWS, Secrets Manager is the path of least resistance. It integrates natively with RDS, Lambda, ECS, and EKS. Automatic rotation for RDS credentials works with a single configuration. Furthermore, IAM policies control access, so you use the same permission model as the rest of your AWS infrastructure.
import boto3
import json
from botocore.exceptions import ClientError
# Retrieve a secret — with caching for performance
class SecretCache:
def __init__(self, region="us-east-1"):
self.client = boto3.client("secretsmanager", region_name=region)
self._cache = {}
def get_secret(self, secret_name):
if secret_name in self._cache:
return self._cache[secret_name]
try:
response = self.client.get_secret_value(SecretId=secret_name)
secret = json.loads(response["SecretString"])
self._cache[secret_name] = secret
return secret
except ClientError as e:
if e.response["Error"]["Code"] == "ResourceNotFoundException":
raise ValueError(f"Secret {secret_name} not found")
raise
def invalidate(self, secret_name):
self._cache.pop(secret_name, None)
# Usage in application
secrets = SecretCache()
db_creds = secrets.get_secret("prod/myapp/database")
connection_string = f"postgresql://{db_creds['username']}:{db_creds['password']}@{db_creds['host']}/myapp"
# Enable automatic rotation for RDS secrets
# AWS provides Lambda rotation functions out of the box
# aws secretsmanager rotate-secret --secret-id prod/myapp/database \
# --rotation-lambda-arn arn:aws:lambda:us-east-1:123456:function:SecretsManagerRotation \
# --rotation-rules AutomaticallyAfterDays=30Mozilla SOPS: Encrypted Files in Git
SOPS (Secrets OPerationS) takes a different approach — it encrypts secret values within configuration files while leaving keys visible. This means you can store encrypted secrets directly in Git, review diffs, and use your existing deployment pipeline. SOPS supports AWS KMS, GCP KMS, Azure Key Vault, and age/PGP for encryption.
# secrets.enc.yaml — encrypted with SOPS
# Keys are visible (for diffs), values are encrypted
database:
host: ENC[AES256_GCM,data:abc123...==,tag:xyz...]
port: ENC[AES256_GCM,data:def456...==,tag:uvw...]
username: ENC[AES256_GCM,data:ghi789...==,tag:rst...]
password: ENC[AES256_GCM,data:jkl012...==,tag:opq...]
api_keys:
stripe: ENC[AES256_GCM,data:mno345...==,tag:lmn...]
sendgrid: ENC[AES256_GCM,data:pqr678...==,tag:ijk...]
sops:
kms:
- arn: arn:aws:kms:us-east-1:123456:key/abc-def-ghi
version: 3.8.1# Encrypt a file
sops --encrypt --kms arn:aws:kms:us-east-1:123456:key/abc secrets.yaml > secrets.enc.yaml
# Edit encrypted file (decrypts in temp editor, re-encrypts on save)
sops secrets.enc.yaml
# Decrypt for deployment
sops --decrypt secrets.enc.yaml > /tmp/secrets.yaml
# Use with Kubernetes — decrypt and apply
sops --decrypt k8s-secrets.enc.yaml | kubectl apply -f -
# Use with Terraform
# terraform plan -var-file=<(sops --decrypt terraform.enc.tfvars)SOPS works exceptionally well for GitOps workflows where all configuration lives in Git. You get version history, pull request reviews, and audit trails through Git itself. However, SOPS doesn't handle dynamic secrets or automatic rotation — it's purely a storage and encryption solution.
Kubernetes External Secrets and Secret Rotation
In Kubernetes environments, the External Secrets Operator bridges the gap between your secrets store (Vault, AWS Secrets Manager, GCP Secret Manager) and Kubernetes Secrets. It periodically syncs secrets from the external store into Kubernetes, so applications consume standard Kubernetes Secrets while the actual values live in a centralized, managed store.
# ExternalSecret resource — syncs from AWS Secrets Manager to K8s Secret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: myapp-database
namespace: production
spec:
refreshInterval: 5m # Check for updates every 5 minutes
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: myapp-db-credentials # Kubernetes Secret name
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: prod/myapp/database
property: username
- secretKey: password
remoteRef:
key: prod/myapp/database
property: password
- secretKey: host
remoteRef:
key: prod/myapp/database
property: hostFor secret rotation, the pattern depends on your tool. Vault handles rotation through dynamic secrets with TTLs. AWS Secrets Manager uses Lambda rotation functions. For application-managed rotation, implement a two-credential pattern: generate new credentials, update the secret store, wait for all instances to pick up the new credentials, then revoke the old ones.
Choosing the Right Tool
Choose Vault when: You need dynamic secrets, multi-cloud support, PKI management, or encryption as a service. Be prepared for operational overhead.
Choose AWS Secrets Manager when: Your infrastructure is AWS-centric and you want managed simplicity with automatic RDS rotation.
Choose SOPS when: You want encrypted secrets in Git for GitOps workflows, your team is small, and you don't need dynamic secrets.
Combine them: Many teams use SOPS for infrastructure-as-code secrets (Terraform variables, Kubernetes manifests) and Vault or AWS Secrets Manager for application runtime secrets.
Related Reading:
- Zero Trust Kubernetes Security
- Supply Chain Security with SLSA and Sigstore
- GitOps with ArgoCD and Crossplane
Resources:
In conclusion, secrets management is a non-negotiable part of production infrastructure. Start with the tool that fits your existing stack — AWS Secrets Manager for AWS-heavy environments, SOPS for GitOps, Vault for complex multi-cloud setups. The critical principle is that secrets should never exist in plaintext configuration files, environment variables, or CI/CD settings.