FROM node:20 AS builder WORKDIR /app COPY package*.json ./ # First generate package-lock.json if it doesn't exist, then do clean install RUN if [ ! -f package-lock.json ]; then npm install --package-lock-only; fi && npm ci COPY . . # Vite bakes VITE_* values at build time. Pass the public VITE_* values as # build args (docker compose --env-file .env.dev) instead of COPYing an env file; # service-host worktrees keep .env.dev as a symlink outside the Docker context. ARG VITE_API_BASE ARG VITE_API_URL ARG VITE_APP_NAME ARG VITE_APP_HMR_URL ARG VITE_DEV ARG VITE_FRONTEND_SITE_URL ARG VITE_SEARCH_URL ARG VITE_SUPABASE_ANON_KEY ARG VITE_SUPABASE_URL ARG VITE_SUPER_ADMIN_EMAIL ARG VITE_TLSYNC_URL ARG VITE_WHISPERLIVE_URL # Run build with production mode. Keep these as build-step environment values # rather than final-image ENV entries; Vite still embeds the public client config # into the static bundle, but nginx image metadata does not need them. RUN VITE_API_BASE="${VITE_API_BASE}" \ VITE_API_URL="${VITE_API_URL}" \ VITE_APP_NAME="${VITE_APP_NAME}" \ VITE_APP_HMR_URL="${VITE_APP_HMR_URL}" \ VITE_DEV="${VITE_DEV}" \ VITE_FRONTEND_SITE_URL="${VITE_FRONTEND_SITE_URL}" \ VITE_SEARCH_URL="${VITE_SEARCH_URL}" \ VITE_SUPABASE_ANON_KEY="${VITE_SUPABASE_ANON_KEY}" \ VITE_SUPABASE_URL="${VITE_SUPABASE_URL}" \ VITE_SUPER_ADMIN_EMAIL="${VITE_SUPER_ADMIN_EMAIL}" \ VITE_TLSYNC_URL="${VITE_TLSYNC_URL}" \ VITE_WHISPERLIVE_URL="${VITE_WHISPERLIVE_URL}" \ npm run build -- --mode production FROM nginx:alpine # Copy built files COPY --from=builder /app/dist /usr/share/nginx/html # .mjs files (pdfjs worker) must be served as application/javascript for module workers RUN sed -i 's|application/javascript\s*js;|application/javascript js mjs;|' /etc/nginx/mime.types # Create a simple nginx configuration RUN echo 'server { \ listen 3000; \ root /usr/share/nginx/html; \ index index.html; \ location = /index.html { \ expires -1; \ add_header Cache-Control "no-store, no-cache, must-revalidate"; \ } \ location = /health { \ proxy_pass http://192.168.0.64:18000/health; \ proxy_set_header Host $host; \ proxy_set_header X-Real-IP $remote_addr; \ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; \ } \ location /__ccapi/ { \ proxy_pass http://192.168.0.64:18000/; \ proxy_set_header Host $host; \ proxy_set_header X-Real-IP $remote_addr; \ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; \ } \ location /__supabase/ { \ proxy_pass http://192.168.0.94:8000/; \ 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 Upgrade $http_upgrade; \ proxy_set_header Connection "upgrade"; \ proxy_http_version 1.1; \ } \ location /api/ { \ proxy_pass http://192.168.0.64:18000/api/; \ proxy_set_header Host $host; \ proxy_set_header X-Real-IP $remote_addr; \ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; \ } \ location / { \ try_files $uri $uri/ /index.html; \ expires 30d; \ add_header Cache-Control "public, no-transform"; \ } \ location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ { \ expires 30d; \ add_header Cache-Control "public, no-transform"; \ } \ location /searxng-api/ { \ proxy_pass https://search.kevlarai.com/; \ proxy_ssl_server_name on; \ proxy_set_header Host search.kevlarai.com; \ } \ location ~ /\. { \ deny all; \ } \ error_page 404 /index.html; \ }' > /etc/nginx/conf.d/default.conf # Set up permissions RUN chown -R nginx:nginx /usr/share/nginx/html \ && chown -R nginx:nginx /var/log/nginx # Expose HTTP port (NPM will handle HTTPS) EXPOSE 3000