`npm run build` fires a `prebuild` hook that runs `node ../scripts/stage-frontend.mjs`, the script that copies each plugin's frontend into the Vite tree and codegens routes.gen.js. The Dockerfile's frontend stage copied `frontend/` alone and flattened it to the stage root, so `../scripts` resolved to `/scripts`, which does not exist. Every image build since the staging script landed has died there on `Cannot find module`. The stage now keeps the repo-relative layout - frontend/, scripts/ and plugins/ as siblings under /build - because the script resolves its repo root as its own directory's parent. The final stage copies from /build/frontend/dist to match. Verified with a real build, not by inspection: 16 plugin frontends staged, vite produced dist, and the finished image carries frontend/dist with 241 assets. This also unblocks SITE_PLUGINS lean image builds, which until now only scripts/build-site.sh could do.
93 lines
3.1 KiB
Docker
93 lines
3.1 KiB
Docker
# 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 <name>` (or, declaratively,
|
|
# `flask plugin apply-profile <profile.json>`). 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 <repo>/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"]
|