docs(env): expand .env.example into full compose interpolation template
All checks were successful
CVE Scan & Docker Build / security-scan (push) Successful in 51s
CVE Scan & Docker Build / build-and-push (push) Successful in 3m3s

Replace the minimal placeholder .env.example with a comprehensive template
documenting every variable consumed by docker-compose.yaml, organized by
service (Django core, HTTP, Postgres, Neo4j, Memcached, S3/MinIO, Daedalus,
Celery/RabbitMQ, etc.). Clarifies that this file is rendered from an Ansible
Jinja2 template with vaulted secrets in production, and distinguishes it
from the in-tree mnemosyne/.env used for bare-Python development.
This commit is contained in:
2026-05-04 07:04:28 -04:00
parent d84f0e548b
commit 003f958f7b
4 changed files with 623 additions and 68 deletions

View File

@@ -1,13 +1,133 @@
# =============================================================================
# Mnemosyne — Docker Compose environment
# Mnemosyne — docker compose interpolation template
# =============================================================================
# This file documents variables consumed by docker-compose.yaml itself
# (image tags, port overrides, etc.). It is NOT the application config.
# This file is consumed by `docker compose` as the source for `${VAR}`
# interpolations in docker-compose.yaml. In production it is generated from
# a Jinja2 template by an Ansible role, with secrets pulled from the Ansible
# vault — do not commit a populated copy.
#
# Application config lives in mnemosyne/.env — copy mnemosyne/.env\ example
# to mnemosyne/.env and fill in your values before running `docker compose up`.
# Copy to `.env` (at the repo root, NOT inside `mnemosyne/`) and fill in the
# blanks before running `docker compose up -d`. The in-tree `mnemosyne/.env`
# file (used by bare-Python development on caliban) is a separate concern
# and is NOT read by the compose stack.
#
# This file has no required variables for a default deployment: the compose
# file uses a fixed image tag and port. Add overrides here if you parameterise
# those in docker-compose.yaml (e.g. MNEMOSYNE_IMAGE, MNEMOSYNE_PORT).
# Every variable below is referenced by at least one service in
# docker-compose.yaml. Per-service scoping (which container sees which var)
# is defined by the `environment:` blocks in that file; this template just
# provides the values.
# =============================================================================
# --- Django core ------------------------------------------------------------
# Consumed by: app, mcp, worker
SECRET_KEY=change-me-to-a-real-secret-key
DEBUG=False
TIME_ZONE=UTC
LANGUAGE_CODE=en-us
# --- HTTP surface -----------------------------------------------------------
# Consumed by: app (CSRF_TRUSTED_ORIGINS: app only; ALLOWED_HOSTS: app + mcp)
# Include every hostname HAProxy routes to this stack, plus localhost for the
# inter-container health probes.
ALLOWED_HOSTS=localhost,127.0.0.1,mnemosyne.ouranos.helu.ca
CSRF_TRUSTED_ORIGINS=https://mnemosyne.ouranos.helu.ca
# --- PostgreSQL (Portia) ----------------------------------------------------
# Consumed by: app, mcp, worker
APP_DB_NAME=mnemosyne
APP_DB_USER=mnemosyne
APP_DB_PASSWORD=change-me
DB_HOST=portia.incus
DB_PORT=5432
# --- Neo4j (Umbriel — dedicated Mnemosyne instance) -------------------------
# Consumed by: app, mcp, worker
# Umbriel MUST be dedicated to Mnemosyne; do not share with Spelunker or any
# other graph workload. See README.md for the full rationale.
NEOMODEL_NEO4J_BOLT_URL=bolt://neo4j:change-me@umbriel.incus:7687
# --- Memcached --------------------------------------------------------------
# Consumed by: app, mcp, worker
# Must resolve from inside containers — 127.0.0.1 will NOT work.
KVDB_LOCATION=oberon.incus:11211
KVDB_PREFIX=mnemosyne
# --- S3 / MinIO (Nyx) — Mnemosyne's own bucket ------------------------------
# Consumed by: app, mcp, worker
# Mnemosyne writes chunk text and item files here. Set USE_LOCAL_STORAGE=False
# in production so the S3Boto3Storage backend is used instead of the local
# FileSystemStorage fallback.
USE_LOCAL_STORAGE=False
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_STORAGE_BUCKET_NAME=mnemosyne-content
AWS_S3_ENDPOINT_URL=https://nyx.helu.ca:8555
AWS_S3_USE_SSL=True
AWS_S3_VERIFY=False
AWS_S3_REGION_NAME=us-east-1
# --- Daedalus S3 (cross-bucket reads for ingest) ----------------------------
# Consumed by: worker only
# Mnemosyne's ingest Celery task reads files from Daedalus's bucket and
# copies them into AWS_STORAGE_BUCKET_NAME for processing. These creds
# should be scoped read-only to the Daedalus bucket in your secret manager.
DAEDALUS_S3_ENDPOINT_URL=https://nyx.helu.ca:8555
DAEDALUS_S3_ACCESS_KEY_ID=
DAEDALUS_S3_SECRET_ACCESS_KEY=
DAEDALUS_S3_BUCKET_NAME=daedalus
DAEDALUS_S3_REGION_NAME=us-east-1
DAEDALUS_S3_USE_SSL=True
DAEDALUS_S3_VERIFY=True
# --- Celery / RabbitMQ (Oberon) ---------------------------------------------
# Consumed by: app (producer), worker (consumer). NOT mcp.
# Remember to percent-encode any password characters that have meaning in a
# URL (`@ : / # % + ? & =` and space). Kombu's AMQP URL parser is strict —
# an unencoded password is the most common cause of PLAIN 403 failures when
# the bare-Python client happens to connect fine.
CELERY_BROKER_URL=amqp://mnemosyne:change-me@oberon.incus:5672/mnemosyne
CELERY_RESULT_BACKEND=rpc://
CELERY_TASK_ALWAYS_EAGER=False
# --- Worker tuning ---------------------------------------------------------
# Consumed by: worker only (read by entrypoint.sh → `celery -A mnemosyne worker`)
# Override per host if you want to dedicate a worker to a single queue.
CELERY_QUEUES=celery,embedding,batch
CELERY_CONCURRENCY=2
# --- MCP server -------------------------------------------------------------
# Consumed by: mcp only
MCP_REQUIRE_AUTH=True
# --- LLM API encryption -----------------------------------------------------
# Consumed by: app (admin pages), worker (ingest vision pass). NOT mcp.
# Generate once per deployment, store in the vault, never rotate without
# re-encrypting every stored provider key first.
# python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
LLM_API_SECRETS_ENCRYPTION_KEY=
# --- Email (smtp4dev on Oberon) --------------------------------------------
# Consumed by: app only
EMAIL_HOST=oberon.incus
EMAIL_PORT=22025
EMAIL_USE_TLS=False
# --- Embedding pipeline -----------------------------------------------------
# Consumed by: worker only
EMBEDDING_BATCH_SIZE=8
EMBEDDING_TIMEOUT=120
# --- Search & re-ranker -----------------------------------------------------
# Consumed by: app, mcp. Not worker (workers never serve queries).
SEARCH_VECTOR_TOP_K=50
SEARCH_FULLTEXT_TOP_K=30
SEARCH_GRAPH_MAX_DEPTH=2
SEARCH_RRF_K=60
SEARCH_DEFAULT_LIMIT=20
RERANKER_MAX_CANDIDATES=32
RERANKER_TIMEOUT=30
# --- Logging ----------------------------------------------------------------
# Consumed by: app, mcp, worker (each picks the levels it cares about)
LOGGING_LEVEL=INFO
CELERY_LOGGING_LEVEL=INFO
DJANGO_LOGGING_LEVEL=WARNING