# nginx config for the static teaching site, baked into the image. # Base: nginxinc/nginx-unprivileged (uid 101, listens on 8080). # Read-only rootfs in k8s: /tmp and /var/cache/nginx are emptyDir mounts. server { listen 8080; server_name _; root /usr/share/nginx/html; index index.html; server_tokens off; # Behind the Cloudflare Tunnel on :443 while nginx listens on :8080 — without these, # nginx emits ABSOLUTE redirects to http://host:8080/... and breaks every link. absolute_redirect off; port_in_redirect off; include /etc/nginx/security-headers.conf; # ---- Caching ------------------------------------------------------------- # Scene images are content-hashed at build time → safe to cache hard. location /assets/ { include /etc/nginx/security-headers.conf; expires 1y; add_header Cache-Control "public, immutable" always; } # Scripts are NOT fingerprinted yet — revalidate so a deploy actually propagates. location ~* \.js$ { include /etc/nginx/security-headers.conf; add_header Cache-Control "no-cache" always; } location ~* \.html$ { include /etc/nginx/security-headers.conf; add_header Cache-Control "no-cache" always; } # Probe endpoint - no file IO, no cache headers, invisible to the CDN. location = /healthz { access_log off; return 200 "ok"; } # ---- Routing ------------------------------------------------------------- location / { try_files $uri $uri/ $uri.html =404; } # ---- Compression --------------------------------------------------------- # WebP/PNG are already compressed — gzipping them burns CPU for nothing. gzip on; gzip_comp_level 6; gzip_min_length 1024; gzip_types text/plain text/css application/javascript application/json image/svg+xml; gzip_vary on; }