Docker
Dockerfile, docker-compose, volumes, networks, multi-stage builds.
Docker — чтобы «у меня работает» работало везде
«У меня работает» — худшая фраза в ML-инженерии. Ты обучил модель на Ubuntu с CUDA 11.8 и Python 3.10. Коллега клонирует репозиторий на macOS — у него другая версия NumPy, нет CUDA, и модель падает с криптичным segfault. На проде CentOS с CUDA 12.1 — и всё ломается третьим, совершенно новым способом.
Docker решает эту проблему раз и навсегда: ты упаковываешь код, зависимости, конфигурации и runtime в контейнер — изолированную среду, которая запускается одинаково на любой машине. Твой ноутбук, сервер коллеги, Kubernetes-кластер в облаке — контейнер ведёт себя идентично. Это не магия, а изоляция через механизмы ядра Linux (namespaces, cgroups).
Для ML-инженера Docker — не опциональный инструмент, а обязательный. Без него невозможно воспроизводимое обучение, CI/CD пайплайн, деплой модели в прод. На собеседованиях вопрос «расскажи про Docker» — это не про DevOps, это про зрелость инженера.
Большая картина: от Dockerfile до продакшена
Весь Docker сводится к четырём понятиям, каждое из которых вытекает из предыдущего:
1. Image (образ) — неизменяемый шаблон. Это «снапшот» файловой системы: ОС + библиотеки + твой код + конфигурации. Образ не изменяется после сборки — он read-only. Аналогия: ISO-файл с операционной системой. Ты не запускаешь ISO напрямую — ты устанавливаешь с него. 2. Container (контейнер) — запущенный экземпляр image. Это живой процесс с изолированной файловой системой, сетью и процессами. Из одного image можно запустить 10 контейнеров — они не мешают друг другу. Контейнер живёт, пока работает его главный процесс. 3. Registry — хранилище образов. Как GitHub для кода, только для Docker-образов. Docker Hub, GitHub Container Registry (ghcr.io), AWS ECR. Ты пушишь образ в registry, а CI/CD или Kubernetes тянет его оттуда. 4. Orchestration — управление множеством контейнеров. Kubernetes (K8s), Docker Swarm, ECS. Когда у тебя 50 контейнеров на 10 серверах, нужен кто-то, кто следит за здоровьем, масштабирует и рестартит упавшие.
Загрузка интерактивного виджета...
Типичный путь ML-проекта: Dockerfile описывает, как собрать образ → docker build создаёт image → docker run запускает container → docker push отправляет образ в registry → Kubernetes или CI/CD тянет образ и запускает его на проде.
Dockerfile — рецепт для сборки образа
Dockerfile — это текстовый файл с инструкциями, которые Docker выполняет сверху вниз. Каждая инструкция создаёт новый слой (layer) в образе. Слои кешируются: если строка не изменилась — слой берётся из кеша. Это ключевая оптимизация, и порядок инструкций имеет значение.
Основные инструкции:
• FROM — базовый образ. Всё начинается с него. FROM python:3.12-slim — минимальный Python-образ.
• WORKDIR — рабочая директория внутри контейнера (аналог cd).
• COPY — копирует файлы с хоста в контейнер. COPY requirements.txt . — из контекста сборки.
• RUN — выполняет команду при сборке. RUN pip install -r requirements.txt — устанавливает зависимости.
• CMD — команда при запуске контейнера. Может быть переопределена при docker run.
• ENTRYPOINT — фиксированная точка входа. CMD дописывается к ENTRYPOINT как аргументы.
• EXPOSE — документирует порт (не открывает! Открывает -p при docker run).
• ENV — переменная окружения.
Вот Dockerfile для ML-сервиса — модель на FastAPI:
FROM python:3.12-slim
WORKDIR /app
# Сначала зависимости — этот слой кешируется, пока requirements.txt не изменится
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Потом код — при каждом изменении перестраивается ТОЛЬКО этот слой
COPY . .
# Non-root пользователь (безопасность)
RUN useradd -m appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]💡 CMD vs ENTRYPOINT — в чём разница?
docker run image <другая команда>.
ENTRYPOINT — фиксированная точка входа. То, что передаётся в docker run, становится аргументами ENTRYPOINT.
Пример: ENTRYPOINT ["python", "train.py"] + CMD ["--epochs", "10"].
docker run image → python train.py --epochs 10
docker run image --epochs 50 → python train.py --epochs 50
Обычный паттерн: ENTRYPOINT для исполняемого файла, CMD для аргументов по умолчанию.💡 Порядок COPY критичен для кеша
requirements.txt не менялся — слой с pip install берётся из кеша мгновенно. Поэтому COPY requirements.txt . идёт ДО COPY . . — изменения в коде не инвалидируют кеш зависимостей. Это экономит минуты на каждом билде.Docker Compose — несколько контейнеров одной командой
ML-проект — это не один контейнер. Модель обслуживает API, предсказания кешируются в Redis, метаданные лежат в PostgreSQL. Поднимать каждый контейнер руками (docker run ... × 3) — мучение. Docker Compose описывает всю инфраструктуру в одном YAML-файле и управляет ей одной командой.
# docker-compose.yml — ML-сервис + Redis + PostgreSQL
services:
api:
build: .
ports: ["8000:8000"]
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/mldb
- REDIS_URL=redis://redis:6379
- MODEL_PATH=/models/model.onnx
volumes:
- ./models:/models # модель на хосте, не в образе
depends_on: [db, redis]
db:
image: postgres:16
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mldb
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
pgdata: # именованный volume — данные переживут docker compose downКлючевые команды:
• docker compose up -d — поднять все сервисы в фоне
• docker compose ps — статус контейнеров
• docker compose logs api — логи конкретного сервиса
• docker compose down — остановить и удалить контейнеры (volumes сохраняются)
• docker compose build && docker compose up -d — пересобрать и перезапустить (НЕ restart! Restart не подтягивает новый образ)
Обрати внимание: сервисы обращаются друг к другу по имени — db, redis, не по IP. Docker Compose автоматически создаёт сеть, где каждый сервис доступен по hostname, совпадающему с его именем в YAML. Это DNS-резолвинг через встроенный Docker DNS.
Volumes и Networking — persistence и связь контейнеров
Volumes (тома) решают проблему данных. По умолчанию всё внутри контейнера — эфемерно: остановил контейнер — данные пропали. Volumes — это постоянное хранилище, которое живёт отдельно от контейнера.
Два типа:
• Named volumes — управляются Docker: volumes: [pgdata:/var/lib/postgresql/data]. Docker сам решает, где хранить данные на хосте. Идеальны для баз данных.
• Bind mounts — привязка директории хоста: volumes: [./models:/models]. Файлы на хосте = файлы в контейнере. Удобно для разработки и для моделей — обновляешь файл на хосте, контейнер видит изменения сразу.
Networking. Docker Compose создаёт одну сеть для всех сервисов проекта. Контейнеры внутри неё видят друг друга по имени сервиса, но изолированы от внешнего мира (если не опубликованы порты через ports). Можно создавать несколько сетей для изоляции: frontend-сеть для API + nginx, backend-сеть для API + DB.
# Пример с двумя сетями: API видит и фронт, и БД. Фронт не видит БД.
services:
nginx:
image: nginx
networks: [frontend]
api:
build: .
networks: [frontend, backend] # API — «мост» между сетями
db:
image: postgres:16
networks: [backend] # БД недоступна из frontend
networks:
frontend:
backend:Best practices — чтобы образы были быстрыми и лёгкими
Multi-stage builds — паттерн, который уменьшает итоговый образ в 2-5 раз. Идея: первый этап (builder) устанавливает зависимости с gcc и прочими инструментами сборки, второй этап (runtime) копирует только готовые пакеты в чистый образ. Всё «мусор сборки» остаётся в первом этапе.
# Этап 1: builder — тяжёлый, с gcc и dev-зависимостями
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# Этап 2: runtime — чистый, лёгкий образ
FROM python:3.12-slim
COPY --from=builder /install /usr/local
WORKDIR /app
COPY . .
RUN useradd -m appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]Ещё несколько практик, которые отличают продакшн-образ от «собрал на коленке»:
- .dockerignore — исключи
.git,__pycache__,.venv,data/,notebooks/,*.pyc. Без негоCOPY . .копирует гигабайты мусора в контейнер - slim/alpine базовые образы —
python:3.12-slim(~150MB) вместоpython:3.12(~1GB). Меньше размер = быстрее pull + меньше уязвимостей - Non-root пользователь —
RUN useradd -m appuser && USER appuser. Контейнер не должен работать от root — это базовая безопасность - Один процесс = один контейнер — API отдельно, Celery worker отдельно, не в одном контейнере. Это упрощает масштабирование и мониторинг
- HEALTHCHECK —
HEALTHCHECK CMD curl -f http://localhost:8000/health || exit 1. Docker и K8s узнают, жив ли сервис, и перезапустят при проблемах - Логи в stdout/stderr — Docker и K8s подхватывают их автоматически. Не пиши в файлы внутри контейнера
- Не храни секреты в Dockerfile — пароли и ключи передавай через
environmentили Docker secrets, не черезENVв Dockerfile (они видны вdocker history)
Docker для ML — GPU, тяжёлые образы и serving моделей
ML-контейнеры имеют свою специфику, которая отличает их от обычных веб-сервисов.
GPU в Docker. По умолчанию контейнер не видит GPU. Для доступа к видеокарте нужен NVIDIA Container Toolkit (раньше назывался nvidia-docker). После его установки контейнер запускается с флагом --gpus all (или --gpus device=0 для конкретной карты). В Compose — deploy.resources.reservations.devices.
# docker-compose.yml — ML-сервис с GPU
services:
inference:
build: .
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1 # или "all" для всех GPU
capabilities: [gpu]
volumes:
- ./models:/modelsТяжёлые образы — боль ML. Базовый nvidia/cuda:12.1-runtime весит ~3.5GB, добавь PyTorch (~2GB) и зависимости — легко получается 8-10GB. Как бороться:
• Multi-stage builds — отделяй сборку от runtime
• Модель НЕ в образе — монтируй через volume или скачивай при старте из S3/GCS. Иначе каждое обновление модели = пересборка образа
• Минимизируй слои RUN — объединяй apt-get install && apt-get clean в один слой
• Используй .dockerignore — не тащи датасеты в контейнер
Model serving. В проде модель обслуживается через HTTP API. Типичные инструменты: FastAPI/Uvicorn (простой вариант), Triton Inference Server (NVIDIA, батчинг и multi-model), TorchServe, BentoML. Контейнер с моделью — это stateless сервис: модель загружается при старте, запросы обрабатываются без изменения состояния. Это позволяет масштабировать горизонтально — запустил 5 реплик за балансировщиком.
Docker vs VM — в чём разница и когда что
Docker — не виртуальная машина, хотя со стороны похоже. Ключевое отличие: VM эмулирует полный компьютер с собственным ядром ОС, а контейнер разделяет ядро хоста. Это делает контейнеры быстрыми и лёгкими, но менее изолированными.
- Запуск: контейнер стартует за секунды (это просто процесс), VM — за минуты (загрузка ОС)
- Размер: образ контейнера ~100MB-2GB, образ VM ~10-40GB
- Изоляция: VM — полная (отдельное ядро), контейнер — на уровне namespaces (одно ядро с хостом)
- Производительность: контейнер — почти нативная (нет гипервизора), VM — overhead виртуализации
- Когда VM: нужна полная изоляция (multi-tenant, разные ОС), legacy-приложения, Windows на Linux
- Когда Docker: микросервисы, CI/CD, ML-пайплайны, dev-окружения — почти всегда
В ML-практике: обучение и инференс — контейнеры (Docker/K8s). VM используются как хосты, на которых крутятся контейнеры (EC2 instance → Docker → твой сервис). На собеседовании достаточно знать: «контейнер — изоляция на уровне процессов, разделяет ядро с хостом; VM — полная виртуализация с отдельным ядром».
Как это выглядит в реальной работе
Разработка (локально): docker compose up поднимает API + PostgreSQL + Redis. Разрабатываешь код, тестируешь. Bind mount (./src:/app/src) позволяет видеть изменения без пересборки образа.
CI/CD: при merge в main CI-сервер (GitHub Actions, GitLab CI) запускает docker build, прогоняет тесты внутри контейнера, пушит образ в registry с тегом (commit SHA или version).
Прод: Kubernetes тянет образ из registry, запускает нужное количество реплик, следит за здоровьем через health checks. Модель подгружается из S3 при старте контейнера, а не вшита в образ — обновление модели не требует пересборки.
🎯 На собеседовании
Junior
Middle
docker run CMD заменяется, ENTRYPOINT — нет.
• Как оптимизировать размер образа? Multi-stage build, slim-базы, .dockerignore, объединение RUN-команд, очистка кешей pip/apt.
• Зачем volumes? Данные внутри контейнера эфемерны. Volumes сохраняют данные между перезапусками. Named volumes для БД, bind mounts для dev-окружения и моделей.Senior
--gpus флаг. В Compose — deploy.resources.reservations.devices. Без Toolkit контейнер не видит видеокарту.
• Как организовать Docker-сеть для микросервисов? Отдельные networks для изоляции (frontend/backend). Сервисы общаются по имени через встроенный DNS. Публикуются только нужные порты.
• Layer caching — как работает и как сломать? Каждая инструкция = слой. Если строка изменилась — все последующие слои пересобираются. COPY requirements.txt до COPY . — зависимости не пересобираются при изменении кода.
• Безопасность контейнеров? Non-root user, минимальные базовые образы (меньше attack surface), секреты через Docker secrets (не ENV), read-only fs (--read-only), security scanning (Trivy, Snyk).Собираем всё вместе
Docker — это инструмент, который превращает «у меня работает» в «у всех работает». Dockerfile описывает среду, docker build создаёт образ, docker run запускает контейнер, registry хранит образы для деплоя. Docker Compose оркестрирует несколько сервисов. Volumes сохраняют данные, networks обеспечивают связь.
Если запомнить одну вещь из этой ноды: Docker = воспроизводимая среда в одной команде. Это фундамент CI/CD, деплоя и командной работы. ML без Docker — это возврат в эру «скинь мне свой requirements.txt и помолимся».
Дальше на роадмапе: MLOps покажет, как Docker вписывается в полный ML-пайплайн, а Model Serving расскажет про инференс в контейнерах — Triton, TorchServe, масштабирование.
Материалы
Официальный туториал Docker от нуля до Compose. Лучшая отправная точка.
Подробная статья на русском с практическими примерами.
Полный курс Docker за 3 часа. Идеально для тех, кто предпочитает видео.
Официальная документация по multi-stage builds — must read.
Как настроить GPU в Docker. Обязательно для ML-инженеров.
Линтер для Dockerfile. Находит плохие практики автоматически.