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: в прод едет только результат
Компиляторы, 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"]Правила, о которых стоит помнить
.dockerignoreпишется первым. Без него в контекст сборки уезжают.git,node_modules,.venv, датасеты — сборка тормозит, образ пухнет, секреты из локальных файлов утекают в слои.- Не
latest.python:3.13-slim,node:22-alpine— конкретные теги.latestоднажды поменяется, и «ничего не трогали, а сломалось». - Non-root.
USER appв финальной стадии: процесс в контейнере не должен работать от рута. - Секреты не в образе. Ключи приходят переменными окружения при запуске. Всё, что попало в
Dockerfileили в скопированный файл, лежит в слое навсегда — даже если удалить следующей инструкцией. - Один процесс — один контейнер. Бэкенд и база в одном образе лишают смысла и масштабирование, и обновление по частям.
Проверь себя
docker build -t myapp . # собралось
docker run --rm -p 8000:8000 myapp # запустилось и отвечает
docker images myapp # размер — сотни МБ, а не единицы ГБ
docker history myapp # видно, какой слой раздул образДальше — Compose: та же система, но целиком и одной командой.