# Mnemosyne nginx — single virtual host that fronts the Django web app # and the FastMCP server. HAProxy on Titania terminates TLS and routes by # hostname; this nginx is plain HTTP on the internal network. # Suppress probe paths from the access log (health checks, Prometheus scrapes). # These fire every 15–30 s and would drown out real traffic in Loki. map $request_uri $loggable { default 1; ~^/live(/|\?|$) 0; ~^/ready(/|\?|$) 0; ~^/metrics(/|\?|$) 0; ~^/healthz(/|\?|$) 0; ~^/health 0; ~^/mcp/health(/|\?|$) 0; ~^/ping(/|\?|$) 0; } # Map of upstreams to give us readable proxy_pass targets and easy retries. upstream mnemosyne_app { server app:8000 max_fails=3 fail_timeout=30s; } upstream mnemosyne_mcp { server mcp:8001 max_fails=3 fail_timeout=30s; } server { listen 80 default_server; server_name _; access_log /var/log/nginx/access.log combined if=$loggable; # Reasonable limits — file uploads to the ingest endpoint can be big, # but the bulk path is S3-direct from Daedalus. 64 MB covers admin # uploads and direct REST POST /library/api/items/upload. client_max_body_size 64m; client_body_timeout 120s; # Liveness probe — always 200 if the Django process is up. # Use the trailing-slash form: /live/ returns 200 directly. # /live (no slash) triggers Django's APPEND_SLASH 301 redirect, which # will cause health check clients that don't follow redirects to fail. location = /live/ { proxy_pass http://mnemosyne_app; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; access_log off; } # Readiness probe — 200 only when PostgreSQL + Memcached are reachable. # Same trailing-slash rule applies. location = /ready/ { proxy_pass http://mnemosyne_app; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; access_log off; } # HAProxy liveness probe — proxies through to the MCP health endpoint. location = /healthz { proxy_pass http://mnemosyne_mcp/mcp/health; access_log off; } # Mnemosyne's REST API — Django REST Framework views + admin. # Under /library/api/* per mnemosyne/urls.py and /admin/* per Django. location /library/ { proxy_pass http://mnemosyne_app; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 300s; } location /admin/ { proxy_pass http://mnemosyne_app; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 300s; } # FastMCP Streamable HTTP at /mcp/ and SSE at /mcp/sse/. # Long-running streams need disabled buffering and a generous timeout. location /mcp/ { proxy_pass http://mnemosyne_mcp; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Connection ""; proxy_buffering off; proxy_cache off; proxy_read_timeout 600s; } # Static files baked into the image at /app/staticfiles, mounted into # this nginx via a named volume populated by the app service. location /static/ { alias /var/www/static/; access_log off; expires 30d; } # Prometheus scrape endpoint — internal networks only. # Allows: loopback + all RFC1918 private ranges. location /metrics { allow 127.0.0.0/8; allow 10.0.0.0/8; allow 172.16.0.0/12; allow 192.168.0.0/16; deny all; proxy_pass http://mnemosyne_app; access_log off; } }