|
| 1 | +# Nginx Configuration |
| 2 | + |
| 3 | +The frontend uses Nginx as a reverse proxy and static file server. This document explains the configuration in |
| 4 | +`frontend/nginx.conf`. |
| 5 | + |
| 6 | +## Architecture |
| 7 | + |
| 8 | +```mermaid |
| 9 | +flowchart LR |
| 10 | + Browser --> Nginx |
| 11 | + Nginx -->|/api/*| Backend[Backend :443] |
| 12 | + Nginx -->|static files| Static[/usr/share/nginx/html] |
| 13 | +``` |
| 14 | + |
| 15 | +Nginx serves two purposes: |
| 16 | + |
| 17 | +1. **Static file server** for the Svelte frontend build |
| 18 | +2. **Reverse proxy** for API requests to the backend |
| 19 | + |
| 20 | +## Configuration Breakdown |
| 21 | + |
| 22 | +### Server Block |
| 23 | + |
| 24 | +```nginx |
| 25 | +server { |
| 26 | + listen 5001; |
| 27 | + server_name _; |
| 28 | + root /usr/share/nginx/html; |
| 29 | + index index.html; |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +| Directive | Purpose | |
| 34 | +|-----------------|--------------------------------------------------| |
| 35 | +| `listen 5001` | Internal container port (mapped via K8s Service) | |
| 36 | +| `server_name _` | Catch-all server name | |
| 37 | +| `root` | Static files from Svelte build | |
| 38 | + |
| 39 | +### Compression |
| 40 | + |
| 41 | +```nginx |
| 42 | +gzip on; |
| 43 | +gzip_vary on; |
| 44 | +gzip_min_length 1024; |
| 45 | +gzip_types text/plain text/css text/xml text/javascript |
| 46 | + application/javascript application/xml+rss |
| 47 | + application/json application/x-font-ttf |
| 48 | + font/opentype image/svg+xml image/x-icon; |
| 49 | +``` |
| 50 | + |
| 51 | +Gzip compression reduces bandwidth for text-based assets. Binary files (images, fonts) are excluded as they're already |
| 52 | +compressed. |
| 53 | + |
| 54 | +### API Proxy |
| 55 | + |
| 56 | +```nginx |
| 57 | +location /api/ { |
| 58 | + proxy_pass https://backend:443; |
| 59 | + proxy_ssl_verify off; |
| 60 | + proxy_set_header Host $host; |
| 61 | + proxy_set_header X-Real-IP $remote_addr; |
| 62 | + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| 63 | + proxy_set_header X-Forwarded-Proto $scheme; |
| 64 | + proxy_pass_request_headers on; |
| 65 | + proxy_set_header Cookie $http_cookie; |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +| Directive | Purpose | |
| 70 | +|------------------------|--------------------------------------------------| |
| 71 | +| `proxy_pass` | Forward to backend service over HTTPS | |
| 72 | +| `proxy_ssl_verify off` | Skip certificate verification (internal traffic) | |
| 73 | +| `X-Real-IP` | Pass client IP to backend for rate limiting | |
| 74 | +| `X-Forwarded-Proto` | Preserve original protocol for redirect URLs | |
| 75 | +| `Cookie` | Forward authentication cookies | |
| 76 | + |
| 77 | +### SSE (Server-Sent Events) |
| 78 | + |
| 79 | +SSE endpoints require special handling to prevent buffering: |
| 80 | + |
| 81 | +```nginx |
| 82 | +location ~ ^/api/v1/events/ { |
| 83 | + proxy_pass https://backend:443; |
| 84 | + proxy_ssl_verify off; |
| 85 | +
|
| 86 | + # SSE-specific settings |
| 87 | + proxy_set_header Connection ''; |
| 88 | + proxy_http_version 1.1; |
| 89 | + proxy_buffering off; |
| 90 | + proxy_cache off; |
| 91 | + proxy_read_timeout 86400s; |
| 92 | + proxy_send_timeout 86400s; |
| 93 | + proxy_set_header X-Accel-Buffering no; |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +| Directive | Purpose | |
| 98 | +|-----------------------------|---------------------------------------------------| |
| 99 | +| `Connection ''` | Disable connection header for HTTP/1.1 keep-alive | |
| 100 | +| `proxy_http_version 1.1` | Required for chunked transfer encoding | |
| 101 | +| `proxy_buffering off` | Stream responses immediately | |
| 102 | +| `proxy_read_timeout 86400s` | 24-hour timeout for long-lived connections | |
| 103 | +| `X-Accel-Buffering no` | Disable upstream buffering | |
| 104 | + |
| 105 | +Without these settings, SSE events would be buffered and delivered in batches instead of real-time. |
| 106 | + |
| 107 | +### Static Asset Caching |
| 108 | + |
| 109 | +```nginx |
| 110 | +# Immutable assets (hashed filenames) |
| 111 | +location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { |
| 112 | + expires 1y; |
| 113 | + add_header Cache-Control "public, immutable"; |
| 114 | +} |
| 115 | +
|
| 116 | +# Build directory |
| 117 | +location /build/ { |
| 118 | + expires 1y; |
| 119 | + add_header Cache-Control "public, max-age=31536000, immutable"; |
| 120 | +} |
| 121 | +
|
| 122 | +# HTML (never cache) |
| 123 | +location ~* \.html$ { |
| 124 | + expires -1; |
| 125 | + add_header Cache-Control "no-store, no-cache, must-revalidate"; |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +Svelte build outputs hashed filenames (`app.abc123.js`), making them safe to cache indefinitely. HTML files must never |
| 130 | +be cached to ensure users get the latest asset references. |
| 131 | + |
| 132 | +### Security Headers |
| 133 | + |
| 134 | +```nginx |
| 135 | +location / { |
| 136 | + add_header Content-Security-Policy "..."; |
| 137 | + add_header X-Frame-Options "SAMEORIGIN"; |
| 138 | + add_header X-Content-Type-Options "nosniff"; |
| 139 | + add_header Referrer-Policy "strict-origin-when-cross-origin"; |
| 140 | + add_header Permissions-Policy "geolocation=(), microphone=(), camera=()"; |
| 141 | + try_files $uri $uri/ /index.html; |
| 142 | +} |
| 143 | +``` |
| 144 | + |
| 145 | +#### Content Security Policy |
| 146 | + |
| 147 | +```nginx |
| 148 | +Content-Security-Policy " |
| 149 | + default-src 'self'; |
| 150 | + script-src 'self' 'unsafe-inline'; |
| 151 | + style-src 'self' 'unsafe-inline'; |
| 152 | + img-src 'self' data: blob:; |
| 153 | + font-src 'self' data:; |
| 154 | + object-src 'none'; |
| 155 | + base-uri 'self'; |
| 156 | + form-action 'self'; |
| 157 | + frame-ancestors 'none'; |
| 158 | + connect-src 'self'; |
| 159 | +" |
| 160 | +``` |
| 161 | + |
| 162 | +| Directive | Value | Purpose | |
| 163 | +|-------------------|--------------------------|-------------------------------------| |
| 164 | +| `default-src` | `'self'` | Fallback for unspecified directives | |
| 165 | +| `script-src` | `'self' 'unsafe-inline'` | Allow inline scripts (Svelte) | |
| 166 | +| `style-src` | `'self' 'unsafe-inline'` | Allow inline styles (Svelte) | |
| 167 | +| `img-src` | `'self' data: blob:` | Allow data: URLs for SVG icons | |
| 168 | +| `font-src` | `'self' data:` | Allow embedded fonts | |
| 169 | +| `object-src` | `'none'` | Block plugins (Flash, Java) | |
| 170 | +| `frame-ancestors` | `'none'` | Prevent clickjacking | |
| 171 | +| `connect-src` | `'self'` | XHR/fetch/WebSocket same-origin | |
| 172 | + |
| 173 | +The `data:` source is required for the Monaco editor's inline SVG icons. |
| 174 | + |
| 175 | +#### Other Security Headers |
| 176 | + |
| 177 | +| Header | Value | Purpose | |
| 178 | +|--------------------------|-----------------------------------|--------------------------------| |
| 179 | +| `X-Frame-Options` | `SAMEORIGIN` | Legacy clickjacking protection | |
| 180 | +| `X-Content-Type-Options` | `nosniff` | Prevent MIME sniffing | |
| 181 | +| `Referrer-Policy` | `strict-origin-when-cross-origin` | Limit referrer leakage | |
| 182 | +| `Permissions-Policy` | Deny geolocation, mic, camera | Disable unused APIs | |
| 183 | + |
| 184 | +### SPA Routing |
| 185 | + |
| 186 | +```nginx |
| 187 | +try_files $uri $uri/ /index.html; |
| 188 | +``` |
| 189 | + |
| 190 | +This directive enables client-side routing. When a URL like `/editor` is requested directly, Nginx serves `index.html` |
| 191 | +and lets the Svelte router handle the path. |
| 192 | + |
| 193 | +## Deployment |
| 194 | + |
| 195 | +The nginx.conf is copied into the container during build: |
| 196 | + |
| 197 | +```dockerfile |
| 198 | +# frontend/Dockerfile.prod |
| 199 | +FROM nginx:alpine |
| 200 | +COPY --from=builder /app/public /usr/share/nginx/html |
| 201 | +COPY nginx.conf /etc/nginx/conf.d/default.conf |
| 202 | +``` |
| 203 | + |
| 204 | +To apply changes: |
| 205 | + |
| 206 | +```bash |
| 207 | +docker build --no-cache -t integr8scode-frontend:latest \ |
| 208 | + -f frontend/Dockerfile.prod frontend/ |
| 209 | +kubectl rollout restart deployment/frontend -n integr8scode |
| 210 | +``` |
| 211 | + |
| 212 | +## Troubleshooting |
| 213 | + |
| 214 | +### SSE connections dropping |
| 215 | + |
| 216 | +Check `proxy_read_timeout`. Default is 60s which will close idle SSE connections. |
| 217 | + |
| 218 | +### CSP blocking resources |
| 219 | + |
| 220 | +Check browser console for CSP violation reports. Add the blocked source to the appropriate directive. |
| 221 | + |
| 222 | +### 502 Bad Gateway |
| 223 | + |
| 224 | +Backend service is unreachable. Verify: |
| 225 | + |
| 226 | +```bash |
| 227 | +kubectl get svc backend -n integr8scode |
| 228 | +kubectl logs -n integr8scode deployment/frontend |
| 229 | +``` |
| 230 | + |
| 231 | +### Assets not updating |
| 232 | + |
| 233 | +Clear browser cache or add cache-busting query parameters. Verify HTML files have `no-cache` headers. |
0 commit comments