Skip to content

Commit d8ea798

Browse files
robodev-r2d2NewDev16a-klos
authored
fix: unify Poetry virtualenv to /opt/.venv and update sync and ignore paths in Tiltfile (#143)
Summary: This PR resolves Linux dev environment issues by standardizing the Poetry virtualenv location and ensuring dev dependencies are reliably installed when building with dev=1. Adjust tiltfile, so that tilt performs clean reloads, when changes are triggered. Changes: - Standardize Poetry virtualenv to /opt/.venv across services. - Set POETRY_VIRTUALENVS_CREATE=false and POETRY_VIRTUALENVS_IN_PROJECT=false to reuse the prebuilt venv. - Export VIRTUAL_ENV and prepend /opt/.venv/bin to PATH in both build and runtime stages, including for nonroot. - Add cache-busting tied to the dev build arg to force correct installation of dev dependencies. - Clean up redundant PATH exports and ensure /etc/environment reflects the unified venv path. - Adjust tiltfile sync and ignore during image build Scope: - services/admin-backend/Dockerfile - services/document-extractor/Dockerfile - services/mcp-server/Dockerfile - services/rag-backend/Dockerfile Fixes: #142 --------- Co-authored-by: Andreas Klos <[email protected]> Co-authored-by: Andreas Klos <[email protected]>
1 parent 259e22d commit d8ea798

File tree

5 files changed

+180
-37
lines changed

5 files changed

+180
-37
lines changed

Tiltfile

Lines changed: 110 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,26 @@ backend_debug = cfg.get("debug", False)
1313
core_library_context = "./libs"
1414

1515

16+
def libs_ignore_except(include_libs):
17+
"""Return a list of paths to ignore for docker builds under the project root.
18+
19+
include_libs: list of folder names under ./libs that should NOT be ignored.
20+
All other folders under ./libs will be ignored to avoid triggering rebuilds when
21+
unrelated libraries change.
22+
"""
23+
all_libs = [
24+
"admin-api-lib",
25+
"extractor-api-lib",
26+
"rag-core-api",
27+
"rag-core-lib",
28+
]
29+
ignore = []
30+
for lib in all_libs:
31+
if lib not in include_libs:
32+
ignore.append("libs/%s/" % lib)
33+
return ignore
34+
35+
1636
def create_linter_command(folder_name, name):
1737
# Use TEST=1 for libs Dockerfile, dev=1 for service Dockerfiles
1838
build_arg = "TEST=1" if folder_name == "./libs" else "dev=1"
@@ -145,9 +165,41 @@ local_resource(
145165
################################## build backend_rag image and do live update ##########################################
146166
########################################################################################################################
147167

168+
# Base ignore patterns applied to docker_builds that reference IGNORE_BASE.
169+
# These are files and directories that should not trigger rebuilds: build artifacts,
170+
# Python caches, test caches, virtualenvs, node_modules, and other OS/tooling files.
148171
IGNORE_BASE = [
172+
# project directories we don't want to include in build context
149173
"infrastructure/",
150174
"services/frontend/",
175+
# Python caches and bytecode
176+
"**/__pycache__/",
177+
"**/*.pyc",
178+
"**/*.pyo",
179+
"**/*.py[co]",
180+
".pytest_cache/",
181+
"**/.pytest_cache/",
182+
".mypy_cache/",
183+
# virtualenvs / local python envs
184+
".venv/",
185+
"venv/",
186+
"env/",
187+
# build artifacts
188+
"build/",
189+
"dist/",
190+
"*.egg-info/",
191+
# tooling caches
192+
"node_modules/",
193+
"**/node_modules/",
194+
"services/frontend/node_modules/",
195+
# OS / editor files
196+
".DS_Store",
197+
"*.swp",
198+
"*.swo",
199+
# pytest / test caches inside libs
200+
"**/.pytest_cache/",
201+
# nix / package manager locks and temp files (optional)
202+
"**/.cache/",
151203
]
152204

153205
# NOTE: full image names should match the one in the helm chart values.yaml!
@@ -168,7 +220,7 @@ docker_build(
168220
sync(core_library_context+"/rag-core-lib", "/app/libs/rag-core-lib"),
169221
],
170222
dockerfile=backend_context + "/Dockerfile",
171-
ignore=IGNORE_BASE
223+
ignore=IGNORE_BASE + libs_ignore_except(["rag-core-api", "rag-core-lib"])
172224
)
173225

174226
# Add linter trigger
@@ -208,7 +260,7 @@ docker_build(
208260
sync(mcp_context, "/app/services/mcp-server"),
209261
],
210262
dockerfile=mcp_context + "/Dockerfile",
211-
ignore=IGNORE_BASE,
263+
ignore=IGNORE_BASE + libs_ignore_except([]),
212264
)
213265

214266
# Add linter trigger
@@ -239,11 +291,13 @@ docker_build(
239291
},
240292
live_update=[
241293
sync(admin_backend_context, "/app/services/admin-backend"),
242-
sync(core_library_context + "/rag-core-lib", "/app/libs/rag-core-lib"),
243-
sync(core_library_context + "/admin-api-lib", "/app/libs/admin-api-lib"),
294+
sync(core_library_context + "/rag-core-lib", "/app/libs/rag-core-lib"),
295+
sync(core_library_context + "/admin-api-lib", "/app/libs/admin-api-lib"),
244296
],
245297
dockerfile=admin_backend_context + "/Dockerfile",
246-
ignore=IGNORE_BASE,
298+
# Ignore rag-core-api for this build context so changes in that library
299+
# don't trigger admin-backend rebuilds (admin-backend doesn't COPY rag-core-api)
300+
ignore=IGNORE_BASE + libs_ignore_except(["rag-core-lib", "admin-api-lib"]),
247301
)
248302

249303
# Add linter trigger
@@ -287,7 +341,7 @@ docker_build(
287341
sync(core_library_context +"/extractor-api-lib", "/app/libs/extractor-api-lib"),
288342
],
289343
dockerfile=extractor_context + "/Dockerfile",
290-
ignore=IGNORE_BASE,
344+
ignore=IGNORE_BASE + libs_ignore_except(["extractor-api-lib"]),
291345
)
292346

293347
# Add linter trigger
@@ -329,10 +383,35 @@ docker_build(
329383
sync("./services/frontend/dist/libs", "/usr/share/nginx/html/libs"),
330384
],
331385
ignore=[
386+
# exclude non-frontend areas
387+
"libs/",
388+
"services/admin-backend/",
389+
"services/rag-backend/",
390+
"services/document-extractor/",
391+
"services/mcp-server/",
332392
"infrastructure/",
393+
"tools/",
394+
"docs/",
395+
".github/",
396+
".vscode/",
397+
# caches/artifacts
398+
"**/__pycache__/",
399+
"**/.pytest_cache/",
400+
".mypy_cache/",
401+
".venv/",
402+
"venv/",
403+
"env/",
404+
"build/",
405+
"dist/",
406+
"*.egg-info/",
407+
# frontend-specific caches
333408
"services/frontend/.nx/",
334409
"services/frontend/tmp/",
335410
"services/frontend/node_modules/",
411+
# OS/editor files
412+
".DS_Store",
413+
"*.swp",
414+
"*.swo",
336415
],
337416
)
338417

@@ -352,10 +431,35 @@ docker_build(
352431
sync("./services/frontend/dist/libs", "/usr/share/nginx/html/libs"),
353432
],
354433
ignore=[
434+
# exclude non-frontend areas
435+
"libs/",
436+
"services/admin-backend/",
437+
"services/rag-backend/",
438+
"services/document-extractor/",
439+
"services/mcp-server/",
355440
"infrastructure/",
441+
"tools/",
442+
"docs/",
443+
".github/",
444+
".vscode/",
445+
# caches/artifacts
446+
"**/__pycache__/",
447+
"**/.pytest_cache/",
448+
".mypy_cache/",
449+
".venv/",
450+
"venv/",
451+
"env/",
452+
"build/",
453+
"dist/",
454+
"*.egg-info/",
455+
# frontend-specific caches
356456
"services/frontend/.nx/",
357457
"services/frontend/tmp/",
358458
"services/frontend/node_modules/",
459+
# OS/editor files
460+
".DS_Store",
461+
"*.swp",
462+
"*.swo",
359463
],
360464
)
361465

services/admin-backend/Dockerfile

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
FROM --platform=linux/amd64 python:3.13-bookworm AS build
22

33
ARG dev=0
4-
ENV POETRY_VIRTUALENVS_PATH=/app/services/admin-backend/.venv
5-
ENV POETRY_VERSION=2.1.3
4+
ENV POETRY_VIRTUALENVS_PATH=/opt/.venv \
5+
POETRY_VIRTUALENVS_CREATE=false \
6+
POETRY_VIRTUALENVS_IN_PROJECT=false \
7+
VIRTUAL_ENV=/opt/.venv \
8+
PATH=/opt/.venv/bin:$PATH \
9+
POETRY_VERSION=2.1.3
610

711
RUN DEBIAN_FRONTEND=noninteractive apt-get update \
812
&& 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 ./
1923
RUN mkdir log && chmod 700 log
2024
RUN touch /app/services/admin-backend/log/logfile.log && chmod 600 /app/services/admin-backend/log/logfile.log
2125

22-
RUN poetry config virtualenvs.create false && \
26+
# Bust cache when dev flag changes
27+
RUN echo "CACHE_BUST_DEV=$dev" && poetry config virtualenvs.create false && \
2328
if [ "$dev" = "1" ]; then \
2429
poetry install --no-interaction --no-ansi --no-root --with dev; \
2530
else \
@@ -31,7 +36,7 @@ ARG dev=0
3136

3237
RUN adduser --disabled-password --gecos "" --uid 10001 nonroot
3338

34-
ENV POETRY_VIRTUALENVS_PATH=/app/services/admin-backend/.venv
39+
ENV POETRY_VIRTUALENVS_PATH=/opt/.venv
3540
COPY --from=build --chown=nonroot:nonroot ${POETRY_VIRTUALENVS_PATH} ${POETRY_VIRTUALENVS_PATH}
3641
COPY --from=build /usr/local/bin/ /usr/local/bin/
3742
COPY --from=build /usr/bin/make /usr/bin/make
@@ -44,6 +49,12 @@ WORKDIR /app/services/admin-backend
4449

4550
COPY --chown=nonroot:nonroot services/admin-backend .
4651

52+
# Ensure poetry reuses existing virtualenv when running as nonroot
53+
ENV POETRY_VIRTUALENVS_CREATE=false \
54+
POETRY_VIRTUALENVS_IN_PROJECT=false \
55+
VIRTUAL_ENV=/opt/.venv \
56+
PATH=/opt/.venv/bin:$PATH
57+
4758
# cleanup
4859
RUN apt-get clean autoclean
4960
RUN apt-get autoremove --yes
@@ -52,13 +63,12 @@ RUN if [ "$dev" = "0" ]; then \
5263
while read -r shell; do rm -f "$shell"; done < /etc/shells; \
5364
rm -rf /var/lib/{apt,dpkg,cache,log}/ \
5465
else \
55-
echo "POETRY_VIRTUALENVS_PATH=/app/services/admin-backend/.venv" >> /etc/environment;\
56-
export POETRY_VIRTUALENVS_PATH=/app/services/admin-backend/.venv;\
66+
echo "POETRY_VIRTUALENVS_PATH=/opt/.venv" >> /etc/environment;\
67+
export POETRY_VIRTUALENVS_PATH=/opt/.venv;\
5768
export PATH="${POETRY_VIRTUALENVS_PATH}/bin:$PATH";\
5869
fi
5970

6071

6172
USER nonroot
6273
COPY --from=build --chown=nonroot:nonroot /app/services/admin-backend/log /app/services/admin-backend/log
6374

64-
ENV PATH="${POETRY_VIRTUALENVS_PATH}/bin:${PATH}"

services/document-extractor/Dockerfile

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
FROM --platform=linux/amd64 python:3.13-bookworm AS build
22

33
ARG dev=0
4-
ENV POETRY_VIRTUALENVS_PATH=/app/services/document-extractor/.venv
5-
ENV POETRY_VERSION=2.1.3
4+
ENV POETRY_VIRTUALENVS_PATH=/opt/.venv \
5+
POETRY_VIRTUALENVS_CREATE=false \
6+
POETRY_VIRTUALENVS_IN_PROJECT=false \
7+
VIRTUAL_ENV=/opt/.venv \
8+
PATH=/opt/.venv/bin:$PATH \
9+
POETRY_VERSION=2.1.3
610

711
RUN DEBIAN_FRONTEND=noninteractive apt-get update \
812
&& 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
2327
RUN mkdir log && chmod 700 log
2428
RUN touch /app/services/document-extractor/log/logfile.log && chmod 600 /app/services/document-extractor/log/logfile.log
2529

26-
RUN poetry config virtualenvs.create false &&\
30+
# Cache bust with dev arg so dev dependencies install when dev=1
31+
RUN echo "CACHE_BUST_DEV=$dev" && poetry config virtualenvs.create false &&\
2732
if [ "$dev" = "1" ]; then \
2833
poetry install --no-interaction --no-ansi --no-root --with dev; \
2934
else \
@@ -35,7 +40,7 @@ ARG dev=0
3540

3641
RUN adduser --disabled-password --gecos "" --uid 10001 nonroot
3742

38-
ENV POETRY_VIRTUALENVS_PATH=/app/services/document-extractor/.venv
43+
ENV POETRY_VIRTUALENVS_PATH=/opt/.venv
3944
COPY --from=build --chown=nonroot:nonroot ${POETRY_VIRTUALENVS_PATH} ${POETRY_VIRTUALENVS_PATH}
4045
COPY --from=build /usr/local/bin/ /usr/local/bin/
4146
COPY --from=build /usr/bin/ /usr/bin/
@@ -50,6 +55,12 @@ WORKDIR /app/services/document-extractor
5055
COPY --chown=nonroot:nonroot services/document-extractor .
5156

5257

58+
# Ensure poetry reuses existing virtualenv when running as nonroot
59+
ENV POETRY_VIRTUALENVS_CREATE=false \
60+
POETRY_VIRTUALENVS_IN_PROJECT=false \
61+
VIRTUAL_ENV=/opt/.venv \
62+
PATH=/opt/.venv/bin:$PATH
63+
5364
# cleanup
5465
RUN apt-get clean autoclean
5566
RUN apt-get autoremove --yes
@@ -58,13 +69,11 @@ RUN if [ "$dev" = "0" ]; then \
5869
while read -r shell; do rm -f "$shell"; done < /etc/shells; \
5970
rm -rf /var/lib/{apt,dpkg,cache,log}/ \
6071
else \
61-
echo "POETRY_VIRTUALENVS_PATH=/app/services/document-extractor/.venv" >> /etc/environment;\
62-
export POETRY_VIRTUALENVS_PATH=/app/services/document-extractor/.venv;\
72+
echo "POETRY_VIRTUALENVS_PATH=/opt/.venv" >> /etc/environment;\
73+
export POETRY_VIRTUALENVS_PATH=/opt/.venv;\
6374
export PATH="${POETRY_VIRTUALENVS_PATH}/bin:$PATH";\
6475
fi
6576

6677

6778
USER nonroot
6879
COPY --from=build --chown=nonroot:nonroot /app/services/document-extractor/log /app/services/document-extractor/log
69-
70-
ENV PATH="${POETRY_VIRTUALENVS_PATH}/bin:${PATH}"

services/mcp-server/Dockerfile

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
FROM --platform=linux/amd64 python:3.13.7-bookworm AS build
22

33
ARG dev=0
4-
ENV POETRY_VIRTUALENVS_PATH=/app/services/mcp-server/.venv
5-
ENV POETRY_VERSION=2.1.3
4+
ENV POETRY_VIRTUALENVS_PATH=/opt/.venv \
5+
POETRY_VIRTUALENVS_CREATE=false \
6+
POETRY_VIRTUALENVS_IN_PROJECT=false \
7+
VIRTUAL_ENV=/opt/.venv \
8+
PATH=/opt/.venv/bin:$PATH \
9+
POETRY_VERSION=2.1.3
610

711
WORKDIR /app
812

@@ -19,7 +23,8 @@ COPY services/mcp-server/pyproject.toml services/mcp-server/poetry.lock ./
1923
RUN mkdir log && chmod 700 log
2024
RUN touch /app/services/mcp-server/log/logfile.log && chmod 600 /app/services/mcp-server/log/logfile.log
2125

22-
RUN poetry config virtualenvs.create false &&\
26+
# Cache bust with dev arg so dev dependencies install when dev=1
27+
RUN echo "CACHE_BUST_DEV=$dev" && poetry config virtualenvs.create false &&\
2328
if [ "$dev" = "1" ]; then \
2429
poetry install --no-interaction --no-ansi --no-root --with dev; \
2530
else \
@@ -31,7 +36,8 @@ ARG dev=0
3136

3237
RUN adduser --disabled-password --gecos "" --uid 10001 nonroot
3338

34-
ENV POETRY_VIRTUALENVS_PATH=/app/services/mcp-server/.venv
39+
ENV POETRY_VIRTUALENVS_PATH=/opt/.venv
40+
3541
COPY --from=build --chown=nonroot:nonroot ${POETRY_VIRTUALENVS_PATH} ${POETRY_VIRTUALENVS_PATH}
3642
COPY --from=build /usr/local/bin/ /usr/local/bin/
3743
COPY --from=build /usr/bin/make /usr/bin/make
@@ -42,6 +48,13 @@ COPY --chown=nonroot:nonroot services/mcp-server/src ./src
4248
COPY --chown=nonroot:nonroot services/mcp-server/tests ./tests
4349
COPY --chown=nonroot:nonroot services/mcp-server/pyproject.toml services/mcp-server/poetry.lock ./
4450
COPY --chown=nonroot:nonroot services/mcp-server/Makefile ./
51+
52+
# Ensure poetry reuses existing virtualenv when running as nonroot
53+
ENV POETRY_VIRTUALENVS_CREATE=false \
54+
POETRY_VIRTUALENVS_IN_PROJECT=false \
55+
VIRTUAL_ENV=/opt/.venv \
56+
PATH=/opt/.venv/bin:$PATH
57+
4558
# cleanup
4659
RUN apt-get clean autoclean
4760
RUN apt-get autoremove --yes
@@ -50,15 +63,13 @@ RUN if [ "$dev" = "0" ]; then \
5063
while read -r shell; do rm -f "$shell"; done < /etc/shells; \
5164
rm -rf /var/lib/{apt,dpkg,cache,log}/ \
5265
else \
53-
echo "POETRY_VIRTUALENVS_PATH=/app/services/mcp-server/.venv" >> /etc/environment;\
54-
export POETRY_VIRTUALENVS_PATH=/app/services/mcp-server/.venv;\
66+
echo "POETRY_VIRTUALENVS_PATH=/opt/.venv" >> /etc/environment;\
67+
export POETRY_VIRTUALENVS_PATH=/opt/.venv;\
5568
export PATH="${POETRY_VIRTUALENVS_PATH}/bin:$PATH";\
5669
fi
5770

5871

5972
USER nonroot
6073
COPY --from=build --chown=nonroot:nonroot /app/services/mcp-server/log /app/services/mcp-server/log
6174

62-
ENV PATH="${POETRY_VIRTUALENVS_PATH}/bin:${PATH}"
63-
6475
CMD [ "poetry", "run", "python", "src/main.py" ]

0 commit comments

Comments
 (0)