1. Основы Docker: жизненный цикл контейнеров и базовое администрирование
Основы Docker: жизненный цикл контейнеров и базовое администрирование
Представьте, что вы разработали приложение, которое идеально работает на вашем ноутбуке, но мгновенно «ломается» при переносе на сервер или компьютер коллеги. Причина почти всегда кроется в разнице окружений: не та версия Python, отсутствует специфическая системная библиотека или по-другому настроены переменные среды. Docker решает эту проблему, предлагая концепцию контейнеризации — упаковки приложения вместе со всем его миром (зависимостями, конфигами и библиотеками) в изолированный юнит, который запустится везде одинаково.
Архитектура изоляции и первый запуск
Прежде чем вводить команды, важно понять, что именно происходит в системе, когда мы говорим «контейнер». В отличие от виртуальных машин, которые эмулируют аппаратное обеспечение и запускают внутри себя полноценную гостевую операционную систему, контейнеры Docker разделяют ядро основной ОС (Host OS). Это делает их невероятно легкими: запуск занимает доли секунды, а потребление оперативной памяти минимально.
Для проверки того, что ваша среда готова к работе, используется команда:
docker version
Она выводит информацию не только о версии клиента, но и о состоянии Docker Engine (серверной части). Если вы видите данные в обоих разделах (Client и Server), значит, демон Docker запущен и готов принимать команды.
Самый простой способ проверить работоспособность — запустить классический тестовый образ:
docker run hello-world
В этот момент происходит целая цепочка событий. Docker ищет образ hello-world локально. Не найдя его, он обращается к Docker Hub (публичному реестру), скачивает слои образа, создает на их основе контейнер и запускает процесс внутри него. Этот процесс выводит текст в консоль и завершается. Контейнер переходит в статус «Exited», но он не исчезает из системы бесследно.
Управление жизненным циклом: от создания до удаления
Контейнер — это не статичный файл, а активный (или остановленный) процесс. Чтобы видеть, что происходит в вашей системе прямо сейчас, используется команда:
docker ps
По умолчанию она показывает только запущенные контейнеры. Однако, как мы выяснили на примере с hello-world, многие контейнеры могут быть остановлены, но при этом они продолжают занимать место и хранить свое состояние. Чтобы увидеть «полную картину», включая завершенные процессы, добавьте флаг -a (all):
docker ps -a
В выводе этой команды вы заметите столбец STATUS. Контейнер может находиться в состояниях Created, Up (запущен), Paused, Exited или Dead. Понимание этих статусов критично для администрирования. Например, если ваше приложение упало с ошибкой, оно перейдет в Exited, и вам нужно будет выяснить причину.
Чтение логов и диагностика
Когда контейнер работает в фоновом режиме или уже завершился с ошибкой, нам нужно заглянуть внутрь его стандартного потока вывода (stdout). Для этого предназначена команда logs:
docker logs <container_id_or_name>
Если приложение генерирует много данных, удобно использовать флаг -f (follow), который позволяет следить за логами в реальном времени, подобно команде tail -f в Linux. Это незаменимо при отладке сетевых соединений или проверке того, поднялась ли база данных.
Принудительная остановка и очистка
Иногда процессы внутри контейнера зависают или ведут себя некорректно. Команда docker stop посылает процессу сигнал SIGTERM, давая ему время (обычно 10 секунд) на корректное завершение работы (сохранение данных, закрытие соединений). Если же приложение не реагирует, применяется «грубая сила»:
docker kill <container_id>
Эта команда отправляет SIGKILL, мгновенно обрывая выполнение процесса. После того как вы поэкспериментировали с десятком разных образов, ваша система неизбежно заполнится «мусором» — остановленными контейнерами, которые больше не нужны. Вместо того чтобы удалять каждый вручную через docker rm, можно воспользоваться командой массовой очистки:
docker container prune
Она удалит все контейнеры со статусом Exited. Будьте осторожны: если в остановленном контейнере были важные данные, которые не были вынесены в тома (volumes), они будут безвозвратно утеряны.
Интерактивный режим и проброс портов
Запуск hello-world полезен для теста, но в реальной разработке нам нужно взаимодействовать с контейнером. Допустим, мы хотим запустить веб-сервер Nginx. Если мы просто напишем docker run nginx, контейнер заберет на себя управление терминалом, и мы будем видеть логи сервера. Чтобы запустить его «в фоне», используется флаг -d (detached):
docker run -d nginx
Теперь сервер работает, но мы не можем на него зайти через браузер. Почему? Потому что контейнер живет в своей изолированной сети. Чтобы связать порт внутри контейнера с портом на вашем физическом компьютере, используется флаг -p (publish):
docker run -d -p 8080:80 nginx
Здесь мы говорим: «Весь трафик, приходящий на порт 8080 моего компьютера, перенаправляй на порт 80 внутри контейнера». Теперь, открыв localhost:8080, вы увидите приветственную страницу Nginx.
Иногда возникает необходимость «войти» внутрь уже работающего контейнера, чтобы проверить конфигурационные файлы или наличие установленных пакетов. Для этого используется связка exec с интерактивным режимом:
docker exec -it <container_name> /bin/bash
Флаги -i (interactive) и -t (tty) создают полноценную терминальную сессию. Вы оказываетесь внутри изолированной файловой системы контейнера. Важно помнить: любые изменения, внесенные вами вручную через exec (например, установка пакета через apt-get), исчезнут, как только контейнер будет удален и пересоздан из образа. Контейнеры должны быть эфемерными.
Работа с образами: фундамент контейнеризации
Образ (Image) — это неизменяемый шаблон, из которого создается контейнер. Если провести аналогию с программированием, то образ — это класс, а контейнер — экземпляр этого класса.
Для просмотра списка всех скачанных или собранных образов используйте:
docker image ls
Вы увидите колонки REPOSITORY (имя), TAG (версия, например latest или 18-bookworm) и IMAGE ID. Чтобы создать собственный образ, используется файл Dockerfile и команда сборки.
Предположим, у нас есть простейший скрипт. Команда сборки выглядит так:
docker build . -t my-app:v1
Здесь . указывает на текущую директорию как контекст сборки, а -t (tag) задает человекочитаемое имя и версию. В процессе сборки Docker выполняет команды из Dockerfile послойно. Каждый слой кэшируется, что позволяет собирать обновленные версии образа за считанные секунды, если изменения коснулись только кода, а не системных зависимостей.
Персистентность данных и переменные окружения
Контейнеры по своей природе не сохраняют данные после удаления. Если вы запустите базу данных PostgreSQL, запишете в нее миллион строк, а затем удалите контейнер командой docker rm, ваши данные исчезнут. Для решения этой проблемы используются тома (volumes) и монтирование (mounts).
Рассмотрим запуск PostgreSQL версии 18-bookworm. Для работы базы данных нам обязательно нужно передать ей пароль администратора через переменную окружения (флаг -e):
docker run -d --name my-db -e POSTGRES_PASSWORD=secret_pass -v my_db_data:/var/lib/postgresql/data postgres:18-bookworm
Разберем флаг -v (или --volume):
my_db_data — это имя тома в хранилище Docker./var/lib/postgresql/data — путь внутри контейнера, где PostgreSQL хранит свои файлы.Теперь, даже если вы удалите контейнер my-db, данные останутся в томе my_db_data. При запуске нового контейнера с тем же флагом -v, база данных «подхватит» все старые записи.
От одиночных контейнеров к Docker Compose
Запуск через docker run удобен для простых задач, но современные приложения состоят из множества сервисов: фронтенд, бэкенд, база данных, кэш (Redis). Писать огромные bash-скрипты с десятками флагов -p, -v и -e — путь к ошибкам.
Здесь на сцену выходит Docker Compose. Это инструмент, который позволяет описать всю вашу инфраструктуру в одном YAML-файле. Вместо длинной команды в терминале вы создаете файл docker-compose.yml:
Теперь управление всей системой сводится к трем основным командам:
* docker compose up — читает файл, скачивает образы, создает сети и запускает все сервисы.
* docker compose up -d — запускает всю связку в фоновом режиме.
* docker compose down — останавливает и полностью удаляет контейнеры и сети, созданные для этого проекта (но сохраняет тома, если они описаны как внешние или не указан флаг удалений волюмов).
Разница между docker run и docker compose фундаментальна: первый управляет отдельными «кирпичиками», второй — всем «зданием» целиком. Compose автоматически создает внутреннюю сеть, где сервисы могут обращаться друг к другу по именам (например, бэкенд может подключиться к базе по хосту db, а не по IP-адресу).
Практические сценарии и администрирование
В повседневной работе администратора или разработчика важно уметь быстро диагностировать состояние системы. Если docker ps показывает, что контейнер постоянно перезапускается (статус Restarting), первым делом нужно проверить логи. Часто ошибка кроется в неправильно переданных переменных окружения или конфликте портов (если порт 8080 уже занят другим приложением на хосте).
Еще один нюанс — использование тегов образов. Никогда не используйте тег latest в продакшене. Это «плавающий» тег, который сегодня указывает на одну версию, а завтра — на другую. Всегда фиксируйте версии, например postgres:18-bookworm, чтобы гарантировать воспроизводимость среды.
Docker — это не просто способ запуска софта, это стандарт упаковки. Освоив базовые команды управления жизненным циклом и логику проброса ресурсов (портов и данных), вы закладываете фундамент для построения сложных отказоустойчивых систем. В следующих этапах мы глубже погрузимся в оптимизацию Dockerfile и тонкую настройку сетей, но база остается неизменной: контейнер должен быть изолированным, заменяемым и предсказуемым.