# Environment & Secrets Management

> Single-source env files, symlinks for sharing, Key Vault for secrets, and .env.example templates for onboarding.

## When to Use

- Setting up a new project with multiple components sharing the same secrets
- Managing env vars across services, dashboards, desktop app, and mobile
- Onboarding new developers (or AI agents) who need working environment
- Migrating from hardcoded secrets to a vault

## The Pattern

```
~/.AppName/.env           ← SINGLE SOURCE (all secrets + config)
     │
     ├── backend/.env          → symlink
     ├── .env                  → symlink (root)
     ├── admin-web/.env.local  → symlink
     ├── user-web/.env.local   → symlink
     └── tracker-web/.env.local → symlink

Azure Key Vault            ← SECRET SOURCE OF TRUTH
     │
     └── Resolved at startup via resolveKeyVaultSecrets()
```

## Step-by-Step

### 1. Create the canonical env file

```bash
mkdir -p ~/.AppName
cat > ~/.AppName/.env << 'EOF'
# Core
NODE_ENV=development
PRODUCT_ID=myapp

# Database
DB_ENDPOINT=https://your-account.example.com:443/
DB_KEY=<your-db-key-here>
DB_NAME=myapp

# Auth
JWT_SECRET=<your-jwt-secret>

# External Services
STRIPE_SECRET_KEY=sk_test_...
AZURE_BLOB_CONNECTION_STRING=...

# Key Vault (for production — resolves above secrets from AKV)
AZURE_KEYVAULT_URL=https://kv-myapp.vault.azure.net/
EOF
```

### 2. Create symlinks

```bash
# All components read from the same file
ln -sfn ~/.AppName/.env ./backend/.env
ln -sfn ~/.AppName/.env ./.env
ln -sfn ~/.AppName/.env ./admin-web/.env.local
ln -sfn ~/.AppName/.env ./user-web/.env.local
```

**Benefits:**

- Change a secret once, all components pick it up
- No risk of out-of-sync env files
- `~/.AppName/.env` is outside the repo (can't be committed)

### 3. Create .env.example templates

For every component, create a committed template with placeholders:

```bash
# .env.example — Copy to .env and fill in real values
# Or set up symlinks: ln -sfn ~/.AppName/.env ./.env

NODE_ENV=development
PRODUCT_ID=myapp

# Database
DB_ENDPOINT=https://your-account.example.com:443/
DB_KEY=<your-db-key-here>
DB_NAME=myapp

# Auth
JWT_SECRET=<your-jwt-secret-here>

# Optional: Azure Key Vault (resolves secrets automatically)
# AZURE_KEYVAULT_URL=https://kv-myapp.vault.azure.net/
```

### 4. Key Vault resolution (production)

For production, secrets come from Azure Key Vault with env var fallback:

```typescript
// lib/keyvault.ts
import { SecretClient } from '@azure/keyvault-secrets';
import { DefaultAzureCredential } from '@azure/identity';

const SECRET_MAP: Record<string, string> = {
  DB_KEY: 'myapp-db-key',
  JWT_SECRET: 'myapp-jwt-secret',
  PAYMENT_KEY: 'myapp-payment-key',
};

export async function resolveKeyVaultSecrets(): Promise<void> {
  const vaultUrl = process.env.AZURE_KEYVAULT_URL;
  if (!vaultUrl) return; // Dev mode — use env vars directly

  const client = new SecretClient(vaultUrl, new DefaultAzureCredential());

  for (const [envVar, secretName] of Object.entries(SECRET_MAP)) {
    if (!process.env[envVar]) {
      try {
        const secret = await client.getSecret(secretName);
        process.env[envVar] = secret.value;
      } catch (e) {
        console.warn(`Failed to resolve ${secretName} from Key Vault`);
      }
    }
  }
}
```

Call at app startup (e.g., in Next.js `instrumentation.ts` or Fastify server bootstrap).

### 5. Gitignore everything sensitive

```gitignore
# Environment files
.env
.env.local
.env.*.local
*.env

# Credentials
*.pem
*.p12
*.pfx
*.key

# Config directories
.AppName/
```

## Secret Naming Convention

Use a product prefix for Key Vault secrets:

```
myapp-cosmos-key
myapp-jwt-secret
myapp-stripe-secret-key
myapp-blob-connection-string
```

This prevents collisions when multiple products share a Key Vault.

## Env Var Categories

| Category     | Example                         | Where to store                             |
| ------------ | ------------------------------- | ------------------------------------------ |
| **Secrets**  | DB_KEY, JWT_SECRET, PAYMENT_KEY | Key Vault (env var fallback for dev)       |
| **Config**   | PORT, NODE_ENV, PRODUCT_ID      | .env file directly                         |
| **Public**   | STRIPE_PUBLISHABLE_KEY          | .env file (safe to commit in .env.example) |
| **Computed** | Database name from PRODUCT_ID   | Code (don't store)                         |

## Checklist for Adding a New Secret

- [ ] Add to Key Vault with product prefix
- [ ] Map in SECRET_MAP (keyvault.ts or equivalent)
- [ ] Add placeholder to `~/.AppName/.env`
- [ ] Add placeholder to ALL `.env.example` files
- [ ] Document in AGENTS.md or env docs
- [ ] Verify all components can resolve it

## Anti-Patterns

- **Different values in different .env files** — Use symlinks to one source
- **Secrets in .env.example** — Only placeholders in committed files
- **Hardcoded secrets in code** — Always use process.env
- **No Key Vault for production** — Env vars leak in logs, crash reports, docker inspect
- **Forgetting to update .env.example** — New devs can't set up without it

## Related Skills

- [09 — Secret Scanning Guardrails](./09-secret-scanning.md)
- [17 — Multi-Repo Coordination](./17-multi-repo-coordination.md)
- [01 — AGENTS.md Pattern](./01-agents-md-pattern.md)
