From 5c8565367cd5de861389e27e706d32cd82dc520c Mon Sep 17 00:00:00 2001 From: Lukasz Raczylo Date: Sun, 4 Jan 2026 00:30:20 +0000 Subject: [PATCH] refactor: merge gateway functionality into frontend container MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eliminated duplicate nginx containers by merging gateway reverse proxy functionality into the frontend container. This simplifies deployment and reduces resource usage. Architecture changes: - Frontend now serves both static files AND reverse proxies to backend - Single nginx container handles all HTTP routing - Gateway container removed from builds and Helm chart Dockerfile.frontend changes: - Added upstream backend configuration - Added proxy locations for /api, /health, /metrics, /npm, /pypi, /go, /ws - Added rate limiting for API and downloads - Added WebSocket support - Configurable via BACKEND_HOST and BACKEND_PORT env vars Helm chart changes: - Updated frontend deployment to configure backend connection - Simplified ingress to single route (all traffic → frontend) - Frontend proxies backend requests internally - Removed separate frontend/api ingress configurations GoReleaser changes: - Removed gohoarder-gateway Docker build - Now builds: server, scanner, migrate, frontend (4 images) Benefits: - Fewer containers to manage - Reduced complexity in Docker Compose and Kubernetes - Single point of configuration for routing - Better resource utilization --- .goreleaser.yaml | 23 +-- Dockerfile.frontend | 145 ++++++++++++++++-- .../templates/deployment-frontend.yaml | 9 +- helm/gohoarder/templates/ingress.yaml | 98 +----------- helm/gohoarder/values.yaml | 19 +-- 5 files changed, 152 insertions(+), 142 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 94688f8..81eeefb 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -209,28 +209,7 @@ dockers_v2: - internal - config.yaml.example - # 4. Gateway - Nginx reverse proxy for unified deployment - - id: gohoarder-gateway - images: - - ghcr.io/lukaszraczylo/gohoarder-gateway - tags: - - "{{ .Version }}" - - latest - platforms: - - linux/amd64 - - linux/arm64 - dockerfile: Dockerfile.gateway - flags: - - "--pull" - - "--label=org.opencontainers.image.title=GoHoarder Gateway" - - "--label=org.opencontainers.image.description=Nginx reverse proxy for unified GoHoarder deployment" - - "--label=org.opencontainers.image.url=https://github.com/lukaszraczylo/gohoarder" - - "--label=org.opencontainers.image.source=https://github.com/lukaszraczylo/gohoarder" - - "--label=org.opencontainers.image.version={{ .Version }}" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - # 5. Migration Engine - Database migration tool + # 4. Migration Engine - Database migration tool - id: gohoarder-migrate images: - ghcr.io/lukaszraczylo/gohoarder-migrate diff --git a/Dockerfile.frontend b/Dockerfile.frontend index c4f3175..c6e8a6b 100644 --- a/Dockerfile.frontend +++ b/Dockerfile.frontend @@ -1,4 +1,5 @@ -# Website - Frontend Dashboard +# Website - Frontend Dashboard with integrated reverse proxy +# Combines frontend serving and backend proxy (previously separate gateway) # Build stage FROM node:20-alpine AS builder @@ -48,21 +49,129 @@ EOF RUN chmod +x /docker-entrypoint.d/40-inject-config.sh -# Copy nginx configuration -RUN cat > /etc/nginx/conf.d/default.conf <<'EOF' +# Create nginx configuration template with backend proxy support +RUN cat > /etc/nginx/templates/default.conf.template <<'EOF' +# Upstream backend server +upstream backend { + server ${BACKEND_HOST}:${BACKEND_PORT}; + keepalive 32; +} + +# Rate limiting zones +limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; +limit_req_zone $binary_remote_addr zone=download_limit:10m rate=5r/s; + +# Cache configuration +proxy_cache_path /var/cache/nginx/static levels=1:2 keys_zone=static_cache:10m max_size=100m inactive=60m use_temp_path=off; + server { listen 80; - server_name _; + server_name ${SERVER_NAME}; root /usr/share/nginx/html; index index.html; + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # Client body size for package uploads + client_max_body_size 500M; + client_body_timeout 300s; + + # Logging + access_log /var/log/nginx/access.log combined; + error_log /var/log/nginx/error.log warn; + # Compression gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript; - # SPA routing - location / { - try_files $uri $uri/ /index.html; + # API endpoints - proxy to backend + location /api/ { + # Rate limiting + limit_req zone=api_limit burst=20 nodelay; + + # Proxy settings + proxy_pass http://backend/; + proxy_http_version 1.1; + + # Headers + 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_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header Connection ""; + + # Timeouts for long-running operations + proxy_connect_timeout 60s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + + # Buffer settings + proxy_buffering on; + proxy_buffer_size 4k; + proxy_buffers 8 4k; + proxy_busy_buffers_size 8k; + } + + # Health check endpoint + location /health { + proxy_pass http://backend/health; + proxy_http_version 1.1; + proxy_set_header Connection ""; + access_log off; + } + + # Metrics endpoint + location /metrics { + proxy_pass http://backend/metrics; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } + + # Package download endpoints with rate limiting + location ~ ^/(npm|pypi|go)/ { + limit_req zone=download_limit burst=10 nodelay; + + proxy_pass http://backend; + 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; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Connection ""; + + # Extended timeouts for package downloads + proxy_connect_timeout 60s; + proxy_send_timeout 600s; + proxy_read_timeout 600s; + + # Large buffer for package downloads + proxy_buffering on; + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + } + + # WebSocket support + location /ws/ { + proxy_pass http://backend/ws/; + 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; + + # WebSocket timeouts + proxy_connect_timeout 7d; + proxy_send_timeout 7d; + proxy_read_timeout 7d; } # Runtime configuration endpoint @@ -72,19 +181,23 @@ server { add_header Expires "0"; } - # Security headers - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-XSS-Protection "1; mode=block" always; - # Cache static assets location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; } + + # Frontend SPA - serve index.html for all other routes + location / { + try_files $uri $uri/ /index.html; + } } EOF +# Create cache directory +RUN mkdir -p /var/cache/nginx/static && \ + chown -R nginx:nginx /var/cache/nginx + # Expose port EXPOSE 80 @@ -95,6 +208,10 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ # Environment variables with defaults ENV API_BASE_URL=/api \ APP_VERSION=unknown \ - APP_NAME=GoHoarder + APP_NAME=GoHoarder \ + BACKEND_HOST=gohoarder-server \ + BACKEND_PORT=8080 \ + SERVER_NAME=_ -CMD ["nginx", "-g", "daemon off;"] +# Use nginx template substitution and start nginx +CMD ["/bin/sh", "-c", "envsubst '$$BACKEND_HOST $$BACKEND_PORT $$SERVER_NAME' < /etc/nginx/templates/default.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"] diff --git a/helm/gohoarder/templates/deployment-frontend.yaml b/helm/gohoarder/templates/deployment-frontend.yaml index 97dbe60..f8bc47b 100644 --- a/helm/gohoarder/templates/deployment-frontend.yaml +++ b/helm/gohoarder/templates/deployment-frontend.yaml @@ -67,11 +67,18 @@ spec: protocol: TCP env: - name: API_BASE_URL - value: {{ .Values.frontend.backendUrl | default (printf "http://%s-server:%d" (include "gohoarder.fullname" .) (.Values.server.service.port | int)) | quote }} + value: {{ .Values.frontend.backendUrl | default "/api" | quote }} - name: APP_VERSION value: {{ .Chart.AppVersion | quote }} - name: APP_NAME value: "GoHoarder" + # Backend proxy configuration (frontend now includes reverse proxy) + - name: BACKEND_HOST + value: {{ include "gohoarder.fullname" . }}-server + - name: BACKEND_PORT + value: {{ .Values.server.service.port | quote }} + - name: SERVER_NAME + value: {{ .Values.frontend.serverName | default "_" | quote }} {{- with .Values.frontend.env }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/helm/gohoarder/templates/ingress.yaml b/helm/gohoarder/templates/ingress.yaml index cd4f08c..3423528 100644 --- a/helm/gohoarder/templates/ingress.yaml +++ b/helm/gohoarder/templates/ingress.yaml @@ -1,11 +1,10 @@ {{- if .Values.ingress.enabled -}} -{{- if .Values.ingress.frontend.enabled -}} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: {{ include "gohoarder.fullname" . }}-frontend + name: {{ include "gohoarder.fullname" . }} labels: - {{- include "gohoarder.frontend.labels" . | nindent 4 }} + {{- include "gohoarder.labels" . | nindent 4 }} {{- with .Values.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} @@ -14,65 +13,17 @@ spec: {{- if .Values.ingress.className }} ingressClassName: {{ .Values.ingress.className }} {{- end }} - {{- if .Values.ingress.frontend.tls.enabled }} + {{- if .Values.ingress.tls.enabled }} tls: - hosts: - - {{ .Values.ingress.frontend.host | default (printf "%s.%s" "gohoarder" .Values.global.domain) | quote }} - secretName: {{ .Values.ingress.frontend.tls.secretName }} + - {{ .Values.ingress.host | default (printf "%s.%s" "gohoarder" .Values.global.domain) | quote }} + secretName: {{ .Values.ingress.tls.secretName }} {{- end }} rules: - - host: {{ .Values.ingress.frontend.host | default (printf "%s.%s" "gohoarder" .Values.global.domain) | quote }} + - host: {{ .Values.ingress.host | default (printf "%s.%s" "gohoarder" .Values.global.domain) | quote }} http: paths: - - path: /npm - pathType: Prefix - backend: - service: - name: {{ include "gohoarder.fullname" . }}-server - port: - number: {{ .Values.server.service.port }} - - path: /pypi - pathType: Prefix - backend: - service: - name: {{ include "gohoarder.fullname" . }}-server - port: - number: {{ .Values.server.service.port }} - - path: /go - pathType: Prefix - backend: - service: - name: {{ include "gohoarder.fullname" . }}-server - port: - number: {{ .Values.server.service.port }} - - path: /api - pathType: Prefix - backend: - service: - name: {{ include "gohoarder.fullname" . }}-server - port: - number: {{ .Values.server.service.port }} - - path: /ws - pathType: Prefix - backend: - service: - name: {{ include "gohoarder.fullname" . }}-server - port: - number: {{ .Values.server.service.port }} - - path: /health - pathType: Prefix - backend: - service: - name: {{ include "gohoarder.fullname" . }}-server - port: - number: {{ .Values.server.service.port }} - - path: /metrics - pathType: Prefix - backend: - service: - name: {{ include "gohoarder.fullname" . }}-server - port: - number: {{ .Values.server.service.port }} + # Route all traffic to frontend (which now includes reverse proxy to backend) - path: / pathType: Prefix backend: @@ -81,38 +32,3 @@ spec: port: number: {{ .Values.frontend.service.port }} {{- end }} ---- -{{- if .Values.ingress.api.enabled }} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ include "gohoarder.fullname" . }}-api - labels: - {{- include "gohoarder.server.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if .Values.ingress.className }} - ingressClassName: {{ .Values.ingress.className }} - {{- end }} - {{- if .Values.ingress.api.tls.enabled }} - tls: - - hosts: - - {{ .Values.ingress.api.host | default (printf "api.%s.%s" "gohoarder" .Values.global.domain) | quote }} - secretName: {{ .Values.ingress.api.tls.secretName }} - {{- end }} - rules: - - host: {{ .Values.ingress.api.host | default (printf "api.%s.%s" "gohoarder" .Values.global.domain) | quote }} - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: {{ include "gohoarder.fullname" . }}-server - port: - number: {{ .Values.server.service.port }} -{{- end }} -{{- end }} diff --git a/helm/gohoarder/values.yaml b/helm/gohoarder/values.yaml index 3632c83..ab73fb6 100644 --- a/helm/gohoarder/values.yaml +++ b/helm/gohoarder/values.yaml @@ -507,21 +507,12 @@ ingress: nginx.ingress.kubernetes.io/proxy-read-timeout: "300" nginx.ingress.kubernetes.io/proxy-send-timeout: "300" - # Ingress for frontend - frontend: - enabled: true - host: "gohoarder.local" - tls: - enabled: false - secretName: "gohoarder-frontend-tls" - - # Ingress for API (if you want separate ingress) - api: + # Single ingress routes all traffic to frontend + # Frontend now includes reverse proxy to backend (merged gateway functionality) + host: "gohoarder.local" + tls: enabled: false - host: "api.gohoarder.local" - tls: - enabled: false - secretName: "gohoarder-api-tls" + secretName: "gohoarder-tls" # Autoscaling configuration autoscaling: