ADR-0005: Multi-environment .env files split into .infra and .app#
Date: 2024-03-10
Status: Accepted
Deciders: Core maintainers
Context#
Generated projects need environment-specific configuration for at least three stages: dev, feature, and prod. The initial approach was a single .env.<env> file per environment containing both Docker Compose service variables (POSTGRES_*) and application-level variables (DATABASE_URL, APP_ENV).
This created two problems:
- Conceptual mixing: Docker Compose's
env_filedirective loaded the same file for both thedbservice (which only needsPOSTGRES_*) and the app container (which only needsDATABASE_URL). This made the blast radius of a misconfiguration unnecessarily large. - Secrets discipline: It was unclear which variables should be committed to version control and which should be injected at deploy time.
Decision#
Split each environment file into two:
.env.<env>.infra— consumed by thedbDocker Compose service. Contains onlyPOSTGRES_DB,POSTGRES_USER,POSTGRES_PASSWORD. Safe to commit fordevandfeature; must be injected via secrets manager forprod..env.<env>.app— consumed by the application container (workspace service). ContainsAPP_ENV,DATABASE_URL,DATABASE_*application-level variables. Commitable fordev; rotate credentials forfeature/prod.
docker-compose.yml loads env_file: .env.${APP_ENV}.infra for db and env_file: .env.${APP_ENV}.app for workspace.
Consequences#
- Positive: Clear separation of concerns; reduces the risk of app-layer leaking into the DB container; sets up a natural injection boundary for secrets managers (doppler, sops, Vault).
- Positive: Easier to audit: a security reviewer can inspect
.infrafiles independently. - Negative: More files to manage per environment (6 instead of 3). Mitigated by generation — the files are always scaffold-produced, never hand-written.
- Neutral: The original
.env.<env>files are no longer generated. Projects generated before this change need a one-time migration (rename and split).
Secrets guidance#
For prod, never commit real credentials. Use one of:
- doppler —
doppler run -- docker compose up - sops — encrypted
.env.prod.infrain version control - CI secrets — inject via
${{ secrets.POSTGRES_PASSWORD }}in GitHub Actions