Приложение в коробке
Обычная проблема разработки: программа работает у автора и не запускается на сервере, потому что различаются версии библиотек и настройки системы. Docker решает её, упаковывая приложение и всё необходимое для запуска в единый образ. Образ можно запустить одинаково на ноутбуке, сервере и в облаке.
Запущенный экземпляр образа называют контейнером. Из одного образа получают сколько угодно контейнеров, каждый со своей изолированной средой.
Именно в этом смысл лозунга «работает у меня»: контейнер переносит не только код, но и версии библиотек, интерпретатора и системных утилит.
Изоляция без виртуальной машины
Контейнер не содержит собственного ядра. Он использует ядро хост-системы Linux, а изоляцию обеспечивают его механизмы: пространства имён отделяют процессы, сеть и файловую систему, а группы контроля cgroups ограничивают процессор и память. Поэтому контейнеры запускаются за секунды и занимают мало места, в отличие от виртуальных машин с полноценной ОС, описанных на странице про гипервизор.
Обратная сторона: изоляция слабее, потому что ядро общее. Контейнер с чрезмерными привилегиями может представлять угрозу для хоста.
Ограничения ресурсов задаются при запуске: можно указать лимит памяти и долю процессора, чтобы один контейнер не съел весь сервер.
Образы и слои
Образ собирают по инструкциям из Dockerfile: базовая система, установка пакетов, копирование кода, команда запуска. Каждая инструкция создаёт слой, а слои кэшируются и переиспользуются. Если изменился только код, пересобирается лишь верхний слой, поэтому сборка быстра.
Готовые образы хранятся в реестрах, самый известный из которых Docker Hub. Образ определяется именем и тегом, например версией. Использование тега latest удобно, но затрудняет воспроизводимость.
Тег также определяет версию: образ с тегом конкретного релиза остаётся неизменным, а метка latest со временем указывает на разные сборки, и одна и та же команда запуска в разные дни может дать разные результаты.
Данные, сеть, порты
Файловая система контейнера временна: при удалении контейнера изменения пропадают. Постоянные данные хранят в томах или подключённых каталогах хоста. Сеть контейнера по умолчанию отдельная, а доступ снаружи открывают публикацией порта: параметр -p связывает порт хоста с портом контейнера.
Несколько связанных контейнеров, например приложение и база данных, описывают одним файлом Docker Compose и запускают одной командой.
Просмотреть, какие порты и тома использует контейнер, помогает команда docker inspect, которая выводит подробную конфигурацию в структурированном виде.
Заблуждения и как попробовать
Заблуждение первое: контейнер — это лёгкая виртуальная машина. По устройству нет, это обычный процесс с ограничениями. Второе: контейнер автоматически безопасен. Публикация порта может действовать мимо правил файрвола хоста, поскольку Docker сам добавляет правила, поэтому доступ стоит ограничивать явно. Третье: в контейнер удобно класть секреты в образ, хотя их лучше передавать при запуске.
Чтобы посмотреть у себя: команда docker ps показывает работающие контейнеры, docker images — образы, docker logs — вывод приложения.
Полезная проверка, если контейнер не запускается: посмотреть его журнал командой docker logs и код завершения, а затем выполнить образ интерактивно, чтобы увидеть ошибку в своём окружении.
Безопасность контейнеров
Запуск процессов в контейнере от имени root внутри контейнера не так опасен, как на хосте, но всё же увеличивает риск. Рекомендуется указывать непривилегированного пользователя в Dockerfile, использовать минимальные базовые образы и обновлять их: устаревший образ приносит с собой уязвимые библиотеки.
Не стоит монтировать в контейнер сокет управления Docker и запускать его в привилегированном режиме без необходимости: это фактически даёт полный доступ к хосту. Секреты, вроде паролей и ключей, передают через переменные окружения, специальные механизмы секретов или тома, а не записывают в слои образа, где их можно извлечь.
Проверить образ на известные уязвимости позволяют сканеры, которые сопоставляют состав пакетов с базами CVE.