# ============================================================================= # 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 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 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8001/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: - "23181: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: