# syntax=docker/dockerfile:1.7 # ───────────────────────────────────────────────────────────────────────────── # Stage 1: Dependencies # ───────────────────────────────────────────────────────────────────────────── FROM node:22-bookworm-slim AS deps WORKDIR /app COPY package.json package-lock.json ./ RUN --mount=type=cache,target=/root/.npm \ NODE_ENV=development npm ci --prefer-offline # ───────────────────────────────────────────────────────────────────────────── # Stage 2: Build # ───────────────────────────────────────────────────────────────────────────── FROM node:22-bookworm-slim AS build WORKDIR /app # Accept build arguments for Vite env vars ARG VITE_API_URL=http://localhost:3000 ENV VITE_API_URL=${VITE_API_URL} COPY package.json package-lock.json ./ RUN NODE_ENV=development npm ci COPY tsconfig.json tsconfig.app.json tsconfig.node.json ./ COPY vite.config.ts tailwind.config.js postcss.config.js ./ COPY index.html ./ COPY public ./public COPY src ./src # Build the Vite app (outputs to /app/dist) RUN npm run build # ───────────────────────────────────────────────────────────────────────────── # Stage 3: Runtime (nginx) # ───────────────────────────────────────────────────────────────────────────── FROM nginx:alpine AS runtime # Copy built static files to nginx html directory COPY --from=build /app/dist /usr/share/nginx/html # Create nginx configuration for SPA routing RUN cat > /etc/nginx/conf.d/default.conf <<'EOF' server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; # Gzip compression gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; # 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"; } # SPA fallback: serve index.html for all routes location / { try_files $uri $uri/ /index.html; } # Health check endpoint location /healthz { access_log off; return 200 "OK\n"; add_header Content-Type text/plain; } } EOF EXPOSE 80 HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=5s \ CMD wget --no-verbose --tries=1 --spider http://localhost/healthz || exit 1 CMD ["nginx", "-g", "daemon off;"]