bone_2026/Dockerfile

98 lines
5.5 KiB
Docker
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# DXA Quality Assessment — контейнер для инференса (CPU)
#
# Сборка: docker build -t dxa-quality .
# Запуск: docker run -p 8000:8000 dxa-quality
# или: docker compose up -d (сервис dxa-cpu)
#
# Чекпоинт лежит ВНУТРИ образа: models/dxa_model.pth отслеживается git (каталог
# models/ не исключён в .gitignore) и копируется на этапе сборки. Монтировать
# пути не нужно — условие приёмки в том, что модели уже в образе. Если всё-таки
# смонтировать каталог в /app/models, он перекроет встроенный чекпоинт.
#
# Требования методики: зафиксированные версии зависимостей и работа без
# внешних сервисов. Inference не скачивает веса из сети — они внутри
# чекпоинта, поэтому при запуске сеть не нужна.
FROM python:3.11-slim
# libgl1/libglib2.0-0 нужны opencv (импортируется через зависимости проекта),
# libgomp1 — для параллельных циклов torch.
RUN apt-get update && apt-get install -y --no-install-recommends \
libgl1 \
libglib2.0-0 \
libgomp1 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# --- Слой зависимостей: отдельно от кода, чтобы кэшировался ---
# Все версии зафиксированы, torch ставится с CPU-индексом: образ для инференса
# не требует GPU, а версия из PyPI на Linux подтянула бы CUDA-колёса.
COPY requirements.txt ./
RUN pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir \
--extra-index-url https://download.pytorch.org/whl/cpu \
-r requirements.txt
# --- Код ---
COPY src ./src
# Скрипт пакетной обработки (оценка берёт его из корня образа).
COPY run.sh ./run.sh
RUN chmod +x ./run.sh
# Smoke-проверка импорта: падает на сборке, если расходятся зависимости или
# модуль не найден, вместо тихой 500-й на каждом запросе в рантайме.
RUN python -c "import src.main; print('app import ok')"
# --- Проверка статики для офлайн-работы ---
# Веб-интерфейс не должен зависеть от CDN: Tailwind и FontAwesome лежат в
# src/api/static/vendor и src/api/static/webfonts (см. index.html). Страница
# ручной разметки /label тоже должна попасть в образ целиком.
RUN python -c "import os; \
files=['src/api/static/vendor/tailwind.js','src/api/static/vendor/fontawesome.css', \
'src/api/static/webfonts/fa-solid-900.woff2', \
'src/api/static/label.html','src/api/static/js/labeling.js']; \
missing=[f for f in files if not os.path.exists(f)]; \
assert not missing, f'missing frontend assets: {missing}'; \
print('frontend assets ok')"
# --- Чекпоинт внутри образа ---
# Условие приёмки: модель уже в образе, чтобы проверяющему не приходилось
# указывать путь к каталогу с весами. models/dxa_model.pth отслеживается git,
# поэтому копирование работает и в CI.
COPY models/dxa_model.pth /app/models/dxa_model.pth
# Проверка, что чекпоинт читается и содержит всё нужное для инференса: без этого
# битый или забытый файл проявился бы только в рантайме как model_loaded: false
# и HTTP 500 на каждый запрос анализа.
RUN python -c "import torch; \
ckpt=torch.load('/app/models/dxa_model.pth', map_location='cpu', weights_only=False); \
missing=[k for k in ('model_state_dict','backbone','head','threshold') if k not in ckpt]; \
assert not missing, f'checkpoint missing keys: {missing}'; \
print('checkpoint ok:', ckpt['backbone'], ckpt['head'], 'threshold', ckpt['threshold'])"
# Каталоги монтирований swarm: создаём заранее, чтобы образ вёл себя одинаково
# и при обычном `docker run` без volume.
RUN mkdir -p /app/data /app/logs
# --- Переменные окружения ---
# Читается кодом только DXA_MODEL_PATH (src/main.py). MODEL_PATH, DATA_PATH,
# ANNOTATION_DIR, APP_* swarm передаёт по инерции от прежней версии — код их
# не использует, и они ни на что не влияют.
ENV PYTHONUNBUFFERED=1 \
PYTHONPATH=/app \
DXA_MODEL_PATH=/app/models/dxa_model.pth \
MODEL_PATH=/app/models \
DATA_PATH=/app/data \
ANNOTATION_DIR=/app/data/annotations
EXPOSE 8000
# HEALTHCHECK опирается на /api/v1/health, который отдаёт model_loaded.
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD python -c "import urllib.request,sys; \
r=urllib.request.urlopen('http://127.0.0.1:8000/api/v1/health', timeout=5); \
sys.exit(0 if r.status==200 else 1)"
CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]