Secrets Management Strategy
Status: Active / Golden
Decision Date: 2026-01-18
Scope: All environments (local, Elastic Muscle, CI/CD, production)
🎯 Core Principle
ALL secrets MUST be managed through Doppler.
This is a non-negotiable architectural decision to ensure: - ✅ Single source of truth - ✅ Consistent across all environments - ✅ Secure (no credentials in code/scripts) - ✅ Easy to rotate - ✅ Auditable
🚫 Anti-Patterns (DO NOT DO)
❌ Manual Environment Variable Syncing
❌ Hardcoded Secrets in Scripts
❌ .env Files in Version Control
❌ Passing Secrets as Arguments
✅ Correct Patterns
1. Local Development
# Load secrets from Doppler
doppler run -- pnpm dev
# Or for one-off commands
doppler run -- npx tsx scripts/some-script.ts
2. Elastic Muscle (Remote Server)
# Setup once (installs Doppler CLI and configures project)
./scripts/setup-elastic-muscle.sh
# Then always use Doppler
ssh elastic-muscle
cd singular-dream
doppler run -- npx tsx scripts/refactor-directory.ts
3. Dispatch to Remote
# Dispatch script automatically uses Doppler
./scripts/dispatch-to-elastic-muscle.sh refactor-directory-overnight
4. CI/CD (GitHub Actions, Vercel, etc.)
# GitHub Actions
- name: Run tests
env:
DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN }}
run: doppler run -- pnpm test
📋 Setup Checklist
Local Machine
- [x] Doppler CLI installed (
brew install dopplerhq/cli/doppler) - [x] Authenticated (
doppler login) - [x] Project configured (
doppler setup) - [x] All scripts use
doppler run --
Elastic Muscle
- [x] Doppler CLI installed (via
setup-elastic-muscle.sh) - [x] Service token configured in
~/.zshrc - [x] Project configured (
doppler setup --project singular-dream --config dev) - [x] All scripts use
doppler run --
CI/CD
- [ ]
DOPPLER_TOKENadded to GitHub Secrets - [ ]
DOPPLER_TOKENadded to Vercel environment variables - [ ] All workflows use
doppler run --
🔐 Doppler Configuration
Project Structure
singular-dream (project)
├── dev (config)
│ ├── SENTRY_DSN
│ ├── UPSTASH_REDIS_REST_URL
│ ├── UPSTASH_REDIS_REST_TOKEN
│ ├── UPSTASH_VECTOR_REST_URL
│ ├── UPSTASH_VECTOR_REST_TOKEN
│ ├── UPSTASH_SEARCH_REST_URL
│ ├── UPSTASH_SEARCH_REST_TOKEN
│ └── ... (all other secrets)
├── stg (config)
│ └── ... (same secrets, staging values)
└── prd (config)
└── ... (same secrets, production values)
Access Tokens
Personal Token (for local development):
- Used for: doppler login
- Scope: Read/write access to all projects
- Location: Stored by Doppler CLI
Service Token (for servers/CI):
- Used for: Elastic Muscle, GitHub Actions, Vercel
- Scope: Read-only access to specific config (e.g., dev)
- Location: Environment variable DOPPLER_TOKEN
🛠️ Common Operations
Adding a New Secret
# Add via CLI
doppler secrets set NEW_SECRET="value" --config dev
# Or via dashboard
# https://dashboard.doppler.com/workplace/singular-dream/projects/singular-dream/configs/dev
Rotating a Secret
# Update in Doppler
doppler secrets set UPSTASH_REDIS_REST_TOKEN="new-token" --config dev
# Changes are immediately available everywhere
# No need to restart services (Doppler injects at runtime)
Viewing Secrets
# List all secrets
doppler secrets
# View specific secret
doppler secrets get SENTRY_DSN
# Download all as .env (for debugging)
doppler secrets download --no-file --format env
🏗️ Integration Examples
TypeScript Script
// scripts/some-script.ts
// NO NEED to import dotenv or read .env files
// Doppler injects all secrets as environment variables
const sentryDsn = process.env.SENTRY_DSN;
const redisUrl = process.env.UPSTASH_REDIS_REST_URL;
// Just use them directly!
Shell Script
#!/bin/bash
# scripts/some-script.sh
# NO NEED to source .env or export variables
# Doppler injects all secrets as environment variables
echo "Sentry DSN: $SENTRY_DSN"
echo "Redis URL: $UPSTASH_REDIS_REST_URL"
# Just use them directly!
Next.js
// next.config.mjs
// Doppler injects secrets at build time
export default {
env: {
// These are automatically available from Doppler
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
},
};
🚨 Emergency Procedures
If Doppler is Down
- DO NOT hardcode secrets as a workaround
- DO use Doppler's fallback mode:
- DO remove
.env.emergencyafter Doppler is back up
If Service Token is Compromised
- Revoke the token in Doppler dashboard
- Generate a new service token
- Update
DOPPLER_TOKENin: - Elastic Muscle
~/.zshrc - GitHub Secrets
- Vercel environment variables
📚 References
- Doppler Dashboard: https://dashboard.doppler.com/workplace/singular-dream
- Doppler Docs: https://docs.doppler.com
- Setup Script:
scripts/setup-elastic-muscle.sh - Dispatch Script:
scripts/dispatch-to-elastic-muscle.sh
⚖️ Enforcement
This is a Golden Architecture decision. Any deviation requires: 1. Documented justification 2. Architecture review 3. Update to this ADR
Violations will be flagged in code review.
🎯 Benefits Realized
| Benefit | Before | After |
|---|---|---|
| Secret Rotation | Manual, error-prone | One update in Doppler |
| Environment Parity | Drift between local/remote | Guaranteed identical |
| Onboarding | Share .env files | doppler login + doppler setup |
| Audit Trail | None | Full history in Doppler |
| Security | Secrets in scripts/files | Never in code |
Last Updated: 2026-01-18
Next Review: When adding new environments or services