FROM node:24-bookworm-slim AS web-build WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY tsconfig.json vite.config.ts index.html ./ COPY src ./src COPY public ./public RUN npm run build FROM python:3.12-slim-bookworm AS engine-build RUN apt-get update && apt-get install -y --no-install-recommends git cmake build-essential ca-certificates && rm -rf /var/lib/apt/lists/* RUN git clone --depth 1 --branch v2.3.5 https://github.com/OpenWaterAnalytics/EPANET.git /engine RUN cmake -S /engine -B /engine/build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF && cmake --build /engine/build --target epanet2 -j 2 FROM python:3.12-slim-bookworm AS api WORKDIR /app COPY backend/requirements-api.txt /tmp/requirements-api.txt RUN pip install --no-cache-dir -r /tmp/requirements-api.txt COPY --from=engine-build /engine/build/lib/libepanet2.so /app/64bit/libepanet2.so COPY backend/__init__.py backend/main.py backend/epanet.py backend/columnar.py backend/batch.py backend/jobs.py backend/gisio.py backend/gisops.py backend/dem.py ./backend/ COPY public/Net1.inp /app/smoke.inp RUN python -c "from backend.main import run_epanet; import tempfile; from pathlib import Path; t=tempfile.TemporaryDirectory(); p=Path(t.name); r=run_epanet('/app/smoke.inp',str(p/'test.rpt'),str(p/'test.bin')); assert r['nodes'] and r['links']" RUN useradd --system --uid 10001 spipe && mkdir -p /app/data/dem && chown -R spipe /app/data ENV SPIPE_DEM_DIR=/app/data/dem USER spipe CMD ["python", "-m", "uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000"] FROM nginx:stable-bookworm AS web COPY --from=web-build /app/dist /usr/share/nginx/html COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf