feat(docker): rename web service to app, add nginx as web
All checks were successful
CVE Scan & Docker Build / security-scan (push) Successful in 53s
CVE Scan & Docker Build / build-and-push (push) Successful in 3m0s

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
This commit is contained in:
2026-05-03 19:35:27 -04:00
parent a2c885cf34
commit 7185d326eb
10 changed files with 163 additions and 38 deletions

View File

@@ -2,9 +2,22 @@
# 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 1530 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_web {
server web:8000 max_fails=3 fail_timeout=30s;
upstream mnemosyne_app {
server app:8000 max_fails=3 fail_timeout=30s;
}
upstream mnemosyne_mcp {
@@ -15,16 +28,50 @@ 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_web;
proxy_pass http://mnemosyne_app;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
@@ -34,7 +81,7 @@ server {
}
location /admin/ {
proxy_pass http://mnemosyne_web;
proxy_pass http://mnemosyne_app;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
@@ -59,7 +106,7 @@ server {
}
# Static files baked into the image at /app/staticfiles, mounted into
# this nginx via a named volume populated by the web service.
# this nginx via a named volume populated by the app service.
location /static/ {
alias /var/www/static/;
access_log off;
@@ -67,20 +114,14 @@ server {
}
# Prometheus scrape endpoint — internal networks only.
# Allows: localhost + RFC1918 private ranges (10/8, 172.16/12, 192.168/16).
# 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_web;
access_log off;
}
# Liveness probe — proxies through to the MCP health endpoint.
location = /healthz {
proxy_pass http://mnemosyne_mcp/mcp/health;
proxy_pass http://mnemosyne_app;
access_log off;
}
}