# syntax=docker/dockerfile:1.7 # ============================================================================= # AuthGlow — single portable image (FastAPI backend + built SPA frontend). # # One container, one port, no reverse proxy and no extra services needed. It # runs unchanged on Cloud Run, GKE, EKS, ECS, any Docker host — anything that # runs a container and injects a $PORT env var. The FastAPI app serves the # API routes AND the pre-built React app (single-origin, cookies just work). # # docker build -t authglow . # docker run -p 8080:8080 -e PORT=8080 -e SECRET_KEY=... authglow # ============================================================================= # ---- Stage 1: build the frontend ------------------------------------------- # Matches the `engines` requirement in frontend/package.json (node >=26.7.0). FROM node:26-slim AS frontend-build WORKDIR /app/frontend COPY frontend/package*.json ./ RUN npm ci --no-audit --no-fund COPY frontend/ ./ # VITE_API_URL is intentionally NOT set: the SPA is built with the relative, # same-origin API base (see frontend/src/lib/constants.ts), so this single # image works behind any public URL without a rebuild. RUN npm run build # ---- Stage 2: runtime (FastAPI + SPA assets) -------------------------------- FROM python:3.13-slim AS runtime WORKDIR /app # UVICORN_WORKERS defaults to 1: each worker is a full Python process with # its own caches and rate-limiter counters, so orchestrator replicas remain # the recommended scaling path (see docs/internals/architecture/deployment.md, # "Process Scaling"). Raise it only with shared cache/limiter backends set. ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ PORT=8080 \ UVICORN_WORKERS=1 \ FRONTEND_DIST_DIR=/app/frontend/dist # System deps for build/ssh wheels are pulled by pip; Python only deps below. COPY backend/requirements.txt ./ RUN pip install -r requirements.txt COPY backend/ ./ COPY --from=frontend-build /app/frontend/dist /app/frontend/dist # Runtime state (users, keys, sessions) is volume-mounted here or on GCS. RUN mkdir -p /app/data /app/data/keys \ && chmod 700 /app/data \ && useradd --system --create-home --uid 1000 appuser \ && chown -R appuser:appuser /app/data # Run as an unprivileged user: a container escape must not yield host root # (VAPT-095). The app only ever writes under /app/data. USER appuser EXPOSE 8080 # $PORT comes from the platform (Cloud Run, Railway…) or is set at runtime. # $UVICORN_WORKERS opts into multiple worker processes in one container. HEALTHCHECK --interval=30s --timeout=3s --start-period=15s --retries=3 \ CMD python -c "import os,urllib.request;urllib.request.urlopen('http://127.0.0.1:'+os.environ.get('PORT','8080')+'/health',timeout=2)" CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port ${PORT} --workers ${UVICORN_WORKERS}"]