# J621 public entry point (the "nginx" service in the compose files). # # Routes: # /api, /admin, /static, /health -> backend:8000 (Django / gunicorn) # everything else -> frontend:80 (SPA static files) # # Both upstreams are variables so the same file works in all three compose # variants: with no frontend on the network "/" answers a small JSON hint, # with no backend the API paths answer 502 until one is configured via /setup. # Nothing is published to the host; the Tailscale sidecar shares this # container's network namespace and funnels to 127.0.0.1:80. resolver 127.0.0.11 valid=10s ipv6=off; map $http_x_forwarded_proto $j621_forwarded_proto { default $http_x_forwarded_proto; "" $scheme; } server { listen 80; server_name _; location = /nginx-health { access_log off; return 200 "ok\n"; } location ~ ^/(api|admin|static|health)(/|$) { set $j621_backend http://backend:8000; proxy_pass $j621_backend$request_uri; 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-Host $host; proxy_set_header X-Forwarded-Proto $j621_forwarded_proto; # Uploads and client-processed files can be large; stream them. client_max_body_size 2048m; proxy_request_buffering off; proxy_read_timeout 600s; proxy_send_timeout 600s; } location / { set $j621_frontend http://frontend:80; proxy_pass $j621_frontend$request_uri; 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-Host $host; proxy_set_header X-Forwarded-Proto $j621_forwarded_proto; # Backend-only deployments have no frontend: say so instead of 502. error_page 502 503 504 = @j621_no_frontend; } location @j621_no_frontend { default_type application/json; return 200 '{"detail":"J621 API - no frontend is attached to this deployment."}'; } }