diff --git a/.env.example b/.env.example index 2e952fe..81fdc06 100644 --- a/.env.example +++ b/.env.example @@ -39,7 +39,9 @@ PROVIDER_BASE_URL_ALLOWLIST=[] PUBLIC_BASE_URL=http://localhost:8000 CORS_ORIGINS=["http://localhost:5173","http://localhost:3000"] +# Used at build time for production frontend image, and at runtime in development. VITE_API_BASE= +# Development-only Vite host allowlist override. VITE_ALLOWED_HOSTS= # Optional frontend build network and npm fetch tuning: diff --git a/README.md b/README.md index 0fd01bb..9cfb0be 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ These create an extra non-admin account on first startup. - `APP_ENV=development`: Local mode (default). - `APP_ENV=production`: Use when running as a real shared deployment with HTTPS and tighter security settings. + - Frontend runtime switches to a static build served by Nginx in this mode. ## Daily Use Commands diff --git a/doc/operations-and-configuration.md b/doc/operations-and-configuration.md index 7adc680..38de046 100644 --- a/doc/operations-and-configuration.md +++ b/doc/operations-and-configuration.md @@ -86,7 +86,7 @@ Use `.env.example` as baseline. The table below documents user-managed settings | `APP_ENV` | `development` | `production` | | `HOST_BIND_IP` | `127.0.0.1` or local LAN bind if needed | `127.0.0.1` (publish behind proxy only) | | `PUBLIC_BASE_URL` | `http://localhost:8000` | `https://api.example.com` | -| `VITE_API_BASE` | empty for host-derived `http://:8000/api/v1`, or explicit local URL | `https://api.example.com/api/v1` | +| `VITE_API_BASE` | empty for host-derived `http://:8000/api/v1`, or explicit local URL | `https://api.example.com/api/v1` (build-time value for production frontend image) | | `VITE_ALLOWED_HOSTS` | optional comma-separated hostnames, for example `localhost,docs.lan` | optional comma-separated public frontend hostnames, for example `app.example.com` | | `CORS_ORIGINS` | `["http://localhost:5173","http://localhost:3000"]` | exact frontend origins only, for example `["https://app.example.com"]` | | `REDIS_URL` | `redis://:@redis:6379/0` in isolated local network | `rediss://:@redis.internal:6379/0` | @@ -139,13 +139,14 @@ Recommended LIVE pattern: ## Frontend Runtime - Frontend no longer consumes `VITE_API_TOKEN`. -- Frontend startup mode is environment-driven: - - `APP_ENV=development` runs `vite dev` - - `APP_ENV=production` runs `vite build` then `vite preview` +- Frontend image target is environment-driven: + - `APP_ENV=development` builds the `development` target and runs Vite dev server + - `APP_ENV=production` builds the `production` target and serves static assets through Nginx +- Frontend Docker targets are selected from `APP_ENV`, so use `development` or `production` values. - Vite dev server host allowlist uses the union of: - hostnames extracted from `CORS_ORIGINS` - optional explicit hostnames from `VITE_ALLOWED_HOSTS` -- The same host allowlist policy is applied to both Vite `server` and `preview`. +- `VITE_ALLOWED_HOSTS` only affects development mode where Vite is running. - Session authentication is cookie-based; browser reloads and new tabs can reuse an active session until it expires or is revoked. - Protected media and file download flows still use authenticated fetch plus blob/object URL handling. diff --git a/docker-compose.yml b/docker-compose.yml index b7a02bc..03c1c45 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -143,15 +143,16 @@ services: frontend: build: context: ./frontend + target: ${APP_ENV:-development} network: ${DOCKER_BUILD_NETWORK:-default} args: + VITE_API_BASE: ${VITE_API_BASE:-} NPM_REGISTRY: ${NPM_REGISTRY:-https://registry.npmjs.org/} NPM_FETCH_RETRIES: ${NPM_FETCH_RETRIES:-5} NPM_FETCH_RETRY_MINTIMEOUT: ${NPM_FETCH_RETRY_MINTIMEOUT:-20000} NPM_FETCH_RETRY_MAXTIMEOUT: ${NPM_FETCH_RETRY_MAXTIMEOUT:-120000} NPM_FETCH_TIMEOUT: ${NPM_FETCH_TIMEOUT:-300000} environment: - APP_ENV: ${APP_ENV:-development} VITE_API_BASE: ${VITE_API_BASE:-} CORS_ORIGINS: '${CORS_ORIGINS:-["http://localhost:5173","http://localhost:3000"]}' VITE_ALLOWED_HOSTS: ${VITE_ALLOWED_HOSTS:-} diff --git a/frontend/Dockerfile b/frontend/Dockerfile index bd4d27f..9b503e2 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-slim +FROM node:22-slim AS base ARG NPM_REGISTRY=https://registry.npmjs.org/ ARG NPM_FETCH_RETRIES=5 @@ -15,19 +15,35 @@ RUN npm config set registry "${NPM_REGISTRY}" \ && npm config set fetch-retry-mintimeout "${NPM_FETCH_RETRY_MINTIMEOUT}" \ && npm config set fetch-retry-maxtimeout "${NPM_FETCH_RETRY_MAXTIMEOUT}" \ && npm config set fetch-timeout "${NPM_FETCH_TIMEOUT}" \ - && NODE_OPTIONS=--dns-result-order=ipv4first npm ci --no-audit -RUN chown -R node:node /app + && NODE_OPTIONS=--dns-result-order=ipv4first npm ci --no-audit \ + && chown -R node:node /app COPY --chown=node:node tsconfig.json /app/tsconfig.json COPY --chown=node:node tsconfig.node.json /app/tsconfig.node.json COPY --chown=node:node vite.config.ts /app/vite.config.ts COPY --chown=node:node index.html /app/index.html COPY --chown=node:node src /app/src -COPY --chown=node:node docker-entrypoint.sh /app/docker-entrypoint.sh -RUN chmod +x /app/docker-entrypoint.sh + +FROM base AS development EXPOSE 5173 USER node -CMD ["/app/docker-entrypoint.sh"] +CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "5173"] + +FROM base AS build + +ARG VITE_API_BASE= +ENV VITE_API_BASE=${VITE_API_BASE} + +RUN npm run build + +FROM nginx:1.27-alpine AS production + +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist /usr/share/nginx/html + +EXPOSE 5173 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/docker-entrypoint.sh b/frontend/docker-entrypoint.sh deleted file mode 100644 index d0cac0e..0000000 --- a/frontend/docker-entrypoint.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh - -# Frontend runtime entrypoint. -# Uses APP_ENV to select development or production Vite startup mode. - -set -eu - -APP_ENV_VALUE="${APP_ENV:-development}" -HOST_VALUE="${FRONTEND_HOST:-0.0.0.0}" -PORT_VALUE="${FRONTEND_PORT:-5173}" - -if [ "$APP_ENV_VALUE" = "production" ]; then - npm run build - exec npm run preview -- --host "$HOST_VALUE" --port "$PORT_VALUE" -fi - -exec npm run dev -- --host "$HOST_VALUE" --port "$PORT_VALUE" diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..ac3d618 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,12 @@ +server { + listen 5173; + listen [::]:5173; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index eef2d2c..7f50d21 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -81,10 +81,5 @@ export default defineConfig(({ mode }) => { port: 5173, ...(allowedHosts ? { allowedHosts } : {}), }, - preview: { - host: '0.0.0.0', - port: 5173, - ...(allowedHosts ? { allowedHosts } : {}), - }, }; });