compose: drive backend/worker/migrate config from .env (env_file)
Replace the per-setting environment allow-list with `env_file: .env` on the
three app-image services, so every setting in app/core/config.py is configurable
from .env with no compose edit. This kills the recurring trap where a documented
env var (OWNER_EMAIL, the AI keys, SMTP, APP_BASE_URL) silently didn't reach the
app because it wasn't on the hand-maintained list.
`env_file` is `required: false` so local/CI without a .env still works (falls
back to ${VAR:-default} interpolation + code defaults). The small `environment:`
block that remains is only for values that must NOT come from .env:
- RUN_MIGRATIONS=1 (backend) — a deploy flag, not an app setting.
- DATABASE_URL — pinned to the compose-internal host, because the code default
points at localhost (wrong inside the network). environment wins over
env_file, so this is a safety net if .env ever omits it.
Trade-off (accepted, see comment): env_file also injects infra secrets
(POSTGRES_*, MINIO_*, CLOUDFLARE_TUNNEL_TOKEN) into the app process env; the app
ignores unknown vars (pydantic extra="ignore").
Verified on prod: DATABASE_URL resolves to postgres:5432, RUN_MIGRATIONS=1 and
OWNER_EMAIL intact, COOKIE_SECURE=true (no posture change), health 200, trees
200. The earlier explicit AI/SMTP/OWNER passthrough is now subsumed by this.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Paul <justin@jpaul.me>
This commit is contained in:
+27
-48
@@ -51,8 +51,13 @@ services:
|
||||
command: ["uv", "run", "--no-dev", "alembic", "upgrade", "head"]
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "true"
|
||||
# All app config comes from .env (twelve-factor) — no per-setting allow-list
|
||||
# to maintain. The `environment:` block below only pins values that must NOT
|
||||
# come from .env. See the backend service for the full rationale.
|
||||
env_file:
|
||||
- path: .env
|
||||
required: false
|
||||
environment:
|
||||
APP_ENV: ${APP_ENV:-development}
|
||||
DATABASE_URL: ${DATABASE_URL:-postgresql+asyncpg://provenance:provenance@postgres:5432/provenance}
|
||||
depends_on:
|
||||
postgres:
|
||||
@@ -63,50 +68,24 @@ services:
|
||||
image: git.jpaul.io/justin/provenance-backend:${IMAGE_TAG:-test-main}
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "true"
|
||||
# Twelve-factor: ALL application settings come straight from .env — owner,
|
||||
# AI providers, mailer/SMTP, S3, sessions, everything in app/core/config.py.
|
||||
# No per-setting allow-list to maintain, so a new setting in .env (and
|
||||
# .env.example) reaches the app with no compose edit. The `environment:`
|
||||
# block below is only for values that must NOT come from .env:
|
||||
# - RUN_MIGRATIONS: backend-only flag, not an app setting.
|
||||
# - DATABASE_URL: pinned to the compose-internal host as a safety net —
|
||||
# the code default points at localhost, which is wrong inside the
|
||||
# network. (.env normally sets it; this guards against it being absent.)
|
||||
# `environment:` wins over `env_file`, so these always take effect.
|
||||
# Trade-off (accepted): env_file also exposes infra secrets (POSTGRES_*,
|
||||
# MINIO_*, CLOUDFLARE_TUNNEL_TOKEN) to the app process; the app ignores them.
|
||||
env_file:
|
||||
- path: .env
|
||||
required: false
|
||||
environment:
|
||||
APP_ENV: ${APP_ENV:-development}
|
||||
# Self-migrate on start so a Watchtower in-place image swap applies any new
|
||||
# migrations (idempotent). The one-shot `migrate` service covers the same
|
||||
# for `compose up`; the depends_on below serializes them so they never run
|
||||
# alembic concurrently.
|
||||
RUN_MIGRATIONS: "1"
|
||||
DATABASE_URL: ${DATABASE_URL:-postgresql+asyncpg://provenance:provenance@postgres:5432/provenance}
|
||||
# Instance owner/operator — the account(s) with instance-admin rights.
|
||||
OWNER_EMAIL: ${OWNER_EMAIL:-}
|
||||
S3_ENDPOINT_URL: ${S3_ENDPOINT_URL:-http://minio:9000}
|
||||
S3_BUCKET: ${S3_BUCKET:-provenance}
|
||||
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-provenance}
|
||||
S3_SECRET_KEY: ${S3_SECRET_KEY:-change-me-too}
|
||||
S3_REGION: ${S3_REGION:-us-east-1}
|
||||
# Email / mailer — verification + password-reset links. APP_BASE_URL is the
|
||||
# base for those links; MAILER=smtp activates the SMTP_* settings.
|
||||
APP_BASE_URL: ${APP_BASE_URL:-http://localhost}
|
||||
REQUIRE_EMAIL_VERIFICATION: ${REQUIRE_EMAIL_VERIFICATION:-false}
|
||||
MAILER: ${MAILER:-console}
|
||||
SMTP_HOST: ${SMTP_HOST:-}
|
||||
SMTP_PORT: ${SMTP_PORT:-587}
|
||||
SMTP_USERNAME: ${SMTP_USERNAME:-}
|
||||
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
|
||||
SMTP_FROM: ${SMTP_FROM:-Provenance <no-reply@provenance.local>}
|
||||
# Model providers (AI assistant + embeddings). Each activates when its key
|
||||
# is set; DEFAULT_*_PROVIDER picks the default. 'null' keeps AI off.
|
||||
DEFAULT_LLM_PROVIDER: ${DEFAULT_LLM_PROVIDER:-null}
|
||||
DEFAULT_EMBEDDING_PROVIDER: ${DEFAULT_EMBEDDING_PROVIDER:-null}
|
||||
LLM_MAX_TOKENS: ${LLM_MAX_TOKENS:-4096}
|
||||
EMBEDDING_DIMENSIONS: ${EMBEDDING_DIMENSIONS:-1536}
|
||||
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
||||
ANTHROPIC_MODEL: ${ANTHROPIC_MODEL:-claude-opus-4-8}
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
||||
OPENAI_BASE_URL: ${OPENAI_BASE_URL:-https://api.openai.com/v1}
|
||||
OPENAI_MODEL: ${OPENAI_MODEL:-gpt-4o}
|
||||
OPENAI_EMBEDDING_MODEL: ${OPENAI_EMBEDDING_MODEL:-text-embedding-3-small}
|
||||
XAI_API_KEY: ${XAI_API_KEY:-}
|
||||
XAI_BASE_URL: ${XAI_BASE_URL:-https://api.x.ai/v1}
|
||||
XAI_MODEL: ${XAI_MODEL:-grok-2-latest}
|
||||
OLLAMA_ENABLED: ${OLLAMA_ENABLED:-false}
|
||||
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://localhost:11434/v1}
|
||||
OLLAMA_MODEL: ${OLLAMA_MODEL:-llama3.1}
|
||||
OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-nomic-embed-text}
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
@@ -133,14 +112,14 @@ services:
|
||||
command: ["uv", "run", "--no-dev", "python", "-m", "app.worker"]
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "true"
|
||||
# Same .env-driven config as the backend (see its comment). The worker reads
|
||||
# the model-provider settings too, so the upcoming embedding/matching jobs
|
||||
# are configured the moment they land — no compose change needed.
|
||||
env_file:
|
||||
- path: .env
|
||||
required: false
|
||||
environment:
|
||||
APP_ENV: ${APP_ENV:-development}
|
||||
DATABASE_URL: ${DATABASE_URL:-postgresql+asyncpg://provenance:provenance@postgres:5432/provenance}
|
||||
S3_ENDPOINT_URL: ${S3_ENDPOINT_URL:-http://minio:9000}
|
||||
S3_BUCKET: ${S3_BUCKET:-provenance}
|
||||
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-provenance}
|
||||
S3_SECRET_KEY: ${S3_SECRET_KEY:-change-me-too}
|
||||
S3_REGION: ${S3_REGION:-us-east-1}
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
Reference in New Issue
Block a user