fix(docker): relocate app to /home/user/demo to preserve DeepISLES modules (#17)
Browse files* fix(docker): relocate app to /home/user/demo to preserve DeepISLES modules
Root cause: Our Dockerfile was copying src/ to /app/src/, which overwrote
DeepISLES's isles22_ensemble.py module in the base image.
Fix:
- Changed WORKDIR from /app to /home/user/demo
- Added DEEPISLES_PATH=/app env var for explicit path resolution
- Updated direct.py to check DEEPISLES_PATH first, then fallback paths
The isleschallenge/deepisles base image stores DeepISLES at:
/app/main.py
/app/src/isles22_ensemble.py
/app/weights/
Our app now lives at /home/user/demo without conflicts.
Fixes: DeepISLES modules not found on HF Spaces
See: docs/specs/09-bug-deepisles-not-installed-hf-spaces.md
* docs: update Dockerfile examples to use /home/user/demo path
Update both Dockerfile examples in 07-hf-spaces-deployment.md to reflect
the correct app location (/home/user/demo instead of /app) to prevent
overwriting DeepISLES modules in the base image.
Related: fix/deepisles-docker-path
* refactor: address CodeRabbit review feedback
- Add language specifier to code block in bug doc (markdownlint)
- Deduplicate paths in _get_deepisles_search_paths() when DEEPISLES_PATH=/app
|
@@ -4,6 +4,9 @@
|
|
| 4 |
#
|
| 5 |
# IMPORTANT: During Docker build, GPU is NOT available.
|
| 6 |
# All GPU operations happen at runtime only.
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
# NOTE: isleschallenge/deepisles only publishes 'latest' tag on Docker Hub.
|
| 9 |
# For reproducibility, consider using a SHA digest if available:
|
|
@@ -21,20 +24,21 @@ ENV PYTHONDONTWRITEBYTECODE=1
|
|
| 21 |
# Create user if not exists (DeepISLES image may already have a user)
|
| 22 |
RUN useradd -m -u 1000 user 2>/dev/null || true
|
| 23 |
|
| 24 |
-
#
|
| 25 |
-
|
|
|
|
| 26 |
|
| 27 |
# Copy requirements first for better layer caching
|
| 28 |
-
COPY --chown=1000:1000 requirements.txt /
|
| 29 |
|
| 30 |
# Install Python dependencies (extras only - DeepISLES image has PyTorch, nnUNet, etc.)
|
| 31 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 32 |
|
| 33 |
# Copy application source code and package files
|
| 34 |
-
COPY --chown=1000:1000 pyproject.toml /
|
| 35 |
-
COPY --chown=1000:1000 README.md /
|
| 36 |
-
COPY --chown=1000:1000 src/ /
|
| 37 |
-
COPY --chown=1000:1000 app.py /
|
| 38 |
|
| 39 |
# Install the package itself (makes stroke_deepisles_demo importable)
|
| 40 |
# Using --no-deps since requirements.txt already installed dependencies
|
|
@@ -44,12 +48,17 @@ RUN pip install --no-cache-dir --no-deps -e .
|
|
| 44 |
# This allows the app to detect runtime environment and use direct invocation
|
| 45 |
ENV HF_SPACES=1
|
| 46 |
ENV DEEPISLES_DIRECT_INVOCATION=1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
# Ensure HuggingFace cache uses our writable directory
|
| 48 |
-
ENV HF_HOME=/
|
| 49 |
|
| 50 |
# Create directories for data with proper permissions
|
| 51 |
-
RUN mkdir -p /
|
| 52 |
-
chown -R 1000:1000 /
|
| 53 |
|
| 54 |
# Switch to non-root user (required by HF Spaces)
|
| 55 |
USER user
|
|
|
|
| 4 |
#
|
| 5 |
# IMPORTANT: During Docker build, GPU is NOT available.
|
| 6 |
# All GPU operations happen at runtime only.
|
| 7 |
+
#
|
| 8 |
+
# CRITICAL: DeepISLES code lives at /app/src/ in the base image.
|
| 9 |
+
# We install our demo at /home/user/demo to avoid overwriting DeepISLES.
|
| 10 |
|
| 11 |
# NOTE: isleschallenge/deepisles only publishes 'latest' tag on Docker Hub.
|
| 12 |
# For reproducibility, consider using a SHA digest if available:
|
|
|
|
| 24 |
# Create user if not exists (DeepISLES image may already have a user)
|
| 25 |
RUN useradd -m -u 1000 user 2>/dev/null || true
|
| 26 |
|
| 27 |
+
# IMPORTANT: Use /home/user/demo for our app, NOT /app
|
| 28 |
+
# /app contains DeepISLES code (main.py, src/, weights/) that we must NOT overwrite
|
| 29 |
+
WORKDIR /home/user/demo
|
| 30 |
|
| 31 |
# Copy requirements first for better layer caching
|
| 32 |
+
COPY --chown=1000:1000 requirements.txt /home/user/demo/requirements.txt
|
| 33 |
|
| 34 |
# Install Python dependencies (extras only - DeepISLES image has PyTorch, nnUNet, etc.)
|
| 35 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 36 |
|
| 37 |
# Copy application source code and package files
|
| 38 |
+
COPY --chown=1000:1000 pyproject.toml /home/user/demo/pyproject.toml
|
| 39 |
+
COPY --chown=1000:1000 README.md /home/user/demo/README.md
|
| 40 |
+
COPY --chown=1000:1000 src/ /home/user/demo/src/
|
| 41 |
+
COPY --chown=1000:1000 app.py /home/user/demo/app.py
|
| 42 |
|
| 43 |
# Install the package itself (makes stroke_deepisles_demo importable)
|
| 44 |
# Using --no-deps since requirements.txt already installed dependencies
|
|
|
|
| 48 |
# This allows the app to detect runtime environment and use direct invocation
|
| 49 |
ENV HF_SPACES=1
|
| 50 |
ENV DEEPISLES_DIRECT_INVOCATION=1
|
| 51 |
+
|
| 52 |
+
# Point to DeepISLES location for direct invocation
|
| 53 |
+
# DeepISLES code is at /app in the base image
|
| 54 |
+
ENV DEEPISLES_PATH=/app
|
| 55 |
+
|
| 56 |
# Ensure HuggingFace cache uses our writable directory
|
| 57 |
+
ENV HF_HOME=/home/user/demo/cache
|
| 58 |
|
| 59 |
# Create directories for data with proper permissions
|
| 60 |
+
RUN mkdir -p /home/user/demo/data /home/user/demo/results /home/user/demo/cache && \
|
| 61 |
+
chown -R 1000:1000 /home/user/demo
|
| 62 |
|
| 63 |
# Switch to non-root user (required by HF Spaces)
|
| 64 |
USER user
|
|
@@ -498,17 +498,33 @@ For showcasing to others. Spin up when needed, pause when done.
|
|
| 498 |
|
| 499 |
```dockerfile
|
| 500 |
# Dockerfile for HF Spaces
|
|
|
|
|
|
|
| 501 |
FROM isleschallenge/deepisles:latest
|
| 502 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 503 |
# Add our application
|
| 504 |
-
COPY requirements.txt /
|
| 505 |
-
RUN pip install -r
|
| 506 |
|
| 507 |
-
COPY
|
| 508 |
-
COPY
|
|
|
|
|
|
|
| 509 |
|
| 510 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
EXPOSE 7860
|
|
|
|
| 512 |
CMD ["python", "-m", "stroke_deepisles_demo.ui.app"]
|
| 513 |
```
|
| 514 |
|
|
@@ -681,19 +697,34 @@ uv run stroke-demo run --case sub-stroke0001
|
|
| 681 |
|
| 682 |
```dockerfile
|
| 683 |
# Dockerfile
|
|
|
|
|
|
|
| 684 |
FROM isleschallenge/deepisles:latest
|
| 685 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 686 |
# Install additional dependencies
|
| 687 |
-
COPY requirements.txt /
|
| 688 |
-
RUN pip install --no-cache-dir -r
|
| 689 |
|
| 690 |
# Copy application code
|
| 691 |
-
COPY
|
| 692 |
-
COPY
|
|
|
|
|
|
|
| 693 |
|
| 694 |
-
|
| 695 |
-
|
|
|
|
|
|
|
| 696 |
|
|
|
|
|
|
|
|
|
|
| 697 |
CMD ["python", "-m", "stroke_deepisles_demo.ui.app"]
|
| 698 |
```
|
| 699 |
|
|
|
|
| 498 |
|
| 499 |
```dockerfile
|
| 500 |
# Dockerfile for HF Spaces
|
| 501 |
+
# CRITICAL: DeepISLES code lives at /app/src/ in the base image.
|
| 502 |
+
# We install our demo at /home/user/demo to avoid overwriting DeepISLES.
|
| 503 |
FROM isleschallenge/deepisles:latest
|
| 504 |
|
| 505 |
+
# HF Spaces runs containers with user ID 1000
|
| 506 |
+
RUN useradd -m -u 1000 user 2>/dev/null || true
|
| 507 |
+
|
| 508 |
+
# IMPORTANT: Use /home/user/demo for our app, NOT /app
|
| 509 |
+
WORKDIR /home/user/demo
|
| 510 |
+
|
| 511 |
# Add our application
|
| 512 |
+
COPY --chown=1000:1000 requirements.txt /home/user/demo/requirements.txt
|
| 513 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 514 |
|
| 515 |
+
COPY --chown=1000:1000 pyproject.toml /home/user/demo/pyproject.toml
|
| 516 |
+
COPY --chown=1000:1000 src/ /home/user/demo/src/
|
| 517 |
+
COPY --chown=1000:1000 app.py /home/user/demo/app.py
|
| 518 |
+
RUN pip install --no-cache-dir --no-deps -e .
|
| 519 |
|
| 520 |
+
# Environment variables for HF Spaces + direct invocation
|
| 521 |
+
ENV HF_SPACES=1
|
| 522 |
+
ENV DEEPISLES_DIRECT_INVOCATION=1
|
| 523 |
+
ENV DEEPISLES_PATH=/app
|
| 524 |
+
|
| 525 |
+
USER user
|
| 526 |
EXPOSE 7860
|
| 527 |
+
ENTRYPOINT []
|
| 528 |
CMD ["python", "-m", "stroke_deepisles_demo.ui.app"]
|
| 529 |
```
|
| 530 |
|
|
|
|
| 697 |
|
| 698 |
```dockerfile
|
| 699 |
# Dockerfile
|
| 700 |
+
# CRITICAL: DeepISLES code lives at /app/src/ in the base image.
|
| 701 |
+
# We install our demo at /home/user/demo to avoid overwriting DeepISLES.
|
| 702 |
FROM isleschallenge/deepisles:latest
|
| 703 |
|
| 704 |
+
# HF Spaces runs containers with user ID 1000
|
| 705 |
+
RUN useradd -m -u 1000 user 2>/dev/null || true
|
| 706 |
+
|
| 707 |
+
# IMPORTANT: Use /home/user/demo for our app, NOT /app
|
| 708 |
+
WORKDIR /home/user/demo
|
| 709 |
+
|
| 710 |
# Install additional dependencies
|
| 711 |
+
COPY --chown=1000:1000 requirements.txt /home/user/demo/requirements.txt
|
| 712 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 713 |
|
| 714 |
# Copy application code
|
| 715 |
+
COPY --chown=1000:1000 pyproject.toml /home/user/demo/pyproject.toml
|
| 716 |
+
COPY --chown=1000:1000 src/ /home/user/demo/src/
|
| 717 |
+
COPY --chown=1000:1000 app.py /home/user/demo/app.py
|
| 718 |
+
RUN pip install --no-cache-dir --no-deps -e .
|
| 719 |
|
| 720 |
+
# Environment variables for HF Spaces + direct invocation
|
| 721 |
+
ENV HF_SPACES=1
|
| 722 |
+
ENV DEEPISLES_DIRECT_INVOCATION=1
|
| 723 |
+
ENV DEEPISLES_PATH=/app
|
| 724 |
|
| 725 |
+
USER user
|
| 726 |
+
EXPOSE 7860
|
| 727 |
+
ENTRYPOINT []
|
| 728 |
CMD ["python", "-m", "stroke_deepisles_demo.ui.app"]
|
| 729 |
```
|
| 730 |
|
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Bug Spec: DeepISLES Path Conflict in Docker Build
|
| 2 |
+
|
| 3 |
+
**Status:** Root Cause Found β Fix Ready
|
| 4 |
+
**Priority:** P0 (Blocks inference)
|
| 5 |
+
**Branch:** `fix/deepisles-docker-path`
|
| 6 |
+
**Date:** 2025-12-08
|
| 7 |
+
|
| 8 |
+
## Executive Summary
|
| 9 |
+
|
| 10 |
+
Our Dockerfile was **overwriting DeepISLES modules** by copying our app to `/app/src/`, which is where the base image stores DeepISLES code. The fix is to install our app at `/home/user/demo` instead.
|
| 11 |
+
|
| 12 |
+
## Root Cause
|
| 13 |
+
|
| 14 |
+
The `isleschallenge/deepisles:latest` Docker image has this structure:
|
| 15 |
+
```text
|
| 16 |
+
/app/
|
| 17 |
+
βββ main.py
|
| 18 |
+
βββ requirements.txt
|
| 19 |
+
βββ src/ β DeepISLES Python modules
|
| 20 |
+
β βββ isles22_ensemble.py
|
| 21 |
+
βββ weights/ β Model weights (~GB)
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
Our original Dockerfile:
|
| 25 |
+
```dockerfile
|
| 26 |
+
FROM isleschallenge/deepisles:latest
|
| 27 |
+
WORKDIR /app
|
| 28 |
+
COPY src/ /app/src/ β OVERWRITES DeepISLES modules!
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
This replaced `/app/src/isles22_ensemble.py` (DeepISLES) with `/app/src/stroke_deepisles_demo/` (our app).
|
| 32 |
+
|
| 33 |
+
## The Fix
|
| 34 |
+
|
| 35 |
+
1. **Changed app directory** from `/app` to `/home/user/demo`
|
| 36 |
+
2. **Added `DEEPISLES_PATH=/app`** environment variable
|
| 37 |
+
3. **Updated `direct.py`** to check `DEEPISLES_PATH` first
|
| 38 |
+
|
| 39 |
+
### Dockerfile Changes
|
| 40 |
+
```dockerfile
|
| 41 |
+
# Before: WORKDIR /app
|
| 42 |
+
# After:
|
| 43 |
+
WORKDIR /home/user/demo
|
| 44 |
+
|
| 45 |
+
# Before: COPY src/ /app/src/
|
| 46 |
+
# After:
|
| 47 |
+
COPY src/ /home/user/demo/src/
|
| 48 |
+
|
| 49 |
+
# New:
|
| 50 |
+
ENV DEEPISLES_PATH=/app
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
### direct.py Changes
|
| 54 |
+
```python
|
| 55 |
+
def _get_deepisles_search_paths() -> list[str]:
|
| 56 |
+
paths = []
|
| 57 |
+
# Check environment variable first (set in Dockerfile)
|
| 58 |
+
env_path = os.environ.get("DEEPISLES_PATH")
|
| 59 |
+
if env_path:
|
| 60 |
+
paths.append(env_path)
|
| 61 |
+
# Add common installation locations
|
| 62 |
+
paths.extend(["/app", "/DeepIsles", ...])
|
| 63 |
+
return paths
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
## Investigation Process
|
| 67 |
+
|
| 68 |
+
1. Pulled `isleschallenge/deepisles:latest` locally
|
| 69 |
+
2. Inspected WORKDIR: `/app`
|
| 70 |
+
3. Listed `/app` contents: found `src/`, `weights/`, `main.py`
|
| 71 |
+
4. Realized our `COPY src/ /app/src/` was overwriting DeepISLES
|
| 72 |
+
|
| 73 |
+
## Files Changed
|
| 74 |
+
|
| 75 |
+
- `Dockerfile` - Use `/home/user/demo`, add `DEEPISLES_PATH`
|
| 76 |
+
- `src/stroke_deepisles_demo/inference/direct.py` - Dynamic search paths
|
| 77 |
+
|
| 78 |
+
## Testing
|
| 79 |
+
|
| 80 |
+
- All 125 unit tests pass
|
| 81 |
+
- Need to test on HF Spaces to verify inference works
|
| 82 |
+
|
| 83 |
+
## References
|
| 84 |
+
|
| 85 |
+
- [DeepISLES GitHub](https://github.com/ezequieldlrosa/DeepIsles)
|
| 86 |
+
- [Docker Hub Image](https://hub.docker.com/r/isleschallenge/deepisles)
|
| 87 |
+
- [HuggingFace Docker Spaces](https://huggingface.co/docs/hub/en/spaces-sdks-docker)
|
| 88 |
+
|
| 89 |
+
## Sources
|
| 90 |
+
|
| 91 |
+
- [Docker Blog: Build ML Apps with HuggingFace](https://www.docker.com/blog/build-machine-learning-apps-with-hugging-faces-docker-spaces/)
|
| 92 |
+
- [HuggingFace Docker Spaces Docs](https://huggingface.co/docs/hub/en/spaces-sdks-docker)
|
|
@@ -16,6 +16,7 @@ See:
|
|
| 16 |
|
| 17 |
from __future__ import annotations
|
| 18 |
|
|
|
|
| 19 |
import sys
|
| 20 |
import time
|
| 21 |
from dataclasses import dataclass
|
|
@@ -27,13 +28,30 @@ from stroke_deepisles_demo.inference.deepisles import find_prediction_mask
|
|
| 27 |
|
| 28 |
logger = get_logger(__name__)
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
"
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
|
| 39 |
@dataclass(frozen=True)
|
|
@@ -54,7 +72,9 @@ def _ensure_deepisles_importable() -> str:
|
|
| 54 |
Raises:
|
| 55 |
DeepISLESError: If DeepISLES cannot be found
|
| 56 |
"""
|
| 57 |
-
|
|
|
|
|
|
|
| 58 |
if Path(path).exists():
|
| 59 |
if path not in sys.path:
|
| 60 |
sys.path.insert(0, path)
|
|
@@ -70,7 +90,7 @@ def _ensure_deepisles_importable() -> str:
|
|
| 70 |
raise DeepISLESError(
|
| 71 |
"DeepISLES modules not found. Direct invocation requires running "
|
| 72 |
"inside the DeepISLES Docker image. Searched paths: "
|
| 73 |
-
f"{
|
| 74 |
)
|
| 75 |
|
| 76 |
|
|
|
|
| 16 |
|
| 17 |
from __future__ import annotations
|
| 18 |
|
| 19 |
+
import os
|
| 20 |
import sys
|
| 21 |
import time
|
| 22 |
from dataclasses import dataclass
|
|
|
|
| 28 |
|
| 29 |
logger = get_logger(__name__)
|
| 30 |
|
| 31 |
+
|
| 32 |
+
def _get_deepisles_search_paths() -> list[str]:
|
| 33 |
+
"""Get paths to search for DeepISLES modules.
|
| 34 |
+
|
| 35 |
+
Checks DEEPISLES_PATH environment variable first, then falls back to
|
| 36 |
+
common installation locations.
|
| 37 |
+
"""
|
| 38 |
+
paths = []
|
| 39 |
+
|
| 40 |
+
# Check environment variable first (set in Dockerfile)
|
| 41 |
+
env_path = os.environ.get("DEEPISLES_PATH")
|
| 42 |
+
if env_path:
|
| 43 |
+
paths.append(env_path)
|
| 44 |
+
|
| 45 |
+
# Add common installation locations (excluding any already added via env var)
|
| 46 |
+
fallback_paths = [
|
| 47 |
+
"/app", # Default location in isleschallenge/deepisles Docker image
|
| 48 |
+
"/DeepIsles",
|
| 49 |
+
"/opt/deepisles",
|
| 50 |
+
"/home/user/DeepIsles",
|
| 51 |
+
]
|
| 52 |
+
paths.extend(p for p in fallback_paths if p not in paths)
|
| 53 |
+
|
| 54 |
+
return paths
|
| 55 |
|
| 56 |
|
| 57 |
@dataclass(frozen=True)
|
|
|
|
| 72 |
Raises:
|
| 73 |
DeepISLESError: If DeepISLES cannot be found
|
| 74 |
"""
|
| 75 |
+
search_paths = _get_deepisles_search_paths()
|
| 76 |
+
|
| 77 |
+
for path in search_paths:
|
| 78 |
if Path(path).exists():
|
| 79 |
if path not in sys.path:
|
| 80 |
sys.path.insert(0, path)
|
|
|
|
| 90 |
raise DeepISLESError(
|
| 91 |
"DeepISLES modules not found. Direct invocation requires running "
|
| 92 |
"inside the DeepISLES Docker image. Searched paths: "
|
| 93 |
+
f"{search_paths}"
|
| 94 |
)
|
| 95 |
|
| 96 |
|