Skip to content

refactor: separate frontend and backend into independent containers#41

Merged
tusharkhatriofficial merged 1 commit intomainfrom
dev
Feb 18, 2026
Merged

refactor: separate frontend and backend into independent containers#41
tusharkhatriofficial merged 1 commit intomainfrom
dev

Conversation

@tusharkhatriofficial
Copy link
Copy Markdown
Owner

Architecture:

  • nginx serves React dashboard on port 80 (exposed as 8080)
  • nginx proxies /api and /ws to Spring Boot backend (internal)
  • Backend has no exposed ports (all traffic goes through nginx)
  • SPA routing handled by nginx try_files (no Spring Boot controller needed)

Changes:

  • Removed SpaForwardingController (nginx handles SPA routing)
  • Created eventara-dashboard/Dockerfile (Node build → nginx)
  • Created eventara-dashboard/nginx.conf (proxy + SPA fallback)
  • Reverted backend Dockerfile to 2-stage (Maven → JRE only)
  • Updated docker-compose.prod.yaml with 5-container setup
  • CORS set to allow all origins by default

Tested locally: all routes (/, /settings, /rules, /analytics, /api, /swagger) return 200.

Architecture:
- nginx serves React dashboard on port 80 (exposed as 8080)
- nginx proxies /api and /ws to Spring Boot backend (internal)
- Backend has no exposed ports (all traffic goes through nginx)
- SPA routing handled by nginx try_files (no Spring Boot controller needed)

Changes:
- Removed SpaForwardingController (nginx handles SPA routing)
- Created eventara-dashboard/Dockerfile (Node build → nginx)
- Created eventara-dashboard/nginx.conf (proxy + SPA fallback)
- Reverted backend Dockerfile to 2-stage (Maven → JRE only)
- Updated docker-compose.prod.yaml with 5-container setup
- CORS set to allow all origins by default

Tested locally: all routes (/, /settings, /rules, /analytics, /api, /swagger) return 200.
@tusharkhatriofficial tusharkhatriofficial merged commit f96c5b9 into main Feb 18, 2026
2 checks passed
Copy link
Copy Markdown

@charliecreates charliecreates Bot left a comment

Choose a reason for hiding this comment

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

Main blocker is production security: allowedOriginPatterns("*") combined with allowCredentials(true) is an unsafe default and should be replaced with a profile/config-driven allowlist. nginx could be tightened for /ws path matching and forwarded headers to avoid subtle WebSocket routing issues. Consider preventing caching of index.html to avoid stale SPA deploys, and improve Maven Docker layer caching to speed rebuilds.

Additional notes (6)
  • Security | src/main/java/com/eventara/config/WebConfig.java:10-16
    CORS configuration is effectively "allow any origin" while also enabling credentials.

With .allowCredentials(true) + .allowedOriginPatterns("*"), you are permitting cross-origin credentialed requests from arbitrary origins (subject to Spring’s header behavior). This is a significant security risk in production because cookies/Authorization headers can be sent cross-site, and it undermines the protection CORS is meant to provide.

Even if Spring blocks * with credentials in some modes, the intent here is still dangerously broad and easy to misconfigure later. If nginx is the sole entry point, you can also enforce CORS at nginx, but you still shouldn’t leave the app permissive by default.

  • Performance | eventara-dashboard/nginx.conf:44-47
    The nginx config caches *.html indirectly via the SPA fallback and sets long-lived caching for assets, but it doesn’t explicitly prevent caching of index.html.

If index.html gets cached aggressively by intermediaries, users can get stuck on old bundles after deploy. The assets are immutable (good), but index.html should usually be no-cache/short TTL.

  • Performance | Dockerfile:1-7
    The backend Docker build copies only pom.xml and src/ then runs mvn clean package.

This misses common Maven caching optimizations (copy pom.xml, run dependency:go-offline, then copy sources). As-is, every code change invalidates the dependency layer and makes rebuilds slower—especially painful in CI/CD or on VPS rebuilds.

  • Performance | Dockerfile:1-17
    The backend Docker build now copies only pom.xml and src/. This often breaks Maven builds that rely on additional files at repo root (e.g., .mvn/, mvnw, settings.xml, or extra module directories). If this is a single-module project it’s fine, but if you have any of those, the container build may become subtly non-reproducible compared to local builds.

Also, mvn clean package -DskipTests will re-download dependencies every build; consider a dependency caching step (copy pom first, run mvn -q -DskipTests dependency:go-offline, then copy src).

  • Maintainability | eventara-dashboard/Dockerfile:1-9
    npm ci --production=false is a legacy/ambiguous flag and can behave unexpectedly across npm versions. Also, you’re not leveraging layer caching well: copying package*.json first is good, but you can further improve reproducibility and build speed by ensuring you’re using npm ci without extra flags and (optionally) setting NODE_ENV=production only for the runtime stage (which is Nginx anyway).

Separately, setting ENV VITE_API_URL="" bakes an empty default into the build. If the app expects runtime configuration, this approach forces rebuilds per environment unless you implement runtime env injection via Nginx templating or a /config.js pattern.

  • Compatibility | docker-compose.prod.yaml:79-79
    With the backend no longer publishing a host port, the only intended access path is via Nginx. That’s good, but you should ensure the backend is not accidentally reachable from the host via other means and that Spring’s notion of scheme/host is correct behind the proxy.

Right now Nginx forwards X-Forwarded-* headers, but Spring Boot may not honor them unless server.forward-headers-strategy is configured. This can affect generated OpenAPI server URLs, redirects, and cookie Secure handling.

Summary of changes

Overview

This PR refactors deployment to split the React dashboard and Spring Boot API into separate containers, fronted by an nginx container that serves the SPA and proxies API/WebSocket traffic.

Key changes

  • Backend image build simplified
    • Root Dockerfile now builds only the Spring Boot JAR (removed Node/Vite stage and static asset copy).
  • New dashboard container
    • Added eventara-dashboard/Dockerfile (Node build → nginx runtime).
    • Added eventara-dashboard/nginx.conf with:
      • try_files ... /index.html SPA fallback
      • proxy routes for /api/, /ws, /swagger-ui, and /v3/api-docs
  • Production compose wiring updated
    • docker-compose.prod.yaml now introduces a dashboard service as the public entry point and removes host port publishing from the backend.
    • Backend image renamed to eventara-api:latest.
  • SPA routing moved out of Spring
    • Deleted src/main/java/com/eventara/config/SpaForwardingController.java.
  • CORS loosened
    • WebConfig changed from explicit origins to allowedOriginPatterns("*").

Comment on lines +22 to +31
# WebSocket proxy — required for real-time dashboard
location /ws {
proxy_pass http://eventara:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

location /ws is missing a trailing slash and doesn’t forward standard proxy headers used by many WebSocket/SockJS setups.

As written, /wsfoo would also match, and depending on how the backend expects the path, you can end up with subtle routing issues. Additionally, many reverse-proxy WS configs also set X-Forwarded-For/X-Forwarded-Proto (you do for /api/ but not for /ws).

Suggestion

Harden the WebSocket location matching and align headers with /api/.

Suggested nginx snippet:

location /ws/ {
  proxy_pass http://eventara:8080;
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
  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 86400;
}

If the backend endpoint is exactly /ws (no trailing slash), consider location = /ws plus location /ws/ to cover both. Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

Comment on lines +44 to +53
# SPA fallback — serve index.html for all React Router paths
location / {
try_files $uri $uri/ /index.html;
}

# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The Nginx config caches *.js/*.css/etc with immutable for 30 days, but it’s placed after the SPA fallback location /. In Nginx, location / is a prefix match; regex locations are only considered if the selected prefix location does not use ^~ (you don’t), so this may still work—but ordering here is easy to misunderstand and can lead to assets being served without the intended caching headers depending on how other locations evolve.

More importantly: try_files $uri $uri/ /index.html; can cause HTML to be served for missing asset paths, which then might get cached incorrectly if the cache headers ever apply. It’s safer to ensure missing static assets return 404 (not index.html).

Suggestion

Harden SPA + asset handling by:

  • Moving the static asset location above the SPA fallback, and
  • Using try_files $uri =404; for the asset location so missing assets don’t fall back to index.html.

Example:

# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
  try_files $uri =404;
  expires 30d;
  add_header Cache-Control "public, immutable";
}

# SPA fallback
location / {
  try_files $uri $uri/ /index.html;
}

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this nginx.conf adjustment.

@charliecreates charliecreates Bot removed the request for review from CharlieHelps February 18, 2026 21:11
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.

1 participant