Python и Docker: контейнеры, Dockerfile, Docker Compose

Курс по упаковке Python-приложений в изолированные контейнеры [forpes.ru](https://www.forpes.ru/post/211205). Вы научитесь создавать Dockerfile для сборки образов [back4app.com](https://back4app.com/docs-containers-ru/sozdanie-konteinernogo-python-prilozheniia-s-dockerfile) и управлять мультисервисной архитектурой через Docker Compose [zomro.com](https://zomro.com/rus/blog/faq/476-how-to-deploy-a-python-application-to-docker-compose).

1. Введение в контейнеризацию: зачем нужен Docker для Python

Введение в контейнеризацию: зачем нужен Docker для Python

Вы написали код на Python. На вашем компьютере он работает идеально: тесты проходят, API отвечает, данные сохраняются. Вы отправляете код коллеге или пытаетесь запустить его на сервере, и вдруг видите ошибку: ModuleNotFoundError, Python version mismatch или проблемы с системными библиотеками. Знакомая ситуация?

Это классическая проблема «на моей машине работает» («it works on my machine»). В этой статье мы разберем, как технология контейнеризации и инструмент Docker решают эту проблему, чем они отличаются от виртуальных машин и почему каждый Python-разработчик должен уметь ими пользоваться.

Проблема зависимостей и окружения

Python-приложения редко работают в вакууме. Им требуются:

  • Интерпретатор определенной версии (например, 3.11, а не 3.8).
  • Библиотеки Python (указанные в requirements.txt или pyproject.toml).
  • Системные зависимости (драйверы баз данных, компиляторы C++, утилиты OS).
  • Переменные окружения (ключи API, доступы к БД).
  • Управление всем этим вручную превращается в хаос, особенно когда на одном компьютере нужно запустить несколько проектов с конфликтующими версиями библиотек. Виртуальные окружения (venv, poetry) решают проблему только частично — они изолируют библиотеки Python, но не системные зависимости.

    Что такое контейнеризация?

    Контейнеризация — это метод упаковки приложения вместе со всеми его зависимостями (код, библиотеки, настройки, системные утилиты) в единый объект, который называется контейнер.

    Представьте грузоперевозки до появления стандартных контейнеров. Грузчики носили мешки, бочки и ящики разного размера. Это было долго и неудобно. Появление стандартного морского контейнера изменило всё: теперь неважно, что внутри (автозапчасти или бананы) — контейнер всегда имеет стандартные крепления и габариты. Его можно погрузить на любой корабль, поезд или грузовик.

    Docker делает то же самое для программного обеспечения.

    > Контейнеры помогают упрощать разработку, доставку и развертывание приложений. > > purpleschool.ru

    Docker против Виртуальных машин (VM)

    Многие путают контейнеры с виртуальными машинами, но архитектурно это разные вещи. Главное отличие — в уровне изоляции и использовании ресурсов.

    Виртуальные машины

    Виртуальная машина (VM) эмулирует целое аппаратное обеспечение. На физический сервер устанавливается Гипервизор, поверх которого запускаются гостевые операционные системы (Guest OS). Каждая VM имеет своё ядро, свои драйверы и выделенную память.

    Контейнеры

    Контейнеры используют ядро основной операционной системы (Host OS), но изолируют процессы друг от друга. Им не нужно загружать отдельную ОС для каждого приложения.

    Давайте выразим разницу в потреблении ресурсов через упрощенную формулу. Допустим, у нас есть приложений, каждое из которых требует ресурсов (память и CPU). Операционная система требует ресурсов.

    Для виртуальных машин общее потребление ресурсов () будет выглядеть так:

    Где — общие ресурсы для всех виртуальных машин, — количество приложений, — ресурсы для одного приложения, — ресурсы, потребляемые гостевой операционной системой.

    Для контейнеров Docker формула () выглядит иначе:

    Где — общие ресурсы при использовании Docker, — ресурсы хостовой ОС (одной на всех), — количество приложений, — ресурсы приложения, — минимальные накладные расходы на процесс контейнеризации (пренебрежимо малы по сравнению с целой ОС).

    Вывод: Контейнеры позволяют запустить больше приложений на том же железе, так как мы не тратим ресурсы на дублирование ядер операционных систем.

    > В отличие от виртуальных машин, которые эмулируют полноценную ОС, контейнеры используют ядро хостовой ОС, что делает их легковесными и быстрыми. > > purpleschool.ru

    Основные понятия Docker

    Чтобы работать с Docker, нужно понимать четыре ключевых термина: Dockerfile, Image (Образ), Container (Контейнер) и Registry (Реестр).

    1. Dockerfile (Рецепт)

    Это простой текстовый файл с инструкциями. В нём вы описываете, «как приготовить» ваше приложение. Например: «Возьми Python 3.10, скопируй мой код в папку /app, установи библиотеки из requirements.txt и запусти main.py».

    > Dockerfile – это текстовый файл, содержащий инструкции, как создать Docker Image. > > purpleschool.ru

    2. Image (Образ)

    Это результат выполнения инструкций из Dockerfile. Образ — это неизменяемый (read-only) шаблон. Представьте, что это установочный диск или «слепок» файловой системы. Если Dockerfile — это рецепт, то Image — это готовое замороженное блюдо.

    Важная особенность образов — слоистая структура. Каждый шаг в Dockerfile создает новый слой.

    > Образы неизменны – после создания образа его невозможно изменить. Можно только создать новый образ или применить изменения поверх него. > > beget.com

    3. Container (Контейнер)

    Это запущенная версия образа. Когда вы говорите Docker «запусти этот образ», он создает контейнер. В отличие от образа, контейнер имеет слой для записи, где приложение может создавать временные файлы. Если удалить контейнер, все данные внутри него (если они не сохранены во внешнее хранилище) исчезнут, но образ останется нетронутым.

    4. Registry (Реестр)

    Это место, где хранятся образы. Самый популярный публичный реестр — Docker Hub. Это как GitHub, но для скомпилированных пакетов, а не для исходного кода. Вы можете скачать официальный образ Python, PostgreSQL или Redis одной командой.

    Зачем это Python-разработчику?

    Согласно docker.com, миллионы разработчиков используют Python для создания масштабируемых приложений, и развертывание их в среде Docker дает ряд преимуществ.

    1. Изоляция зависимостей

    Вы можете разрабатывать проект на Python 2.7 (если вдруг приходится поддерживать легаси) и Python 3.12 на одной машине без конфликтов. Библиотеки устанавливаются внутрь контейнера и не засоряют вашу основную систему.

    2. Предсказуемость (Reproducibility)

    Если приложение работает в контейнере на вашем ноутбуке, оно с вероятностью 99.9% будет так же работать на сервере, у коллеги или в облаке. Окружение зафиксировано в Docker Image.

    3. Быстрый онбординг

    Новому разработчику не нужно тратить день на настройку базы данных, установку Redis и компиляцию зависимостей. Он просто пишет docker compose up и получает готовое окружение за пару минут.

    4. Удобство деплоя

    Вместо того чтобы настраивать сервер вручную (устанавливать Python, Nginx, настраивать systemd), вы просто доставляете туда Docker Image и запускаете его.

    > Деплой – это процесс выгрузки и запуска ПО на рабочем сервере или в облачной среде... можно взять Docker-контейнер Python, “упаковать” все зависимости приложения в одну сущность и, когда необходим деплой на сервер, запустить ее в изолированном окружении. > > beget.com

    Как это выглядит на практике?

    Рассмотрим простейший пример арифметики, чтобы понять экономию времени. Допустим, время настройки окружения вручную () и с Docker ().

    При ручной настройке на каждом новом сервере:

    Где — общее время ручной настройки, — время настройки ОС, — установка зависимостей, — конфигурация, — исправление ошибок несовместимости.

    С Docker вы тратите время один раз на создание образа (), а затем запуск () занимает секунды:

    Где — время развертывания с Docker, — время запуска контейнера (стремится к нулю по сравнению с ручной настройкой).

    Итоги

    В этом уроке мы познакомились с фундаментом контейнеризации. Главное, что нужно запомнить:

    * Docker решает проблему совместимости: «Работает у меня — работает везде». * Контейнер — это не виртуальная машина: Он использует ядро хоста, поэтому он легче и быстрее. * Dockerfile — это инструкция, Image — это шаблон, Container — это процесс. * Изоляция: Docker позволяет держать систему чистой и запускать разные версии Python и библиотек одновременно.

    В следующей статье мы перейдем от теории к практике и напишем ваш первый Dockerfile для Python-приложения.

    2. Создание Dockerfile: инструкции, сборка образа и зависимости

    Создание Dockerfile: инструкции, сборка образа и зависимости

    В предыдущей статье мы разобрали теорию: узнали, чем контейнеры отличаются от виртуальных машин, и поняли, зачем они нужны Python-разработчику. Теперь пришло время практики. Мы напишем ваш первый Dockerfile, соберем образ и запустим приложение.

    Сердцем любого контейнера является Dockerfile. Если Docker — это повар, то Dockerfile — это рецепт, по которому он готовит блюдо. От того, насколько грамотно составлен этот рецепт, зависит скорость сборки, размер итогового образа и безопасность приложения.

    Анатомия Dockerfile

    Dockerfile — это простой текстовый файл без расширения (он так и называется — Dockerfile). Он содержит набор инструкций, которые Docker выполняет последовательно, сверху вниз. Каждая инструкция создает новый слой в файловой системе образа.

    Согласно purpleschool.ru, Dockerfile служит инструкцией Docker о том, как создать контейнер, и почти всегда начинается с базового имиджа, который затем дополняется вашими собственными слоями.

    Рассмотрим ключевые инструкции, необходимые для Python-приложения.

    1. FROM: Фундамент

    Любой образ строится на базе другого образа. Для этого используется инструкция FROM.

    Здесь мы говорим: «Возьми официальный образ Python версии 3.11». Суффикс -slim означает, что мы используем облегченную версию Linux, из которой вырезаны лишние инструменты, что уменьшает размер образа.

    > Инструкция FROM задает базовый образ, из которого будет создан новый образ. Это первая инструкция в любом Dockerfile. > > javarush.com

    2. WORKDIR: Рабочая директория

    Эта инструкция создает директорию внутри образа и делает её текущей для всех последующих команд.

    Если вы не укажете WORKDIR, файлы могут оказаться в корневой папке контейнера, что считается плохой практикой. Это аналог команды cd в терминале, но с созданием папки.

    3. COPY: Доставка файлов

    Чтобы код попал внутрь образа, его нужно туда скопировать.

    Синтаксис: COPY <откуда> <куда>. Первая точка означает «текущая папка на вашем компьютере», вторая точка — «текущая папка внутри образа» (которую мы задали через WORKDIR).

    4. RUN: Установка зависимостей

    Инструкция RUN запускает команды во время сборки образа. Чаще всего она используется для установки библиотек.

    Флаг --no-cache-dir важен: он говорит pip не сохранять кэш скачанных пакетов внутри образа, что позволяет уменьшить его итоговый размер.

    5. CMD: Запуск приложения

    Эта инструкция говорит контейнеру, что делать, когда он запустится.

    Рекомендуется использовать формат JSON-массива (exec form), так как он более надежен и предсказуем в обработке сигналов остановки (например, Ctrl+C), чем строковый формат.

    Практика: Собираем Python-приложение

    Давайте создадим минимальное приложение. Вам понадобится пустая папка и три файла.

    Шаг 1. Подготовка файлов

  • main.py (наш код):
  • requirements.txt (зависимости):
  • Пока оставим пустым или добавим любую библиотеку для примера, например:

  • Dockerfile:
  • Шаг 2. Сборка образа (Build)

    Откройте терминал в папке с файлами и выполните команду:

    Разберем команду: * docker build — команда сборки. * -t my-python-app — флаг tag (тег), дающий имя нашему образу. * . (точка в конце) — контекст сборки. Это путь к файлам, которые Docker может видеть. Точка означает «текущая директория».

    Шаг 3. Запуск контейнера (Run)

    После успешной сборки запустите контейнер:

    Флаг --rm удалит контейнер после остановки, чтобы не засорять диск. Вы должны увидеть вывод: Приложение запущено! Версия Python: 3.11...

    Оптимизация: Слои и Кэширование

    Вы могли заметить, что в примере выше мы сначала скопировали requirements.txt, установили библиотеки, и только потом скопировали остальной код (COPY . .). Почему не скопировать всё сразу?

    Это связано с механизмом слоев (Layers). Docker кэширует каждый шаг сборки. Если файл на определенном шаге не изменился, Docker использует кэш вместо повторного выполнения команды.

    Давайте представим время сборки () как сумму времени выполнения каждого слоя:

    Где: * — общее время сборки образа. * — время загрузки базового слоя (FROM). * — время установки зависимостей (RUN pip install). * — время копирования кода приложения (COPY . .).

    В реальной разработке вы меняете код () постоянно, а зависимости () — редко. Если мы скопируем всё одной командой COPY . ., то любое изменение в коде (даже лишний пробел) инвалидирует кэш для всех последующих команд, включая тяжелый pip install.

    Разделяя копирование, мы добиваемся того, что при изменении кода Docker берет тяжелый слой с зависимостями из кэша:

    Где стремится к нулю, так как используется готовый кэшированный слой.

    > Для уменьшения количества слоев, уменьшения размера image и ускорения сборки рекомендуется объединять несколько команд в одну инструкцию RUN. > > javarush.com

    Однако, разделение COPY для requirements.txt и кода — это исключение из правила объединения, сделанное ради кэширования.

    Файл .dockerignore

    Перед отправкой контекста (файлов) в Docker daemon, система смотрит на файл .dockerignore. Он работает так же, как .gitignore.

    Зачем он нужен?

  • Скорость: Не копировать лишние файлы (например, локальную папку venv или .git, которые могут весить сотни мегабайт).
  • Безопасность: Не скопировать случайно файлы с паролями (.env, ключи).
  • Пример правильного .dockerignore для Python:

    Если вы случайно скопируете локальное виртуальное окружение venv в контейнер (который работает на Linux), а ваш компьютер на Windows или macOS, приложение сломается из-за несовместимости бинарных файлов.

    Итоги

    Мы создали ваш первый воспроизводимый образ. Теперь ваше приложение упаковано вместе с интерпретатором и библиотеками.

  • Dockerfile — это пошаговая инструкция. Порядок команд важен.
  • Основные команды: FROM (база), WORKDIR (папка), COPY (файлы), RUN (сборка/установка), CMD (запуск).
  • Кэширование: Всегда копируйте requirements.txt и устанавливайте зависимости до копирования основного кода приложения. Это экономит минуты при каждой пересборке.
  • Контекст: Точка в конце команды docker build . указывает Docker, где искать файлы.
  • Игнорирование: Всегда создавайте .dockerignore, чтобы не тянуть в контейнер мусор и локальные виртуальные окружения.
  • В следующей статье мы разберем, как управлять сложными приложениями, состоящими из нескольких сервисов (например, Python + база данных), с помощью Docker Compose.