# Bach to Basics: nginx config for the static frontend container. # # Serves the built React app and proxies /api/* to the backend service. # Backend hostname "backend" is resolved via Docker Compose's network. server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; # ── Security headers ──────────────────────────────────────────────────── # These are set here (server block) so they apply to all responses that # don't have their own add_header directives. # NOTE: nginx does NOT inherit server-level add_header into location blocks # that define their own add_header (e.g. /assets/). Those blocks repeat # the security headers explicitly to avoid the inheritance pitfall. # /api/ is intentionally excluded - the FastAPI backend sets its own. add_header X-Frame-Options "DENY" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; # SPA fallback: any route that isn't a real file goes to index.html location / { try_files $uri $uri/ /index.html; } # PWA 시작 문서와 서비스 워커는 매번 최신 상태를 확인한다. 해시가 붙은 # 정적 자산만 장기 캐시하므로 배포 뒤 예전 화면이 계속 남지 않는다. location = /index.html { expires -1; add_header Cache-Control "no-store, no-cache, must-revalidate" always; } location ~ ^/(sw\.js|registerSW\.js|manifest\.webmanifest)$ { expires -1; add_header Cache-Control "no-store, no-cache, must-revalidate" always; } # AlphaTab의 Worker와 AudioWorklet은 ES 모듈이다. nginx 기본 MIME 표에 # mjs가 없으면 브라우저가 nosniff 정책에 따라 실행을 차단한다. location ~* \.mjs$ { default_type application/javascript; expires 1y; add_header Cache-Control "public, immutable" always; add_header X-Frame-Options "DENY" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; } # API proxy, strips the /api prefix on the way through location /api/ { proxy_pass http://backend:8000/; 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 X-API-Key "__BACKEND_API_KEY__"; # Sample / soundfont uploads can be large client_max_body_size 60M; # OMR + transcription can take a while proxy_read_timeout 300s; } # Cache static assets aggressively (Vite hashes filenames). # Repeats security headers because this block defines its own add_header # (nginx would otherwise drop the server-level ones here). location /assets/ { expires 1y; add_header Cache-Control "public, immutable" always; add_header X-Frame-Options "DENY" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; } }