diff --git a/Dockerfile b/Dockerfile index 07f0537..1fa64bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,8 +28,12 @@ FROM nginx:1.29.1-alpine # Remove default nginx website RUN rm -rf /usr/share/nginx/html/* +# Create subfolder structure dynamically based on VITE_BASE_PATH +ARG VITE_BASE_PATH="" +RUN if [ -n "$VITE_BASE_PATH" ]; then mkdir -p /usr/share/nginx/html/$VITE_BASE_PATH; fi + # Copy built application -COPY --from=builder /app/build /usr/share/nginx/html +COPY --from=builder /app/build /usr/share/nginx/html/${VITE_BASE_PATH} # Copy nginx configuration COPY nginx.conf /etc/nginx/nginx.conf @@ -44,7 +48,7 @@ EXPOSE 80 # Health check HEALTHCHECK --interval=120s --timeout=10s --start-period=60s --retries=3 \ - CMD curl -f http://localhost/ || exit 1 + CMD if [ -n "$VITE_BASE_PATH" ]; then curl -f http://localhost/$VITE_BASE_PATH/ || exit 1; else curl -f http://localhost/ || exit 1; fi # Start nginx CMD ["nginx", "-g", "daemon off;"] diff --git a/generate-nginx-config.sh b/generate-nginx-config.sh new file mode 100644 index 0000000..6dd8b1e --- /dev/null +++ b/generate-nginx-config.sh @@ -0,0 +1,153 @@ +#!/bin/sh + +# Generate nginx.conf based on VITE_BASE_PATH environment variable +BASE_PATH=${VITE_BASE_PATH:-""} + +if [ -n "$BASE_PATH" ]; then + # Configuration for subfolder deployment + cat > /etc/nginx/nginx.conf << EOF +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging + log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" ' + '\$status \$body_bytes_sent "\$http_referer" ' + '"\$http_user_agent" "\$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log warn; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied any; + gzip_comp_level 6; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + + server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; + + # Static assets caching for $BASE_PATH subfolder + location ~* ^/$BASE_PATH/.*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)\$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Main application at /$BASE_PATH/ - SPA fallback + location /$BASE_PATH/ { + try_files \$uri \$uri/ /$BASE_PATH/index.html; + } + + # Redirect root to application + location = / { + return 301 /$BASE_PATH/; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + } +} +EOF +else + # Configuration for root deployment + cat > /etc/nginx/nginx.conf << EOF +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging + log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" ' + '\$status \$body_bytes_sent "\$http_referer" ' + '"\$http_user_agent" "\$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log warn; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied any; + gzip_comp_level 6; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + + server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; + + # Static assets caching + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)\$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Main application - SPA fallback + location / { + try_files \$uri \$uri/ /index.html; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + } +} +EOF +fi + +echo "Generated nginx config for BASE_PATH: '$BASE_PATH'" \ No newline at end of file diff --git a/nginx.conf b/nginx.conf index 9610fbb..a7f3e79 100644 --- a/nginx.conf +++ b/nginx.conf @@ -45,15 +45,21 @@ http { add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; - # Static assets caching - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + # Static assets caching - works for any subfolder or root + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; } - # Main application - SPA fallback + # SPA fallback - catch all routes and serve appropriate index.html location / { - try_files $uri $uri/ /index.html; + try_files $uri $uri/ @fallback; + } + + # Fallback handler for SPA routes + location @fallback { + # Try to find index.html in the same directory structure + try_files $uri/index.html /index.html; } # Health check endpoint