МАТЕРИАЛЫ КУРСА
СИИ • НАВИГАЦИЯ

Docker: база, которой хватит

Образ, слои и кэш, multi-stage, Dockerfile для Python и Node, non-root, .dockerignore

Docker: база, которой хватит

Docker в курсе нужен ради одной фразы: «работает у меня» перестаёт быть аргументом. С первой недели проект поднимается одной командой — у тебя, у одногруппника, на проде и в CI одинаково.

Здесь только та база, которая реально понадобится в лабах 3–4. Всё остальное про Docker выучится по дороге, когда прижмёт.

Образ, контейнер, слой

  • Образ — неизменяемый слепок файловой системы с описанием запуска. Собирается из Dockerfile, лежит в реестре, имеет тег.
  • Контейнер — запущенный экземпляр образа плюс тонкий изменяемый слой сверху. Удалил контейнер — изменения этого слоя ушли.
  • Слой — результат одной инструкции Dockerfile. Слои кэшируются и переиспользуются, и именно из-за них порядок строк важнее их содержимого.

Отсюда главное правило сборки: сначала то, что меняется редко, потом то, что меняется каждый коммит. Зависимости ставятся до копирования кода — иначе каждая правка строчки инвалидирует установку пакетов.

# Плохо: любая правка кода = переустановка всех зависимостей
COPY . .
RUN uv sync

# Хорошо: пересборка только когда изменился манифест
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-install-project
COPY . .

Multi-stage: в прод едет только результат

Multi-stage build

Компиляторы, dev-зависимости и кэши нужны только на сборке. Multi-stage оставляет их в стадии builder, а в финальный образ копирует один артефакт — виртуальное окружение или собранный dist.

Что это даёт: образ заметно меньше, в проде нет ни компилятора, ни менеджера пакетов, выкат быстрее.

Python + uv — актуальный стек 2026 (тот же uv, что в принципах работы с агентом):

FROM python:3.13-slim AS builder
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
WORKDIR /app

COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-install-project --no-dev
COPY . .
RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-dev

FROM python:3.13-slim
WORKDIR /app
RUN useradd --create-home app
COPY --from=builder --chown=app:app /app /app
ENV PATH="/app/.venv/bin:$PATH"
USER app
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Три неочевидные строчки: --mount=type=cache переживает пересборку и не качает пакеты заново; UV_COMPILE_BYTECODE=1 компилирует .pyc заранее — контейнер стартует быстрее; UV_LINK_MODE=copy убирает симлинки, которые ломаются при монтировании томов.

Node — та же логика, другой менеджер:

FROM node:22-alpine AS builder
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.output ./
USER node
CMD ["node", "server.js"]

Правила, о которых стоит помнить

  1. .dockerignore пишется первым. Без него в контекст сборки уезжают .git, node_modules, .venv, датасеты — сборка тормозит, образ пухнет, секреты из локальных файлов утекают в слои.
  2. Не latest. python:3.13-slim, node:22-alpine — конкретные теги. latest однажды поменяется, и «ничего не трогали, а сломалось».
  3. Non-root. USER app в финальной стадии: процесс в контейнере не должен работать от рута.
  4. Секреты не в образе. Ключи приходят переменными окружения при запуске. Всё, что попало в Dockerfile или в скопированный файл, лежит в слое навсегда — даже если удалить следующей инструкцией.
  5. Один процесс — один контейнер. Бэкенд и база в одном образе лишают смысла и масштабирование, и обновление по частям.

Проверь себя

docker build -t myapp .          # собралось
docker run --rm -p 8000:8000 myapp   # запустилось и отвечает
docker images myapp              # размер — сотни МБ, а не единицы ГБ
docker history myapp             # видно, какой слой раздул образ

Дальше — Compose: та же система, но целиком и одной командой.