Env and secrets
Where configuration lives, and why a missing variable kills the app.
There is no root .env, ever
Configuration lives in exactly two places, each with a committed .env.example beside it
(ADR-0018):
| Path | For |
|---|---|
tools/dev/env/*.env | backends, local development |
apps/*/web/.env | frontends |
A root .env gets loaded by whatever tool happens to look for one, which means the same variable
name silently means different things to different processes.
Production infrastructure credentials live off the repository entirely, in ~/.lifeos-prod/, and
are uploaded to Dokploy service environments by tools/deploy/infra/deploy.sh.
No :default on any placeholder
Every placeholder in application-platform.yaml is a bare ${VAR}. A missing variable kills the
app at boot rather than letting it run degraded (ADR-0004).
The failure mode this prevents is specific: a defaulted mail host that silently drops every notification, or a defaulted database URL that points somewhere harmless while real writes go nowhere. Both look healthy. Neither is.
Fail loud, never silent
The same principle runs through everything operational here:
- Expected-count checks that refuse a partial run rather than reporting success on half the work.
.partial→ verify →mv, so an interrupted write never leaves a file that looks complete.- Test restores. A backup nobody has restored is a hypothesis.
- A dead-man's-switch ping on success, so silence is the alarm. A job that stops running entirely produces no failure email — which is exactly why the absence of a success ping has to be what pages you.
Rotating a credential
When a secret is rotated, every place holding a copy has to move together. Today that means:
- the GitHub Actions secret, where CI reads it;
~/.lifeos-prod/*.envon the machine that runs the infra deploy script;- the Dokploy service environment for anything already running with the old value.
PROD_IAM_KEY_ENCRYPTION_KEY is the exception that cannot be rotated casually — changing it
invalidates every token ever issued.