# nginx config for the Bonsai-Image HF Space. # - :7860 is the only public port (HF exposes it). # - / and /api/* go to the Next.js frontend on :3000. # - /generate, /backends, /docs go to one (or many) uvicorn backends via # the upstream block, which entrypoint.sh builds from `nvidia-smi -L`. # At N=1 it's just one server line; at N>1 we add least_conn. # - /dash- is the metrics dashboard, basic-auth gated. # # Run as: nginx -c /home/user/app/space/nginx.conf -p /home/user/app/ worker_processes 1; daemon off; pid /tmp/nginx.pid; error_log /dev/stderr warn; events { # Studio /api/generate now does a double nginx pass (browser → nginx → # Next.js → nginx → backend upstream), so each in-flight generation holds # ~2 connections. 1024 leaves ample headroom for launch-load concurrency. worker_connections 1024; } http { default_type application/octet-stream; sendfile on; keepalive_timeout 65; # nginx's stock /var/log/... isn't writable by uid 1000 on the HF image, # so redirect everything into /tmp where we have write access. client_body_temp_path /tmp/nginx-body; proxy_temp_path /tmp/nginx-proxy; fastcgi_temp_path /tmp/nginx-fastcgi; uwsgi_temp_path /tmp/nginx-uwsgi; scgi_temp_path /tmp/nginx-scgi; access_log /tmp/nginx-access.log; # Built at boot by entrypoint.sh from `nvidia-smi -L` — one server line # per GPU. Today: one server at :8000. include /tmp/nginx-upstream.conf; server { listen 7860 default_server; client_max_body_size 16M; # ── frontend ──────────────────────────────────────────────────────── location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; # APPEND $remote_addr to existing X-Forwarded-For (the chain HF's # edge proxy already set with the real visitor IP). Using # $remote_addr alone would overwrite that with the edge proxy's # IP — same for all visitors — collapsing every user to one hash # in the dashboard's unique-user counter. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Generations can run several seconds; Next.js streams the # response back so don't time the connection out. proxy_read_timeout 600s; } # ── backend API surface (called by Next.js api/generate route + curl) ─ location ~ ^/(generate|backends|docs|openapi\.json)$ { proxy_pass http://bonsai_workers; proxy_http_version 1.1; proxy_set_header Host $host; # APPEND $remote_addr to existing X-Forwarded-For (the chain HF's # edge proxy already set with the real visitor IP). Using # $remote_addr alone would overwrite that with the edge proxy's # IP — same for all visitors — collapsing every user to one hash # in the dashboard's unique-user counter. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_read_timeout 600s; proxy_buffering off; # stream PNG bytes back immediately } # ── dashboard ─────────────────────────────────────────────────────── # Obfuscated path + basic auth. Path suffix is in source (visible to # anyone with repo read access); auth is the actual gate. # Trailing-slash exact-match keeps /dash-... from leaking into other # locations. location = /dash-10a08e9c1ee4 { auth_basic "Bonsai Dashboard"; auth_basic_user_file /tmp/.htpasswd; alias /home/user/app/space/dashboard.html; default_type text/html; add_header Cache-Control "no-store" always; } location = /dash-10a08e9c1ee4/analytics.json { auth_basic "Bonsai Dashboard"; auth_basic_user_file /tmp/.htpasswd; alias /tmp/analytics.json; default_type application/json; add_header Cache-Control "no-store" always; } location = /dash-10a08e9c1ee4/gpu-stats.json { auth_basic "Bonsai Dashboard"; auth_basic_user_file /tmp/.htpasswd; alias /tmp/gpu-stats.json; default_type application/json; add_header Cache-Control "no-store" always; } # Catchall under the dashboard prefix → 404 (don't reveal what else # might exist there). location ~ ^/dash- { return 404; } # /metrics on the backend is loopback-only; nginx doesn't forward it. # (metrics_pusher.py scrapes it directly at 127.0.0.1:8000/metrics.) location = /metrics { return 404; } } }