From 0aeacfd14f0e2cfec2d9eb8ce382b38c30c49f1d Mon Sep 17 00:00:00 2001 From: "m2 (AI Agent)" Date: Thu, 2 Jul 2026 05:49:09 +0200 Subject: [PATCH] wedge(002/T103): web Dockerfile + M2MW_STATIC_DIR (site-packages path fix); merge T101/T102 Co-Authored-By: Claude Fable 5 --- .specify/feature.json | 2 +- web/Dockerfile | 24 ++++++++++++++++++++++++ web/backend/src/m2_market_web/main.py | 10 +++++++++- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 web/Dockerfile diff --git a/.specify/feature.json b/.specify/feature.json index 42d75f2..79280c4 100644 --- a/.specify/feature.json +++ b/.specify/feature.json @@ -1 +1 @@ -{"feature_directory":"specs/001-market-first-wedge"} +{"feature_directory":"specs/002-market-web"} diff --git a/web/Dockerfile b/web/Dockerfile new file mode 100644 index 0000000..c3b9261 --- /dev/null +++ b/web/Dockerfile @@ -0,0 +1,24 @@ +# m2-market-web — FastAPI proxy + React SPA in one container (specs/002-market-web). +# Build context: repo root (needs web/backend + web/frontend). + +FROM node:20-slim AS frontend +WORKDIR /fe +COPY web/frontend/package.json web/frontend/package-lock.json* ./ +RUN npm install --no-audit --no-fund +COPY web/frontend/ ./ +RUN npm run build + +FROM python:3.12-slim +WORKDIR /app +COPY web/backend/pyproject.toml ./backend/pyproject.toml +COPY web/backend/src ./backend/src +RUN pip install --no-cache-dir ./backend +COPY --from=frontend /fe/dist ./frontend/dist +ENV M2MW_STATIC_DIR=/app/frontend/dist + +RUN groupadd --system web && useradd --system --gid web web +USER web +EXPOSE 8000 +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2)" || exit 1 +CMD ["uvicorn", "m2_market_web.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/web/backend/src/m2_market_web/main.py b/web/backend/src/m2_market_web/main.py index 4dcaa74..b43e296 100644 --- a/web/backend/src/m2_market_web/main.py +++ b/web/backend/src/m2_market_web/main.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os from pathlib import Path from fastapi import FastAPI, Request @@ -19,7 +20,14 @@ def create_app() -> FastAPI: def health() -> dict[str, str]: return {"status": "healthy"} - dist = Path(__file__).resolve().parents[3] / "frontend" / "dist" + # In the container the package is pip-installed (site-packages), so the + # repo-relative parents[3] fallback misses — M2MW_STATIC_DIR wins. + dist = Path( + os.environ.get( + "M2MW_STATIC_DIR", + Path(__file__).resolve().parents[3] / "frontend" / "dist", + ) + ) @app.get("/{path:path}", include_in_schema=False) def static_or_spa(path: str, request: Request):