Conversation
- Multi-stage Dockerfile: node:20-alpine builder + nginx/node runner - Builder compiles sebuf RPC handlers (api/[domain]/v1/[rpc].ts → .js) so the local API sidecar can discover and load them at runtime - Runner stage pinned to linux/amd64 to avoid QEMU during npm build - docker/nginx.conf: serves Vite SPA, proxies /api/* to Node sidecar - docker/entrypoint.sh: starts local-api-server.mjs sidecar then nginx - deploy/k8s/worldmonitor.yaml: Deployment + Service + HTTPRoute + Secret template with generic placeholders Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@hwcopeland is attempting to deploy a commit to the Elie Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Pull request overview
This PR adds comprehensive Docker containerization support for World Monitor, enabling deployment beyond the existing Vercel serverless architecture. The implementation provides a multi-stage Docker build that packages the Vite frontend with nginx and runs a Node.js API sidecar (repurposed from the Tauri desktop app) to handle the 60+ API endpoints locally instead of relying on Vercel Edge Functions.
Changes:
- Multi-stage Dockerfile with node:20-alpine builder and nginx+node runner, compiling sebuf RPC handlers for runtime discovery
- nginx configuration serving the SPA, proxying /api/* to the Node.js sidecar on port 3001, and relaying PostHog analytics through /ingest/
- Entrypoint script that starts the local-api-server.mjs sidecar in the background before launching nginx in foreground mode
- Kubernetes manifests (Deployment, Service, HTTPRoute, Secret) providing a production-ready template with configurable API keys and resource limits
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 16 comments.
| File | Description |
|---|---|
| Dockerfile | Multi-stage build: builder compiles sebuf handlers and Vite SPA; runner combines nginx + Node.js sidecar with platform pinning to linux/amd64 |
| docker/nginx.conf | nginx server config with gzip compression, 1-year static asset caching, /api/ proxy to localhost:3001, and PostHog analytics relay |
| docker/entrypoint.sh | Container startup script that launches the API sidecar (local-api-server.mjs) then nginx in daemon-off mode |
| deploy/k8s/worldmonitor.yaml | K8s deployment template with Secret for API keys, Deployment with 512Mi memory limit, Service, and Gateway API HTTPRoute |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| proxy_pass https://us.i.posthog.com/; | ||
| proxy_http_version 1.1; | ||
| proxy_set_header Host us.i.posthog.com; | ||
| proxy_ssl_server_name on; |
There was a problem hiding this comment.
The PostHog analytics proxy at /ingest/ forwards requests to an external HTTPS endpoint (us.i.posthog.com) without configuring SSL verification or timeout settings. Consider adding proxy_ssl_verify on and a reasonable proxy_connect_timeout (e.g., 10s) for production reliability. Also consider whether you want to expose this analytics endpoint in production containers, as it could be used by third parties if the container is publicly accessible.
| proxy_ssl_server_name on; | |
| proxy_ssl_server_name on; | |
| proxy_ssl_verify on; | |
| proxy_connect_timeout 10s; |
| server { | ||
| listen 80; | ||
| server_name _; | ||
| root /usr/share/nginx/html; | ||
| index index.html; | ||
|
|
There was a problem hiding this comment.
The nginx configuration lacks security headers that are important for production web applications. Consider adding headers like X-Content-Type-Options: nosniff, X-Frame-Options: DENY or SAMEORIGIN, and Referrer-Policy: strict-origin-when-cross-origin in a server-level add_header directive. These headers help protect against common web vulnerabilities like clickjacking and MIME type confusion.
| 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 Connection ""; |
There was a problem hiding this comment.
The proxy_read_timeout is set to 30 seconds, but there's no corresponding proxy_connect_timeout or proxy_send_timeout configured. For production reliability, consider adding proxy_connect_timeout 10s and proxy_send_timeout 30s to handle connection establishment and request transmission separately from response reading. This provides better control over different phases of the proxy lifecycle.
| proxy_set_header Connection ""; | |
| proxy_set_header Connection ""; | |
| proxy_connect_timeout 10s; | |
| proxy_send_timeout 30s; |
| # Pin to amd64 so the image runs on x86_64 cluster nodes regardless of | ||
| # the build host platform (avoids QEMU emulation for the npm build stage). | ||
| FROM --platform=linux/amd64 node:20-alpine AS runner |
There was a problem hiding this comment.
The platform is pinned to linux/amd64 in the runner stage with the comment explaining it avoids QEMU during npm build. However, the builder stage (lines 12-44) is not platform-pinned, which means the builder stage will use the host platform. If cross-compilation is needed (e.g., building on Apple Silicon for linux/amd64), consider also pinning the builder stage to --platform=linux/amd64, or clarify that native builds are expected.
| # The API sidecar: prefer the repurposed Tauri local-api-server (primary), | ||
| # then fall back to a compiled server/index.js (legacy), then warn. | ||
| if [ -f /app/sidecar/local-api-server.mjs ]; then | ||
| LOCAL_API_PORT="${API_PORT}" node /app/sidecar/local-api-server.mjs & | ||
| elif [ -f /app/server/index.js ]; then | ||
| node /app/server/index.js & | ||
| elif [ -f /app/server/index.ts ]; then | ||
| # Dev image fallback only — ts-node is not installed in production images | ||
| npx ts-node /app/server/index.ts & | ||
| else | ||
| echo " [warn] No API sidecar found — API calls will fall back to worldmonitor.app (cloud)." | ||
| fi | ||
|
|
There was a problem hiding this comment.
The fallback checks for non-existent files. According to the Dockerfile, only local-api-server.mjs is copied to /app/sidecar/, and there are no /app/server/index.js or /app/server/index.ts files built or copied. The server directory in the repo contains only TypeScript modules that are not compiled to a standalone server entry point. These fallback branches will never execute successfully and should be removed to avoid confusion.
| # The API sidecar: prefer the repurposed Tauri local-api-server (primary), | |
| # then fall back to a compiled server/index.js (legacy), then warn. | |
| if [ -f /app/sidecar/local-api-server.mjs ]; then | |
| LOCAL_API_PORT="${API_PORT}" node /app/sidecar/local-api-server.mjs & | |
| elif [ -f /app/server/index.js ]; then | |
| node /app/server/index.js & | |
| elif [ -f /app/server/index.ts ]; then | |
| # Dev image fallback only — ts-node is not installed in production images | |
| npx ts-node /app/server/index.ts & | |
| else | |
| echo " [warn] No API sidecar found — API calls will fall back to worldmonitor.app (cloud)." | |
| fi | |
| # The API sidecar: use the repurposed Tauri local-api-server when present, | |
| # otherwise warn and allow API calls to fall back to worldmonitor.app (cloud). | |
| if [ -f /app/sidecar/local-api-server.mjs ]; then | |
| LOCAL_API_PORT="${API_PORT}" node /app/sidecar/local-api-server.mjs & | |
| else | |
| echo " [warn] No API sidecar found — API calls will fall back to worldmonitor.app (cloud)." | |
| fi |
| 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; |
There was a problem hiding this comment.
The proxy configuration is missing the X-Forwarded-Proto header, which is important for the Node.js backend to determine whether the original request came over HTTP or HTTPS. This is especially critical when the application is behind a load balancer or ingress controller that terminates TLS. Add the following line after line 25: proxy_set_header X-Forwarded-Proto $scheme;
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |
| proxy_set_header X-Forwarded-Proto $scheme; |
| # Runtime API keys (all optional — dashboard degrades gracefully) | ||
| # See .env.example for full descriptions and registration links. | ||
| ENV GROQ_API_KEY="" | ||
| ENV OPENROUTER_API_KEY="" | ||
| ENV UPSTASH_REDIS_REST_URL="" | ||
| ENV UPSTASH_REDIS_REST_TOKEN="" | ||
| ENV FINNHUB_API_KEY="" | ||
| ENV EIA_API_KEY="" | ||
| ENV FRED_API_KEY="" | ||
| ENV WINGBITS_API_KEY="" | ||
| ENV ACLED_ACCESS_TOKEN="" | ||
| ENV CLOUDFLARE_API_TOKEN="" | ||
| ENV NASA_FIRMS_API_KEY="" | ||
| ENV AISSTREAM_API_KEY="" | ||
| ENV OPENSKY_CLIENT_ID="" | ||
| ENV OPENSKY_CLIENT_SECRET="" | ||
| ENV WS_RELAY_URL="" | ||
| ENV RELAY_SHARED_SECRET="" |
There was a problem hiding this comment.
The UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN environment variables are missing from the Dockerfile's ENV declarations but are present in the Kubernetes Secret. For consistency and documentation purposes, these should be added to the Dockerfile's ENV section (lines 82-99) even if set to empty strings as defaults, since they're used by the API sidecar for cross-user caching (as noted in .env.example). This ensures the runtime contract is clear and complete.
|
|
||
| # Gzip | ||
| gzip on; | ||
| gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml; |
There was a problem hiding this comment.
The gzip_types directive doesn't include application/x-javascript, text/javascript, or application/json+sebuf (if the sebuf RPC format uses a custom content-type). Modern best practice is to include text/javascript explicitly alongside application/javascript. Consider expanding the gzip_types list to: text/plain text/css text/javascript application/json application/javascript application/x-javascript text/xml application/xml image/svg+xml
| gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml; | |
| gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript text/xml application/xml image/svg+xml; |
| elif [ -f /app/server/index.ts ]; then | ||
| # Dev image fallback only — ts-node is not installed in production images | ||
| npx ts-node /app/server/index.ts & |
There was a problem hiding this comment.
Using npx ts-node in the container entrypoint can cause the runtime to download and execute the latest ts-node package from the npm registry whenever it is not installed locally, without version pinning or integrity verification. If this fallback branch is hit (for example when a server/index.ts exists), a compromised or hijacked npm package could execute arbitrary code in the container and exfiltrate environment variables or other secrets. Consider removing this fallback for production images or ensuring ts-node is a pinned, vendored dependency that is invoked from node_modules instead of via a bare npx call.
API readiness probe Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Type of change
New feature