Skip to main content

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.example files 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.example is 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 }}"