# shopdb-flask single-site container. # # One image, one site. Per ADR-004, each adopting facility runs its own # stack with its own DB, secrets, and enabled-plugin list. This image # bundles all 13 catalog plugins (computers, employees, geenforce, # knowledgebase, machines, measuringtools, network, notifications, # printedparts, printers, slides, usb, warranty); a site installs + enables # the ones it wants with `flask plugin install ` (or, declaratively, # `flask plugin apply-profile `). Per ADR-013 a future lean # build stages only the chosen plugin directories into this image. # # The frontend is built in a first stage and its dist output is copied into # the final image so Flask can serve the SPA (register_frontend_routes in # shopdb/__init__.py resolves /frontend/dist). # # Build: # docker build -t shopdb-flask . # Run (with .env): # docker run --env-file .env -p 5001:5001 shopdb-flask # ---- Stage 1: build the Vue frontend ---- FROM node:20-slim AS frontendbuild WORKDIR /build # Copy only the manifests first so `npm ci` caches on dependency changes. COPY frontend/package*.json ./frontend/ RUN cd frontend && npm ci # The frontend keeps its repo-relative layout here, because npm prebuild runs # `node ../scripts/stage-frontend.mjs`, which stages each plugin's frontend/ into # the Vite tree and codegens routes.gen.js. That script resolves its repo root as # its own directory's parent, so it needs scripts/ and plugins/ as SIBLINGS of # frontend/. Building with frontend/ flattened to the stage root made `../scripts` # resolve to /scripts and the build failed on a missing module. COPY scripts/stage-frontend.mjs ./scripts/ COPY plugins/ ./plugins/ COPY frontend/ ./frontend/ RUN cd frontend && npm run build # Output lands in /build/frontend/dist (Vite default), copied into the final # stage below. # ---- Stage 2: Python application image ---- FROM python:3.14-slim AS base ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 WORKDIR /app RUN apt-get update \ && apt-get install -y --no-install-recommends \ build-essential \ default-libmysqlclient-dev \ pkg-config \ curl \ && rm -rf /var/lib/apt/lists/* COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt \ && pip install --no-cache-dir gunicorn COPY shopdb/ ./shopdb/ COPY plugins/ ./plugins/ COPY migrations/ ./migrations/ COPY scripts/ ./scripts/ COPY wsgi.py ./ # Built SPA from stage 1. Flask serves it via register_frontend_routes. COPY --from=frontendbuild /build/frontend/dist ./frontend/dist RUN useradd --create-home --shell /bin/bash shopdb \ && chown -R shopdb:shopdb /app USER shopdb EXPOSE 5001 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD curl --fail --silent http://localhost:5001/api/auth/login -X POST \ -H "Content-Type: application/json" -d '{}' \ | grep -q "VALIDATION_ERROR" || exit 1 CMD ["gunicorn", \ "--bind", "0.0.0.0:5001", \ "--workers", "4", \ "--timeout", "60", \ "--access-logfile", "-", \ "--error-logfile", "-", \ "wsgi:app"]