Проблема, с которой все начинается:
Классика жанра. Разработчик пишет сервис на ноутбуке с Ubuntu 24.04 и Python 3.12. На сервере стоит старый Debian с Python 3.9 и другой версией libpq. Локально все работает, на проде падает. Дальше сутки переписки в чате и фраза "у меня же работает".
Корень проблемы в том, что приложение это не только код. Это еще интерпретатор конкретной версии, системные библиотеки, переменные окружения, конфиги. Docker упаковывает все это в один артефакт, образ, и этот образ запускается одинаково на любой машине, где есть Docker. Ноутбук разработчика, CI-сервер, прод, без разницы.
Контейнер это не виртуалка:
Виртуальная машина эмулирует железо и тащит за собой целую гостевую ОС. Гигабайты на диске, десятки секунд на запуск, заметный кусок памяти просто за сам факт существования.
Контейнер устроен иначе. Это обычный процесс на ядре хост-системы, которому средствами Linux ограничили видимость и ресурсы. Изоляцией занимаются namespaces (свое дерево процессов, своя сеть, своя файловая система) и cgroups (лимиты на CPU и память). Гостевой ОС нет, поэтому контейнер стартует за доли секунды и в простое почти ничего не ест.
Отсюда важное следствие: контейнеры работают на ядре Linux. На Windows Docker Desktop запускает их внутри WSL2, это легкая виртуалка с настоящим Linux-ядром, которую поддерживает сама Microsoft. На macOS Docker Desktop точно так же поднимает легкую Linux-виртуалку и гоняет контейнеры внутри нее. В повседневной работе это почти не ощущается, кроме одного места: bind mounts, то есть проброс папки с хоста внутрь контейнера. Файловые операции там ходят через границу виртуалки, и на проектах с тысячами мелких файлов (node_modules, vendor) ввод-вывод проседает в разы по сравнению с нативным Linux. Помните об этом, когда будете мерить производительность: именованные тома живут внутри виртуалки и работают на полной скорости, разницу подробно разберем в главе 5.
Три слова, на которых все держится:
Образ (image), неизменяемый шаблон: файловая система с приложением и зависимостями плюс метаданные. Внутри образ собран из слоев, поэтому общие части скачиваются один раз и переиспользуются, устройство слоев разберем в главе 3. Контейнер, запущенный экземпляр образа, из одного образа можно поднять хоть десять контейнеров. Реестр (registry), хранилище образов, главный публичный называется Docker Hub, про него тоже глава 3. Если знакомы с ООП, аналогия класс/объект здесь работает почти дословно.
Как это выглядит на практике. Поднять nginx без Docker значит ставить пакет, разбираться с конфигами, а потом думать, как его чисто снести:
Код: Выделить всё
sudo apt update
sudo apt install nginx
# плюс конфиги, плюс зачистка хвостов при удаленииКод: Выделить всё
docker run -d -p 8080:80 nginx:1.28Еще показательный сценарий: два проекта, одному нужен PostgreSQL 14 (легаси, которое пока не успели мигрировать), другому 17. Без контейнеров держать обе версии на одной машине это боль. С контейнерами:
Код: Выделить всё
docker run -d --name pg17 -e POSTGRES_PASSWORD=secret -p 127.0.0.1:5432:5432 postgres:17
docker run -d --name pg14 -e POSTGRES_PASSWORD=secret -p 127.0.0.1:5433:5432 postgres:14Где Docker дает максимум:
Одинаковое окружение на всех стадиях, от ноутбука до прода. Онбординг новичка за полчаса: склонировал репозиторий, поднял окружение одной командой (этим займемся в главе 8 про Compose). Плотная упаковка сервисов: на VPS за 400-500 рублей в месяц спокойно живут пять-шесть контейнеров, каждый со своим окружением.
Честности ради, Docker не везде уместен. Десктопные GUI-приложения в нем гонять неудобно, Windows-софт это отдельная ниша, а изоляция контейнера слабее, чем у полноценной VM. К безопасности вернемся в главе 11.
Типичные грабли:
Первая ловушка: воспринимать контейнер как маленький сервер и запихивать туда ssh, cron и три сервиса разом. Рабочее правило для старта: один контейнер, один процесс.
Вторая: ждать, что данные переживут контейнер. Не переживут. Контейнер по своей природе одноразовый, удалили и подняли заново. Для данных есть тома, разберем их в главе 5.
Третья: путать образ и контейнер. Удалили контейнер, а образ остался на диске и ест место. Команды очистки увидим в главе 2.
Четвертая: тег latest. Вокруг него живет миф, что это "самая свежая версия в реестре". На деле latest просто тег по умолчанию, Docker подставляет его, когда тег не указан явно. Сам по себе он никуда не двигается: пуш образа с явным тегом, скажем 1.28.1, latest не трогает, он сместится только когда кто-то явно запушит образ под тегом latest. И стабильности он не обещает: это обычный мутабельный тег, сегодня под ним одна версия, завтра мейнтейнер запушил другую, и сборка внезапно ломается. В примерах выше версии указаны явно, и эту привычку лучше завести с первого дня.
Что усвоили:
Docker упаковывает приложение вместе с окружением в образ, а контейнер это изолированный процесс на ядре хоста, а не легкая виртуалка. Главные слова: образ, контейнер, реестр. В следующей главе ставим Docker и запускаем первый контейнер руками.