From b8f4aa6f8fa44f5aafc431ac539b2622dc090b14 Mon Sep 17 00:00:00 2001 From: Andreas Klos Date: Wed, 15 Oct 2025 11:02:28 +0200 Subject: [PATCH 1/3] fix: update Dockerfiles to use consistent virtualenv configuration and cache busting for dev dependencies --- services/admin-backend/Dockerfile | 24 ++++++++++++++++------- services/document-extractor/Dockerfile | 25 ++++++++++++++++-------- services/mcp-server/Dockerfile | 27 ++++++++++++++++++-------- services/rag-backend/Dockerfile | 25 ++++++++++++++++-------- 4 files changed, 70 insertions(+), 31 deletions(-) diff --git a/services/admin-backend/Dockerfile b/services/admin-backend/Dockerfile index 120944d7..6a133c4f 100644 --- a/services/admin-backend/Dockerfile +++ b/services/admin-backend/Dockerfile @@ -1,8 +1,12 @@ FROM --platform=linux/amd64 python:3.13-bookworm AS build ARG dev=0 -ENV POETRY_VIRTUALENVS_PATH=/app/services/admin-backend/.venv -ENV POETRY_VERSION=2.1.3 +ENV POETRY_VIRTUALENVS_PATH=/opt/.venv \ + POETRY_VIRTUALENVS_CREATE=false \ + POETRY_VIRTUALENVS_IN_PROJECT=false \ + VIRTUAL_ENV=/opt/.venv \ + PATH=/opt/.venv/bin:$PATH \ + POETRY_VERSION=2.1.3 RUN DEBIAN_FRONTEND=noninteractive apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential --no-install-recommends make && \ @@ -19,7 +23,8 @@ COPY services/admin-backend/pyproject.toml services/admin-backend/poetry.lock ./ RUN mkdir log && chmod 700 log RUN touch /app/services/admin-backend/log/logfile.log && chmod 600 /app/services/admin-backend/log/logfile.log -RUN poetry config virtualenvs.create false && \ +# Bust cache when dev flag changes +RUN echo "CACHE_BUST_DEV=$dev" && poetry config virtualenvs.create false && \ if [ "$dev" = "1" ]; then \ poetry install --no-interaction --no-ansi --no-root --with dev; \ else \ @@ -31,7 +36,7 @@ ARG dev=0 RUN adduser --disabled-password --gecos "" --uid 10001 nonroot -ENV POETRY_VIRTUALENVS_PATH=/app/services/admin-backend/.venv +ENV POETRY_VIRTUALENVS_PATH=/opt/.venv COPY --from=build --chown=nonroot:nonroot ${POETRY_VIRTUALENVS_PATH} ${POETRY_VIRTUALENVS_PATH} COPY --from=build /usr/local/bin/ /usr/local/bin/ COPY --from=build /usr/bin/make /usr/bin/make @@ -44,6 +49,12 @@ WORKDIR /app/services/admin-backend COPY --chown=nonroot:nonroot services/admin-backend . +# Ensure poetry reuses existing virtualenv when running as nonroot +ENV POETRY_VIRTUALENVS_CREATE=false \ + POETRY_VIRTUALENVS_IN_PROJECT=false \ + VIRTUAL_ENV=/opt/.venv \ + PATH=/opt/.venv/bin:$PATH + # cleanup RUN apt-get clean autoclean RUN apt-get autoremove --yes @@ -52,8 +63,8 @@ RUN if [ "$dev" = "0" ]; then \ while read -r shell; do rm -f "$shell"; done < /etc/shells; \ rm -rf /var/lib/{apt,dpkg,cache,log}/ \ else \ - echo "POETRY_VIRTUALENVS_PATH=/app/services/admin-backend/.venv" >> /etc/environment;\ - export POETRY_VIRTUALENVS_PATH=/app/services/admin-backend/.venv;\ + echo "POETRY_VIRTUALENVS_PATH=/opt/.venv" >> /etc/environment;\ + export POETRY_VIRTUALENVS_PATH=/opt/.venv;\ export PATH="${POETRY_VIRTUALENVS_PATH}/bin:$PATH";\ fi @@ -61,4 +72,3 @@ RUN if [ "$dev" = "0" ]; then \ USER nonroot COPY --from=build --chown=nonroot:nonroot /app/services/admin-backend/log /app/services/admin-backend/log -ENV PATH="${POETRY_VIRTUALENVS_PATH}/bin:${PATH}" diff --git a/services/document-extractor/Dockerfile b/services/document-extractor/Dockerfile index 5d02dc8f..fcfe14ef 100644 --- a/services/document-extractor/Dockerfile +++ b/services/document-extractor/Dockerfile @@ -1,8 +1,12 @@ FROM --platform=linux/amd64 python:3.13-bookworm AS build ARG dev=0 -ENV POETRY_VIRTUALENVS_PATH=/app/services/document-extractor/.venv -ENV POETRY_VERSION=2.1.3 +ENV POETRY_VIRTUALENVS_PATH=/opt/.venv \ + POETRY_VIRTUALENVS_CREATE=false \ + POETRY_VIRTUALENVS_IN_PROJECT=false \ + VIRTUAL_ENV=/opt/.venv \ + PATH=/opt/.venv/bin:$PATH \ + POETRY_VERSION=2.1.3 RUN DEBIAN_FRONTEND=noninteractive apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential --no-install-recommends make \ @@ -23,7 +27,8 @@ COPY services/document-extractor/pyproject.toml services/document-extractor/poet RUN mkdir log && chmod 700 log RUN touch /app/services/document-extractor/log/logfile.log && chmod 600 /app/services/document-extractor/log/logfile.log -RUN poetry config virtualenvs.create false &&\ +# Cache bust with dev arg so dev dependencies install when dev=1 +RUN echo "CACHE_BUST_DEV=$dev" && poetry config virtualenvs.create false &&\ if [ "$dev" = "1" ]; then \ poetry install --no-interaction --no-ansi --no-root --with dev; \ else \ @@ -35,7 +40,7 @@ ARG dev=0 RUN adduser --disabled-password --gecos "" --uid 10001 nonroot -ENV POETRY_VIRTUALENVS_PATH=/app/services/document-extractor/.venv +ENV POETRY_VIRTUALENVS_PATH=/opt/.venv COPY --from=build --chown=nonroot:nonroot ${POETRY_VIRTUALENVS_PATH} ${POETRY_VIRTUALENVS_PATH} COPY --from=build /usr/local/bin/ /usr/local/bin/ COPY --from=build /usr/bin/ /usr/bin/ @@ -50,6 +55,12 @@ WORKDIR /app/services/document-extractor COPY --chown=nonroot:nonroot services/document-extractor . +# Ensure poetry reuses existing virtualenv when running as nonroot +ENV POETRY_VIRTUALENVS_CREATE=false \ + POETRY_VIRTUALENVS_IN_PROJECT=false \ + VIRTUAL_ENV=/opt/.venv \ + PATH=/opt/.venv/bin:$PATH + # cleanup RUN apt-get clean autoclean RUN apt-get autoremove --yes @@ -58,13 +69,11 @@ RUN if [ "$dev" = "0" ]; then \ while read -r shell; do rm -f "$shell"; done < /etc/shells; \ rm -rf /var/lib/{apt,dpkg,cache,log}/ \ else \ - echo "POETRY_VIRTUALENVS_PATH=/app/services/document-extractor/.venv" >> /etc/environment;\ - export POETRY_VIRTUALENVS_PATH=/app/services/document-extractor/.venv;\ + echo "POETRY_VIRTUALENVS_PATH=/opt/.venv" >> /etc/environment;\ + export POETRY_VIRTUALENVS_PATH=/opt/.venv;\ export PATH="${POETRY_VIRTUALENVS_PATH}/bin:$PATH";\ fi USER nonroot COPY --from=build --chown=nonroot:nonroot /app/services/document-extractor/log /app/services/document-extractor/log - -ENV PATH="${POETRY_VIRTUALENVS_PATH}/bin:${PATH}" diff --git a/services/mcp-server/Dockerfile b/services/mcp-server/Dockerfile index cc99220c..59cc17ff 100644 --- a/services/mcp-server/Dockerfile +++ b/services/mcp-server/Dockerfile @@ -1,8 +1,12 @@ FROM --platform=linux/amd64 python:3.13.7-bookworm AS build ARG dev=0 -ENV POETRY_VIRTUALENVS_PATH=/app/services/mcp-server/.venv -ENV POETRY_VERSION=2.1.3 +ENV POETRY_VIRTUALENVS_PATH=/opt/.venv \ + POETRY_VIRTUALENVS_CREATE=false \ + POETRY_VIRTUALENVS_IN_PROJECT=false \ + VIRTUAL_ENV=/opt/.venv \ + PATH=/opt/.venv/bin:$PATH \ + POETRY_VERSION=2.1.3 WORKDIR /app @@ -19,7 +23,8 @@ COPY services/mcp-server/pyproject.toml services/mcp-server/poetry.lock ./ RUN mkdir log && chmod 700 log RUN touch /app/services/mcp-server/log/logfile.log && chmod 600 /app/services/mcp-server/log/logfile.log -RUN poetry config virtualenvs.create false &&\ +# Cache bust with dev arg so dev dependencies install when dev=1 +RUN echo "CACHE_BUST_DEV=$dev" && poetry config virtualenvs.create false &&\ if [ "$dev" = "1" ]; then \ poetry install --no-interaction --no-ansi --no-root --with dev; \ else \ @@ -31,7 +36,8 @@ ARG dev=0 RUN adduser --disabled-password --gecos "" --uid 10001 nonroot -ENV POETRY_VIRTUALENVS_PATH=/app/services/mcp-server/.venv +ENV POETRY_VIRTUALENVS_PATH=/opt/.venv + COPY --from=build --chown=nonroot:nonroot ${POETRY_VIRTUALENVS_PATH} ${POETRY_VIRTUALENVS_PATH} COPY --from=build /usr/local/bin/ /usr/local/bin/ COPY --from=build /usr/bin/make /usr/bin/make @@ -42,6 +48,13 @@ COPY --chown=nonroot:nonroot services/mcp-server/src ./src COPY --chown=nonroot:nonroot services/mcp-server/tests ./tests COPY --chown=nonroot:nonroot services/mcp-server/pyproject.toml services/mcp-server/poetry.lock ./ COPY --chown=nonroot:nonroot services/mcp-server/Makefile ./ + +# Ensure poetry reuses existing virtualenv when running as nonroot +ENV POETRY_VIRTUALENVS_CREATE=false \ + POETRY_VIRTUALENVS_IN_PROJECT=false \ + VIRTUAL_ENV=/opt/.venv \ + PATH=/opt/.venv/bin:$PATH + # cleanup RUN apt-get clean autoclean RUN apt-get autoremove --yes @@ -50,8 +63,8 @@ RUN if [ "$dev" = "0" ]; then \ while read -r shell; do rm -f "$shell"; done < /etc/shells; \ rm -rf /var/lib/{apt,dpkg,cache,log}/ \ else \ - echo "POETRY_VIRTUALENVS_PATH=/app/services/mcp-server/.venv" >> /etc/environment;\ - export POETRY_VIRTUALENVS_PATH=/app/services/mcp-server/.venv;\ + echo "POETRY_VIRTUALENVS_PATH=/opt/.venv" >> /etc/environment;\ + export POETRY_VIRTUALENVS_PATH=/opt/.venv;\ export PATH="${POETRY_VIRTUALENVS_PATH}/bin:$PATH";\ fi @@ -59,6 +72,4 @@ RUN if [ "$dev" = "0" ]; then \ USER nonroot COPY --from=build --chown=nonroot:nonroot /app/services/mcp-server/log /app/services/mcp-server/log -ENV PATH="${POETRY_VIRTUALENVS_PATH}/bin:${PATH}" - CMD [ "poetry", "run", "python", "src/main.py" ] diff --git a/services/rag-backend/Dockerfile b/services/rag-backend/Dockerfile index ca17cdd4..45732809 100644 --- a/services/rag-backend/Dockerfile +++ b/services/rag-backend/Dockerfile @@ -1,8 +1,12 @@ FROM --platform=linux/amd64 python:3.13-bookworm AS build ARG dev=0 -ENV POETRY_VIRTUALENVS_PATH=/app/services/rag-backend/.venv -ENV POETRY_VERSION=2.1.3 +ENV POETRY_VIRTUALENVS_PATH=/opt/.venv \ + POETRY_VIRTUALENVS_CREATE=false \ + POETRY_VIRTUALENVS_IN_PROJECT=false \ + VIRTUAL_ENV=/opt/.venv \ + PATH=/opt/.venv/bin:$PATH \ + POETRY_VERSION=2.1.3 WORKDIR /app @@ -21,7 +25,8 @@ COPY services/rag-backend/pyproject.toml services/rag-backend/poetry.lock ./ RUN mkdir log && chmod 700 log RUN touch /app/services/rag-backend/log/logfile.log && chmod 600 /app/services/rag-backend/log/logfile.log -RUN poetry config virtualenvs.create false &&\ +# Cache bust with dev arg so dev dependencies install when dev=1 +RUN echo "CACHE_BUST_DEV=$dev" && poetry config virtualenvs.create false &&\ if [ "$dev" = "1" ]; then \ poetry install --no-interaction --no-ansi --no-root --with dev; \ else \ @@ -33,7 +38,7 @@ ARG dev=0 RUN adduser --disabled-password --gecos "" --uid 10001 nonroot -ENV POETRY_VIRTUALENVS_PATH=/app/services/rag-backend/.venv +ENV POETRY_VIRTUALENVS_PATH=/opt/.venv COPY --from=build --chown=nonroot:nonroot ${POETRY_VIRTUALENVS_PATH} ${POETRY_VIRTUALENVS_PATH} COPY --from=build /usr/local/bin/ /usr/local/bin/ COPY --from=build /usr/bin/make /usr/bin/make @@ -47,6 +52,12 @@ WORKDIR /app/services/rag-backend COPY --chown=nonroot:nonroot services/rag-backend . +# Ensure poetry reuses existing virtualenv when running as nonroot +ENV POETRY_VIRTUALENVS_CREATE=false \ + POETRY_VIRTUALENVS_IN_PROJECT=false \ + VIRTUAL_ENV=/opt/.venv \ + PATH=/opt/.venv/bin:$PATH + # cleanup RUN apt-get clean autoclean RUN apt-get autoremove --yes @@ -55,13 +66,11 @@ RUN if [ "$dev" = "0" ]; then \ while read -r shell; do rm -f "$shell"; done < /etc/shells; \ rm -rf /var/lib/{apt,dpkg,cache,log}/ \ else \ - echo "POETRY_VIRTUALENVS_PATH=/app/services/rag-backend/.venv" >> /etc/environment;\ - export POETRY_VIRTUALENVS_PATH=/app/services/rag-backend/.venv;\ + echo "POETRY_VIRTUALENVS_PATH=/opt/.venv" >> /etc/environment;\ + export POETRY_VIRTUALENVS_PATH=/opt/.venv;\ export PATH="${POETRY_VIRTUALENVS_PATH}/bin:$PATH";\ fi USER nonroot COPY --from=build --chown=nonroot:nonroot /app/services/rag-backend/log /app/services/rag-backend/log - -ENV PATH="${POETRY_VIRTUALENVS_PATH}/bin:${PATH}" From 51bc7408a3eaf2eb5746849cb128eff2537e39f5 Mon Sep 17 00:00:00 2001 From: Andreas Klos Date: Wed, 15 Oct 2025 20:54:05 +0200 Subject: [PATCH 2/3] feat: add GitHub Copilot instructions for monorepo conventions and best practices --- .github/copilot-instructions.md | 177 ++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..3bcbfdc3 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,177 @@ +# .github/copilot-instructions.md + +> **Scope:** This file tailors GitHub Copilot (Chat/Code/Review) to this monorepo. It encodes *non-negotiable* conventions so Copilot generates code that builds, tests, and deploys with our Tilt/Helm setup. + +--- + +## 1) Repository at a glance + +* **Monorepo:** Python services + Vue 3/TypeScript frontend +* **Services:** `services/rag-backend`, `services/admin-backend`, `services/document-extractor`, `services/mcp-server`, `services/frontend` +* **Libs:** Reusable APIs/logic in `libs/*-api` and `libs/*-lib` consumed by services +* **Infra:** `infrastructure/rag` (Helm chart, K3d/Tilt config, Terraform). Local orchestration via **Tilt**; env loaded from `.env`. +* **Vector DB & extras:** Qdrant, KeyDB/Redis, Langfuse (observability). LLM provider via OpenAI-compatible APIs (STACKIT/ollama). + +**Rule of thumb:** *Business logic lives in **libs**; services are thin assemblies (routing/DI/bootstrap only).* + +--- + +## 2) Languages & toolchain + +* **Python:** 3.13, **FastAPI**, **Pydantic v2**, **Poetry** +* **Formatting/Linting:** `black` (line length **120**), `isort` (profile `black`), `flake8` (plugins configured in repo) +* **Testing:** `pytest` (+ coverage), prefer `pytest-asyncio` for async +* **Frontend:** Vue 3, TypeScript, Vite, Nx, Tailwind, Pinia, Vue I18n; tests via Vitest + Cypress; lint via ESLint + +--- + +## 3) Copilot behavior (important) + +* **Follow this file strictly.** Do not invent secrets/URLs/paths. Use placeholders/TODOs. +* **Prefer libs over services:** New endpoints and business logic go to `libs/*-api` & `libs/*-lib`; services only import/wire. +* **Always emit tests** with new Python code (1 happy + 1 edge case). +* **Ask via comments if uncertain**: Add a short `# Assumptions:` block at top of the diff. + +--- + +## 4) Python conventions + +* **Typing:** Use modern typing (`list[str]`, `dict[str, Any]`), avoid `Any` unless necessary. Pydantic v2 validators (`field_validator`, `model_validator`). +* **Imports:** **Absolute imports only** (no `from .x import y`). Group per isort. No wildcard imports. +* **FastAPI:** + + * Endpoints are **thin**; DI via `Depends`/container; no business logic in routers. + * Every route sets `response_model=...`; sanitize outputs. + * Centralize exception mapping; do not leak stack traces in prod. + * Prefer **async** endpoints for I/O-bound paths. +* **HTTP client:** Use `httpx.AsyncClient` with timeouts/retries. **Never** use `requests` in async code. +* **Config:** Use environment variables via `pydantic-settings` (document required vars; load `.env` in dev tooling only). Do **not** hardcode secrets. +* **Logging:** Use `logging` (or `structlog` if present). Produce structured, contextual logs (`event`, `service`, `request_id`). Do not log PII. +* **Repositories/Adapters:** Implement external I/O (DBs, HTTP, object storage) behind repository interfaces. Services call repositories; routers call services. +* **File layout for new code:** + + * `libs//src//...` → APIs, services, repositories, schemas, utils + * `libs//tests/` → tests for the above + * `services//` → service bootstrap, DI container, runtime config + +**Flake8 style (align with repo):** max line length **120**; `max-complexity: 8`; `annotations-complexity: 3`; `ignore: E203, W503, E704`; **double quotes** for code & docstrings; docstrings **NumPy style**; absolute imports only. + +--- + +## 5) Testing + +* **pytest** with fixtures/mocks (`unittest.mock`, `pytest-mock`). +* Keep unit tests fast and isolated; integration tests go through defined interfaces. +* No real network in unit tests; for `httpx` use `respx` (or equivalent) to mock. +* Target **high coverage on core libs**; measure with coverage in CI. + +**pytest.ini (suggested):** + +```ini +[pytest] +addopts = -q --strict-markers --strict-config --maxfail=1 --cov=libs --cov-report=term-missing +markers = + slow: marks tests as slow (deselect with '-m "not slow"') +asyncio_mode = strict +``` + +--- + +## 6) Frontend quick rules + +* Use `