Spaces:
Paused
Paused
| # Reverse proxy for Docker Compose (HTTP only — add SSL at host/load balancer in real prod) | |
| # Use Docker DNS with short TTL so gateway picks up new container IPs after rebuild. | |
| # SPA dashboard fires many parallel /api calls on load — keep auth/ML strict, API generous. | |
| limit_req_zone $binary_remote_addr zone=docker_auth:10m rate=10r/m; | |
| limit_req_zone $binary_remote_addr zone=docker_api:10m rate=600r/m; | |
| limit_req_zone $binary_remote_addr zone=docker_ml:10m rate=3r/m; | |
| server { | |
| listen 80; | |
| client_max_body_size 16M; | |
| resolver 127.0.0.11 valid=10s ipv6=off; | |
| resolver_timeout 5s; | |
| add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; media-src 'self' data: https: blob:; connect-src 'self' https://accounts.google.com https://www.youtube.com https://www.googleapis.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://api.fontshare.com; font-src 'self' https://fonts.gstatic.com https://cdn.fontshare.com; script-src 'self' https://www.youtube.com https://s.ytimg.com; frame-src 'self' https://www.youtube.com https://www.youtube-nocookie.com; frame-ancestors 'none'"; | |
| location /api/ { | |
| limit_req zone=docker_api burst=120 nodelay; | |
| set $api_backend api:8000; | |
| proxy_pass http://$api_backend; | |
| 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 ~ ^/api/(auth/login|auth/register|auth/forgot-password)$ { | |
| limit_req zone=docker_auth burst=5 nodelay; | |
| set $api_backend api:8000; | |
| proxy_pass http://$api_backend; | |
| 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; | |
| } | |
| location ~ ^/api/(speaking/evaluate|shadowing/pronunciation/check|vocabulary/pronunciation/check|pronunciation/word|conversation/turn/voice)$ { | |
| limit_req zone=docker_ml burst=2 nodelay; | |
| set $api_backend api:8000; | |
| proxy_pass http://$api_backend; | |
| 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 /uploads/ { | |
| set $api_backend api:8000; | |
| proxy_pass http://$api_backend$uri$is_args$args; | |
| proxy_set_header Host $host; | |
| add_header Cache-Control "public, max-age=86400"; | |
| } | |
| location /audio/ { | |
| set $api_backend api:8000; | |
| proxy_pass http://$api_backend$uri$is_args$args; | |
| } | |
| location /images/ { | |
| set $api_backend api:8000; | |
| proxy_pass http://$api_backend$uri$is_args$args; | |
| } | |
| # Legacy thumbnail URLs (/api/images/{uuid}) → same handler as /images/ | |
| location /api/images/ { | |
| set $api_backend api:8000; | |
| rewrite ^/api/images/(.*)$ /images/$1 break; | |
| proxy_pass http://$api_backend; | |
| proxy_set_header Host $host; | |
| } | |
| location / { | |
| set $frontend_backend frontend:80; | |
| proxy_pass http://$frontend_backend$uri$is_args$args; | |
| proxy_set_header Host $host; | |
| } | |
| gzip on; | |
| gzip_types text/plain application/json application/javascript text/css; | |
| } | |