Secrets management
What counts as a secret
| Secret | Used for | Rotation impact |
|---|---|---|
AUTH_SECRET | better-auth session HMAC | All sessions invalidated on rotate |
DATABASE_URL (password) | DB auth | Need coordinated app + DB rotate |
STRIPE_SECRET_KEY | Stripe API calls | Get a new test/live key from Stripe Dashboard |
STRIPE_WEBHOOK_SECRET | Verify inbound webhooks | Get from the webhook endpoint config |
POSTHOG_API_KEY | Audit log batching | Get from PostHog project |
Where they live
| Environment | Storage |
|---|---|
| Local dev | .env (gitignored) |
| Staging | AWS Secrets Manager (separate path prefix) |
| Production | AWS Secrets Manager |
Never commit .env* files. The .gitignore excludes them by default.
Injection
ECS task definition
"secrets": [
{ "name": "AUTH_SECRET", "valueFrom": "arn:aws:secretsmanager:us-east-1:111:secret:fleapo/prod/auth-secret" },
{ "name": "DATABASE_URL", "valueFrom": "arn:aws:secretsmanager:us-east-1:111:secret:fleapo/prod/database-url" }
]
ECS reads at task start, injects as env vars. Task execution role needs secretsmanager:GetSecretValue on the listed ARNs.
GitHub Actions
For deploys, use OIDC + IAM role assumption — no long-lived access keys in repo secrets:
permissions:
id-token: write
contents: read
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::111:role/gha-deploy
aws-region: us-east-1
Rotation playbook
AUTH_SECRET
- Generate new value:
openssl rand -hex 64. - Update Secrets Manager.
- Trigger ECS service deploy (new tasks pick up the new value on start).
- All users get signed out. Schedule for a low-traffic window.
There's no overlap mode today — old and new can't both validate. If you need zero-downtime, add AUTH_SECRET_OLD support to better-auth (custom plugin work).
DATABASE_URL
- Provision new credentials on RDS.
- Update Secrets Manager with the new URL.
- Roll the ECS service.
- Drop the old credentials after the rollout completes and you've verified the audit log shows the new user in
pg_stat_activity.
STRIPE_*
- Generate a new key in Stripe Dashboard (keep the old key active).
- Update Secrets Manager.
- Roll ECS.
- Revoke the old key in Stripe.
If you revoke before rolling, in-flight checkouts return 503.
POSTHOG_API_KEY
Low-risk; audit log goes dark until rotation completes. Same procedure: update Secrets Manager → roll ECS.
Local dev secrets
.env.example lives in each repo. Copy to .env and fill in. Use throwaway test keys (sk_test_*). Never use production secrets locally.
What NOT to do
- Don't bake secrets into Docker images.
- Don't commit them to repo, even temporarily.
- Don't echo them in CI logs (
set +xaround any line that touches them). - Don't log them from app code, ever.
- Don't store them in browser env (
VITE_*is bundled into the SPA and shipped to clients).
The frontend's VITE_API_BASE_URL is NOT secret — it's the base URL of a public API. Don't accidentally treat sensitive values like STRIPE_SECRET_KEY as VITE_*.