Skip to main content

Secrets management

What counts as a secret

SecretUsed forRotation impact
AUTH_SECRETbetter-auth session HMACAll sessions invalidated on rotate
DATABASE_URL (password)DB authNeed coordinated app + DB rotate
STRIPE_SECRET_KEYStripe API callsGet a new test/live key from Stripe Dashboard
STRIPE_WEBHOOK_SECRETVerify inbound webhooksGet from the webhook endpoint config
POSTHOG_API_KEYAudit log batchingGet from PostHog project

Where they live

EnvironmentStorage
Local dev.env (gitignored)
StagingAWS Secrets Manager (separate path prefix)
ProductionAWS 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

  1. Generate new value: openssl rand -hex 64.
  2. Update Secrets Manager.
  3. Trigger ECS service deploy (new tasks pick up the new value on start).
  4. 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

  1. Provision new credentials on RDS.
  2. Update Secrets Manager with the new URL.
  3. Roll the ECS service.
  4. Drop the old credentials after the rollout completes and you've verified the audit log shows the new user in pg_stat_activity.

STRIPE_*

  1. Generate a new key in Stripe Dashboard (keep the old key active).
  2. Update Secrets Manager.
  3. Roll ECS.
  4. 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 +x around 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_*.