ADR 0002: Secrets Management
Status
Accepted
Context
Secrets must never be stored unencrypted in repositories and must work across both Kubernetes (K8s) and non-Kubernetes deployment targets.
The requirements include:
- No secrets committed to Git repositories
- Support for multiple deployment platforms (K8s, Vercel, Railway, Droplets)
- Audit trail for secret access
- Rotation capabilities
- Developer-friendly workflow
- Integration with cloud provider secret stores
Decision
Use External Secrets Operator (ESO) for Kubernetes and provider secret stores for non-K8s targets. CI injects secrets at deploy time for non-K8s workflows.
Kubernetes Approach
- Install External Secrets Operator in each cluster
- Configure SecretStore resources pointing to cloud providers (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault)
- Define ExternalSecret resources in ops repo that reference secrets by name
- ESO syncs secrets from cloud stores into Kubernetes Secrets automatically
Non-Kubernetes Approach
- Use platform-native secret stores (Vercel Environment Variables, Railway Variables, etc.)
- CI workflows inject secrets at deployment time via platform APIs
- Secrets are never in ops repo manifests
All Environments
- Maintain
.env.examplefiles as documentation of required secrets - Use descriptive secret names with environment prefixes
- Implement secret rotation policies
Consequences
Positive
- K8s secrets are sourced from AWS/GCP/Azure secret stores via ESO
- Non-K8s deploys use provider secret stores (Vercel, Railway, etc.)
.env.exampleis a required artifact and no real secrets live in Git- Centralized secret management with cloud provider tools
- Audit trail through cloud provider logs
Negative
- Requires setup and configuration of ESO for K8s clusters
- Developers need access to cloud secret stores for local development
- Additional complexity in deployment pipeline
Neutral
- Teams must establish secret naming conventions
- Secret rotation procedures need to be documented per platform
- Local development requires separate secret management (e.g., direnv, local .env files)
Implementation Notes
Kubernetes Example
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
spec:
secretStoreRef:
name: aws-secrets-manager
target:
name: app-secrets
data:
- secretKey: DATABASE_URL
remoteRef:
key: prod/app/database-url
CI/CD Example (Non-K8s)
- name: Deploy to Vercel
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
run: |
vercel deploy --prod \
--env DATABASE_URL="${{ secrets.DATABASE_URL }}"
Related Decisions
- ADR 0001: GitOps Engine for Kubernetes - Historical K8s deployment foundation
- Provider Adapters - Platform-specific secret injection