Skip to content

feat(docker): add container support#422

Open
hwcopeland wants to merge 2 commits intokoala73:mainfrom
hwcopeland:main
Open

feat(docker): add container support#422
hwcopeland wants to merge 2 commits intokoala73:mainfrom
hwcopeland:main

Conversation

@hwcopeland
Copy link

  • 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

Type of change

New feature

- 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>
Copilot AI review requested due to automatic review settings February 26, 2026 15:48
@vercel
Copy link

vercel bot commented Feb 26, 2026

@hwcopeland is attempting to deploy a commit to the Elie Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
proxy_ssl_server_name on;
proxy_ssl_server_name on;
proxy_ssl_verify on;
proxy_connect_timeout 10s;

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +6
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;

Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
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 "";
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
proxy_set_header Connection "";
proxy_set_header Connection "";
proxy_connect_timeout 10s;
proxy_send_timeout 30s;

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +49
# 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
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +23
# 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

Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
# 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

Copilot uses AI. Check for mistakes.
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;
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Suggested change
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;

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +99
# 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=""
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.

# Gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Suggested change
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;

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +19
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 &
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
API readiness probe

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants