Add base kubernetes support 2

This commit is contained in:
Urtzi Alfaro
2025-09-27 12:10:43 +02:00
parent 63a3f9c77a
commit 222f945466
50 changed files with 648 additions and 160 deletions

View File

@@ -1,38 +0,0 @@
# frontend/Dockerfile.development - FIXED VERSION
FROM node:18-alpine
# Install curl for healthchecks
RUN apk add --no-cache curl
# Set working directory
WORKDIR /app
# Create non-root user for security but don't switch yet
RUN addgroup -g 1001 -S nodejs && \
adduser -S reactjs -u 1001 -G nodejs
# Copy package files first (better caching)
COPY package*.json ./
# Install all dependencies (including dev dependencies) as root
RUN npm ci --verbose && \
npm cache clean --force
# Copy source code
COPY . .
# Change ownership of all files to the non-root user
RUN chown -R reactjs:nodejs /app
# Now switch to non-root user
USER reactjs
# Expose port 3000 (Vite default)
EXPOSE 3000
# Add healthcheck
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl -f http://localhost:3000/ || exit 1
# Start development server with host binding
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]

View File

@@ -1,5 +1,5 @@
# Production Dockerfile for Frontend with Nginx
# Multi-stage build for optimal size and performance
# Kubernetes-optimized Dockerfile for Frontend
# Multi-stage build for production deployment
# Stage 1: Build the application
FROM node:18-alpine AS builder
@@ -17,6 +17,7 @@ RUN npm ci --verbose && \
COPY . .
# Build the application for production
# This will use environment variables available at build time
RUN npm run build
# Stage 2: Production server with Nginx
@@ -34,7 +35,30 @@ COPY nginx.conf /etc/nginx/conf.d/
# Copy built application from builder stage
COPY --from=builder /app/dist /usr/share/nginx/html
# Nginx user already exists in the base image, just ensure proper ownership
# Create a script to substitute environment variables at runtime
COPY <<'EOF' /docker-entrypoint.d/30-substitute-env.sh
#!/bin/sh
set -e
# Default values for environment variables
export VITE_API_URL=${VITE_API_URL:-"http://gateway-service:8000"}
export VITE_APP_TITLE=${VITE_APP_TITLE:-"PanIA Dashboard"}
export VITE_APP_VERSION=${VITE_APP_VERSION:-"1.0.0"}
# Create a runtime configuration file that can be loaded by the frontend
cat > /usr/share/nginx/html/runtime-config.js << EOL
window.__RUNTIME_CONFIG__ = {
VITE_API_URL: '${VITE_API_URL}',
VITE_APP_TITLE: '${VITE_APP_TITLE}',
VITE_APP_VERSION: '${VITE_APP_VERSION}'
};
EOL
echo "Runtime configuration created with API URL: ${VITE_API_URL}"
EOF
# Make the script executable
RUN chmod +x /docker-entrypoint.d/30-substitute-env.sh
# Set proper permissions
RUN chown -R nginx:nginx /usr/share/nginx/html && \
@@ -65,7 +89,7 @@ EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl -f http://localhost:3000/ || exit 1
CMD curl -f http://localhost:3000/health || exit 1
# Start nginx
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,41 +0,0 @@
# frontend/Dockerfile.production
# Multi-stage build for production
# Build stage
FROM node:18-alpine as builder
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy source code
COPY . .
# Build the application
RUN npm run build
# Production stage
FROM nginx:alpine
# Install curl for healthchecks
RUN apk add --no-cache curl
# Copy built app from builder stage
COPY --from=builder /app/dist /usr/share/nginx/html
# Copy nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf
# Expose port 80
EXPOSE 80
# Add healthcheck
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl -f http://localhost/ || exit 1
# Start nginx
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -22,6 +22,8 @@
</head>
<body>
<div id="root"></div>
<!-- Runtime configuration - loaded by Kubernetes deployment -->
<script src="/runtime-config.js"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -12,7 +12,7 @@ server {
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;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com https://js.stripe.com; script-src-elem 'self' 'unsafe-inline' https://js.stripe.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' http://localhost:8000 http://localhost:8006 ws: wss:; frame-src https://js.stripe.com;" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com https://js.stripe.com; script-src-elem 'self' 'unsafe-inline' https://js.stripe.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' http://gateway-service:8000 http://localhost:8000 http://localhost:8006 ws: wss:; frame-src https://js.stripe.com;" always;
# Gzip compression
gzip on;
@@ -31,9 +31,9 @@ server {
application/atom+xml
image/svg+xml;
# API proxy to gateway service (Kubernetes internal name)
# API proxy to gateway service (Kubernetes service name)
location /api/ {
proxy_pass http://gateway:8000;
proxy_pass http://gateway-service:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';

View File

@@ -10,6 +10,7 @@
* React Query doesn't replace HTTP clients - it manages data fetching/caching/sync
*/
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';
import { getApiUrl } from '../../config/runtime';
export interface ApiError {
message: string;
@@ -55,7 +56,7 @@ class ApiClient {
config: AxiosRequestConfig;
}> = [];
constructor(baseURL: string = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000/api/v1') {
constructor(baseURL: string = getApiUrl() + '/api/v1') {
this.baseURL = baseURL;
this.client = axios.create({

View File

@@ -0,0 +1,62 @@
// Runtime configuration for Kubernetes deployments
// This allows environment variables to be injected at container startup
interface RuntimeConfig {
VITE_API_URL: string;
VITE_APP_TITLE: string;
VITE_APP_VERSION: string;
}
declare global {
interface Window {
__RUNTIME_CONFIG__?: RuntimeConfig;
}
}
// Types are defined in vite-env.d.ts
// Get configuration from runtime or fall back to build-time environment variables
function getRuntimeConfig(): RuntimeConfig {
// First try to get from window (injected at runtime in Kubernetes)
if (typeof window !== 'undefined' && window.__RUNTIME_CONFIG__) {
return window.__RUNTIME_CONFIG__;
}
// Fall back to build-time environment variables (development/local)
return {
VITE_API_URL: import.meta.env.VITE_API_URL || 'http://localhost:8000',
VITE_APP_TITLE: import.meta.env.VITE_APP_TITLE || 'PanIA Dashboard',
VITE_APP_VERSION: import.meta.env.VITE_APP_VERSION || '1.0.0',
};
}
export const config = getRuntimeConfig();
// Helper function to get the API base URL
export function getApiUrl(): string {
return config.VITE_API_URL;
}
// Helper function to get app title
export function getAppTitle(): string {
return config.VITE_APP_TITLE;
}
// Helper function to get app version
export function getAppVersion(): string {
return config.VITE_APP_VERSION;
}
// Helper to check if running in Kubernetes
export function isKubernetesEnvironment(): boolean {
return typeof window !== 'undefined' && !!window.__RUNTIME_CONFIG__;
}
// Debug function to log current configuration
export function logConfig(): void {
console.log('Current configuration:', {
...config,
isKubernetes: isKubernetesEnvironment(),
source: isKubernetesEnvironment() ? 'runtime' : 'build-time'
});
}

14
frontend/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string
readonly VITE_API_BASE_URL: string
readonly VITE_APP_TITLE: string
readonly VITE_APP_VERSION: string
readonly VITE_ENVIRONMENT: string
// more env variables...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}

View File

@@ -28,10 +28,12 @@ export default defineConfig({
},
proxy: {
'/api': {
target: process.env.NODE_ENV === 'development'
? 'http://gateway:8000' // Use internal service name in Kubernetes
: 'http://localhost:8000',
target: process.env.VITE_API_URL ||
(process.env.NODE_ENV === 'development' && process.env.KUBERNETES_SERVICE_HOST
? 'http://gateway-service:8000' // Kubernetes internal service
: 'http://localhost:8000'), // Local development
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},