Reorganize Docker Compose services: the Django/gunicorn container is now `app` and nginx is `web`, better reflecting their roles. Add a dedicated gunicorn configuration and install curl in the runtime image for health checks. Update documentation to reflect: - Neo4j migration from ariel.incus to a dedicated umbriel.incus instance - Rationale for requiring a dedicated Neo4j instance (single-tenancy assumptions, label/index isolation, schema ownership) - New service naming in compose commands and log tailing examples
117 lines
4.0 KiB
YAML
117 lines
4.0 KiB
YAML
# =============================================================================
|
|
# Mnemosyne — production deployment
|
|
# =============================================================================
|
|
# Four services, all from the same image:
|
|
# app — Django REST API + admin (gunicorn, port 8000)
|
|
# mcp — FastMCP server (uvicorn, port 22091)
|
|
# worker — Celery worker (embedding/ingest/batch queues)
|
|
# web — reverse proxy, public port 23090 (nginx)
|
|
#
|
|
# External services (NOT spun up here): Postgres on Portia, Neo4j on Umbriel,
|
|
# RabbitMQ on Oberon, S3/MinIO on Nyx, Memcached on its own host, embedder
|
|
# and reranker on Nyx, smtp4dev on Oberon. All reached over the internal
|
|
# 10.10.0.0/24 network.
|
|
#
|
|
# Run:
|
|
# docker compose up -d
|
|
# docker compose run --rm app migrate # one-shot DB migrate
|
|
# docker compose run --rm app setup # Neo4j indexes + library types
|
|
# =============================================================================
|
|
|
|
services:
|
|
# ── Static-file seeder: copies /app/staticfiles into the shared volume on
|
|
# every `up`. Runs once and exits. Without this, the named volume is only
|
|
# seeded the first time it's empty, so static updates between deploys
|
|
# would not propagate to nginx.
|
|
static-init:
|
|
image: git.helu.ca/r/mnemosyne:latest
|
|
command: ["sh", "-c", "cp -a /app/staticfiles/. /shared-static/"]
|
|
user: "0:0"
|
|
volumes:
|
|
- mnemosyne-static:/shared-static
|
|
restart: "no"
|
|
|
|
# ── App: Django REST API + admin ──────────────────────────────────────────
|
|
app:
|
|
image: git.helu.ca/r/mnemosyne:latest
|
|
command: ["web"]
|
|
env_file: mnemosyne/.env
|
|
restart: unless-stopped
|
|
depends_on:
|
|
static-init:
|
|
condition: service_completed_successfully
|
|
volumes:
|
|
- mnemosyne-media:/app/media
|
|
expose:
|
|
- "8000"
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-f", "http://localhost:8000/live/"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
# ── MCP server: FastMCP Streamable HTTP at /mcp/ ───────────────────────────
|
|
mcp:
|
|
image: git.helu.ca/r/mnemosyne:latest
|
|
command: ["mcp"]
|
|
env_file: mnemosyne/.env
|
|
restart: unless-stopped
|
|
volumes:
|
|
- mnemosyne-media:/app/media
|
|
expose:
|
|
- "22091"
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-f", "http://localhost:22091/mcp/health"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
# ── Celery worker: embedding + ingest + batch queues ───────────────────────
|
|
worker:
|
|
image: git.helu.ca/r/mnemosyne:latest
|
|
command: ["worker"]
|
|
env_file: mnemosyne/.env
|
|
restart: unless-stopped
|
|
depends_on:
|
|
app:
|
|
condition: service_healthy
|
|
volumes:
|
|
- mnemosyne-media:/app/media
|
|
healthcheck:
|
|
test: ["CMD", "celery", "-A", "mnemosyne", "inspect", "ping", "-d", "celery@$$HOSTNAME"]
|
|
interval: 60s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 60s
|
|
|
|
# ── Web: nginx reverse proxy, public port 23090 ───────────────────────────
|
|
web:
|
|
image: nginx:alpine
|
|
restart: unless-stopped
|
|
depends_on:
|
|
app:
|
|
condition: service_healthy
|
|
mcp:
|
|
condition: service_healthy
|
|
ports:
|
|
- "23090:80"
|
|
volumes:
|
|
- ./nginx/mnemosyne.conf:/etc/nginx/conf.d/default.conf:ro
|
|
- mnemosyne-static:/var/www/static:ro
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-f", "http://localhost/live/"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
|
|
volumes:
|
|
# Static files baked into the image at /app/staticfiles. The app service
|
|
# mounts this volume, populating it on first start; nginx reads from it.
|
|
mnemosyne-static:
|
|
# Local FileSystemStorage fallback. Production uses USE_LOCAL_STORAGE=False
|
|
# so this is mostly empty — kept for parity with dev and for any path
|
|
# that writes to MEDIA_ROOT directly.
|
|
mnemosyne-media:
|