Docker с нуля: Основы контейнеризации

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

1. Введение в экосистему Docker: архитектура, установка и основные понятия

Введение в экосистему Docker: архитектура, установка и основные понятия

Добро пожаловать в курс Docker с нуля. Если вы когда-либо слышали фразу «на моем компьютере это работает, я не знаю, почему не работает у тебя», то вы уже понимаете проблему, которую решает Docker. В этой вводной статье мы разберем фундамент контейнеризации, узнаем, чем она отличается от виртуализации, и подготовим рабочее окружение.

Что такое Docker и зачем он нужен?

Docker — это открытая платформа для разработки, доставки и запуска приложений. Она позволяет отделить ваше приложение от инфраструктуры, чтобы вы могли быстро доставлять программное обеспечение. С Docker вы можете управлять своей инфраструктурой так же, как управляете своими приложениями.

Главная идея Docker — контейнеризация. Представьте себе стандартный грузовой контейнер. Неважно, что внутри: автомобили, зерно или электроника. Контейнер имеет стандартные размеры и крепления. Его можно погрузить на корабль, поезд или грузовик, и везде он будет обрабатываться одинаково. Docker делает то же самое для кода: он упаковывает приложение со всеми его зависимостями (библиотеками, конфигурациями, средой выполнения) в единый блок, который гарантированно запустится на любом сервере, где установлен Docker.

Проблема, которую мы решаем

До появления контейнеров разработчики часто сталкивались с «адом зависимостей» (Dependency Hell). Например:

  • Вы пишете код на Python 3.9.
  • На сервере установлен Python 3.6.
  • Ваше приложение падает из-за несовместимости версий.
  • Обновление Python на сервере ломает другие приложения, которые там работали.
  • Docker изолирует каждое приложение в своем собственном контейнере, устраняя конфликты версий.

    Контейнеры против Виртуальных машин

    Часто новички путают контейнеры с виртуальными машинами (VM). Хотя цель у них похожа — изоляция приложений, — работают они совершенно по-разному.

    !Сравнение архитектуры: Виртуальные машины (слева) требуют полноценную ОС для каждого приложения, тогда как контейнеры (справа) используют общее ядро хостовой системы.

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

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

    * Полную копию операционной системы. * Виртуальные копии аппаратного обеспечения. * Приложение и его библиотеки.

    Это надежно, но «тяжело». Каждая VM занимает гигабайты места и требует много оперативной памяти просто для работы своей ОС.

    Контейнеры

    Контейнеры работают иначе. Они используют ядро операционной системы хоста (Host OS). Вместо гипервизора здесь работает Docker Engine. Контейнеры:

    * Легковесные: занимают мегабайты, а не гигабайты. * Быстрые: запускаются за секунды, так как не нужно загружать ОС. * Портативные: легко переносятся между средами.

    > Контейнеры виртуализируют операционную систему, а виртуальные машины виртуализируют аппаратное обеспечение.

    Архитектура Docker

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

    !Архитектура Docker: Клиент отправляет команды Демону, который управляет объектами и взаимодействует с Реестром.

    Основные компоненты

  • Docker Client (Клиент): Это то, с чем вы взаимодействуете в терминале, вводя команду docker. Клиент отправляет запросы демону через REST API.
  • Docker Daemon (Демон dockerd): Это «мозг» системы, работающий в фоновом режиме. Он слушает запросы от API и управляет объектами Docker (образами, контейнерами, сетями).
  • Docker Registry (Реестр): Это хранилище образов. Самый известный публичный реестр — Docker Hub. Когда вы просите Docker запустить программу, которой у вас нет локально, он ищет её именно там.
  • Ключевые понятия: Образ, Контейнер, Dockerfile

    Чтобы начать работать, нужно выучить три главных термина. Представьте, что мы готовим пирог.

    1. Dockerfile (Рецепт)

    Это простой текстовый файл с инструкциями. В нем написано, какую операционную систему взять за основу, какие программы установить, какие файлы скопировать и какую команду запустить на старте.

    Пример простого Dockerfile:

    2. Image (Образ / Формочка для выпечки)

    Когда вы «собираете» (build) Dockerfile, получается Image (Образ). Это неизменяемый (read-only) шаблон. Образ содержит всё необходимое для запуска приложения: код, среду выполнения, библиотеки.

    * Образы строятся слоями (layers). Каждый слой — это изменение файловой системы. * Образы хранятся на диске и могут передаваться через реестр.

    3. Container (Контейнер / Готовый пирог)

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

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

    Установка Docker

    Процесс установки зависит от вашей операционной системы. На текущий момент стандартом для разработки является Docker Desktop.

    Windows

    Для пользователей Windows 10/11:

  • Убедитесь, что у вас включена виртуализация в BIOS.
  • Рекомендуется использовать WSL 2 (Windows Subsystem for Linux). Это позволяет запускать Linux-контейнеры нативно.
  • Скачайте и установите Docker Desktop for Windows с официального сайта.
  • macOS

    Для пользователей Mac (Intel или Apple Silicon M1/M2/M3):

  • Скачайте Docker Desktop for Mac.
  • Установите как обычное приложение (перетащив в Applications).
  • Docker Desktop автоматически оптимизирован под архитектуру вашего процессора.
  • Linux

    На Linux (например, Ubuntu) обычно устанавливают Docker Engine напрямую, без графического интерфейса Docker Desktop (хотя он тоже доступен), так как Linux является «родной» средой для Docker.

    Пример установки на Ubuntu (упрощенно):

    > Важно: После установки на Linux не забудьте добавить своего пользователя в группу docker, чтобы не писать sudo перед каждой командой: sudo usermod -aG docker $USER.

    Первая практика: Hello World

    Давайте проверим, что установка прошла успешно. Откройте терминал (PowerShell, Terminal или iTerm) и введите команду:

    Что произойдет после нажатия Enter?

  • Поиск: Docker клиент спросит у демона: «У нас есть образ hello-world локально?»
  • Скачивание (Pull): Если образа нет (а его нет, вы же только установили Docker), демон скачает его из Docker Hub.
  • Запуск: Демон создаст контейнер из этого образа и запустит его.
  • Вывод: Программа внутри контейнера напечатает приветственное сообщение и завершит работу.
  • Вывод должен выглядеть примерно так:

    Если вы видите этот текст — поздравляю! Вы официально вошли в мир контейнеризации.

    Жизненный цикл контейнера

    Понимание состояний контейнера поможет вам в отладке.

  • Created: Контейнер создан, но не запущен.
  • Running: Процесс внутри контейнера работает.
  • Paused: Все процессы контейнера приостановлены.
  • Exited (Stopped): Процесс внутри контейнера завершился (сам или был убит).
  • Посмотреть запущенные контейнеры можно командой:

    Посмотреть все контейнеры (включая остановленные):

    Заключение

    Сегодня мы разобрали фундамент. Мы узнали, что Docker решает проблему совместимости окружений, выяснили, что контейнеры легче виртуальных машин, так как используют общее ядро, и познакомились с триадой Dockerfile -> Image -> Container.

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

    Готовы проверить свои знания? Переходите к заданиям!

    2. Работа с контейнерами и образами: базовые команды CLI и Docker Hub

    Работа с контейнерами и образами: базовые команды CLI и Docker Hub

    В предыдущей статье мы познакомились с архитектурой Docker, установили его на компьютер и запустили наш первый тестовый контейнер hello-world. Это было похоже на магию: одна команда — и программа работает. Но чтобы магия стала инструментом, нужно разобраться, как она устроена изнутри.

    Сегодня мы переходим к практике. Мы научимся искать готовые программы в глобальном реестре, скачивать их, запускать в фоновом режиме, «пробрасывать» порты и управлять жизненным циклом контейнеров через командную строку (CLI — Command Line Interface).

    Docker Hub: Супермаркет приложений

    Прежде чем запустить приложение, его нужно откуда-то взять. В мире Docker существует понятие Registry (Реестр) — это место, где хранятся образы. Самый популярный и используемый по умолчанию реестр — это Docker Hub.

    Представьте Docker Hub как App Store или Google Play, только для серверных приложений. Там можно найти всё: от баз данных (PostgreSQL, MySQL) и веб-серверов (Nginx, Apache) до языков программирования (Python, Node.js) и операционных систем (Ubuntu, Alpine).

    Структура имени образа

    Когда мы говорим Docker'у «скачай образ», мы используем имя. Полное имя образа обычно состоит из трех частей:

    пользователь/название_образа:тег

  • Пользователь (User): Имя аккаунта, который загрузил образ. Например, bitnami/postgresql. Если этой части нет (например, просто python или nginx), значит, это Официальный образ (Official Image). Официальные образы поддерживаются командой Docker и разработчиками самой технологии, они наиболее безопасны и оптимизированы.
  • Название образа (Image Name): Собственно, имя приложения.
  • Тег (Tag): Версия образа. Если вы не укажете тег, Docker по умолчанию подставит тег latest (последняя версия).
  • Пример: * nginx — официальный образ Nginx, последняя версия. * python:3.9 — официальный образ Python версии 3.9. * mysql:5.7 — официальный образ MySQL версии 5.7.

    Работа с образами (Images)

    Образ — это «формочка» или шаблон, из которого создаются контейнеры. Он хранится на вашем жестком диске. Давайте научимся управлять ими.

    Скачивание образа (docker pull)

    Команда pull позволяет скачать образ из Docker Hub, не запуская его. Это полезно, если вы хотите заранее подготовить окружение.

    Давайте скачаем образ веб-сервера Nginx:

    Вы увидите, как Docker скачивает несколько слоев (layers). Поскольку образы состоят из слоев, если у вас уже есть какой-то базовый слой (например, Linux Debian) от другого образа, Docker не будет качать его заново. Это экономит трафик и место на диске.

    Просмотр локальных образов (docker images)

    Чтобы посмотреть, какие «формочки» уже лежат у вас на компьютере, используйте команду:

    Вывод будет содержать таблицу: * REPOSITORY: Имя образа (например, nginx). * TAG: Версия (например, latest). * IMAGE ID: Уникальный идентификатор образа (хеш). * CREATED: Когда этот образ был создан разработчиком (не когда вы его скачали). * SIZE: Сколько места он занимает.

    Удаление образа (docker rmi)

    Если образ вам больше не нужен, его можно удалить, чтобы освободить место. Команда звучит как rmi (remove image).

    > Важно: Вы не сможете удалить образ, если из него в данный момент запущен или просто существует (даже остановленный) контейнер. Сначала нужно удалить контейнер.

    Работа с контейнерами (Containers)

    Контейнер — это живой процесс. Это то, ради чего мы здесь собрались. Самая главная команда в Docker — это docker run.

    Запуск контейнера (docker run)

    Команда docker run на самом деле делает три вещи:

  • Ищет образ локально. Если нет — делает docker pull.
  • Создает контейнер из образа (docker create).
  • Запускает его (docker start).
  • Попробуем запустить Nginx:

    После ввода этой команды ваш терминал «зависнет». Почему? Потому что Nginx запустился и выводит свои логи прямо вам в консоль. Он захватил ваш терминал. Чтобы остановить его, нужно нажать Ctrl+C.

    Но в реальной жизни мы хотим, чтобы сервисы работали в фоне.

    Запуск в фоновом режиме (Detached mode)

    Для запуска в фоне используется флаг -d (detach — отсоединить).

    В ответ Docker выдаст длинную строку букв и цифр — это полный ID запущенного контейнера. Терминал останется свободным, а Nginx будет работать где-то в недрах Docker Engine.

    Просмотр запущенных контейнеров (docker ps)

    Как узнать, что работает прямо сейчас? Используйте команду ps (process status).

    Вы увидите: * CONTAINER ID: Короткий ID контейнера. * IMAGE: Из какого образа запущен. * STATUS: Сколько времени он работает (Up X seconds). * NAMES: Имя контейнера. Если вы не дали имя сами, Docker придумает смешное имя (например, boring_wozniak или happy_einstein).

    Чтобы увидеть все контейнеры, включая остановленные, добавьте флаг -a:

    Именование контейнеров (--name)

    Обращаться к контейнеру по случайному имени или ID неудобно. Лучше давать свои имена с помощью флага --name.

    Теперь у нас есть контейнер с именем my-web-server.

    Проброс портов (Port Mapping)

    Это одна из самых важных концепций. По умолчанию контейнер изолирован от внешнего мира. Даже если внутри контейнера Nginx слушает порт 80, вы не сможете открыть его в браузере своего компьютера, просто введя localhost.

    Нужно создать «мост» между портом вашего компьютера (хоста) и портом внутри контейнера. Это делается флагом -p.

    Синтаксис: -p <порт_хоста>:<порт_контейнера>

    !Визуализация того, как запрос с вашего компьютера попадает внутрь изолированного контейнера через механизм Port Mapping.

    Давайте запустим Nginx так, чтобы он был доступен в браузере:

    Разберем команду: * -d: Запустить в фоне. * -p 8080:80: Все запросы, приходящие на порт 8080 вашего компьютера, перенаправлять на порт 80 внутри контейнера. * --name my-site: Назвать контейнер my-site. * nginx: Использовать образ nginx.

    Теперь откройте браузер и перейдите по адресу http://localhost:8080. Вы должны увидеть приветственную страницу: "Welcome to nginx!".

    Управление жизненным циклом

    Теперь, когда контейнер работает, мы можем им управлять.

    Остановка (docker stop)

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

    Запуск остановленного (docker start)

    Если контейнер уже создан, но остановлен, не нужно делать run (это создаст новый). Нужно делать start.

    Перезагрузка (docker restart)

    Удобно, если приложение зависло или вы изменили конфигурацию.

    Удаление контейнера (docker rm)

    Чтобы удалить контейнер, он должен быть остановлен. Команда rm удаляет сам контейнер, но не образ.

    Если вы хотите удалить работающий контейнер «силой», можно добавить флаг -f (force), но это не рекомендуется для баз данных:

    Вход внутрь контейнера и логи

    Иногда нужно посмотреть, что происходит внутри.

    Просмотр логов (docker logs)

    Если ваше приложение не работает, первое, что нужно сделать — посмотреть логи. Это то, что приложение выводит в стандартный вывод (stdout).

    Можно добавить флаг -f (follow), чтобы следить за логами в реальном времени (как tail -f в Linux).

    Выполнение команд внутри (docker exec)

    Вы можете запустить дополнительный процесс внутри уже работающего контейнера. Чаще всего это используется, чтобы получить доступ к терминалу внутри контейнера.

    Разберем флаги: * -i (interactive): Оставляет поток ввода открытым. * -t (tty): Выделяет псевдо-терминал. * bash: Команда, которую мы хотим запустить (оболочка командной строки).

    После этого ваше приглашение командной строки изменится (например, на root@a1b2c3d4e5f6:/#). Теперь вы находитесь внутри контейнера. Вы можете проверить файлы, установить утилиты (хотя они исчезнут после пересоздания контейнера) и проверить конфигурацию. Чтобы выйти, наберите exit.

    > Если в контейнере нет bash (например, в образах Alpine Linux), попробуйте использовать sh: > docker exec -it my-site sh

    Очистка системы (docker system prune)

    Со временем у вас накопится много остановленных контейнеров, неиспользуемых сетей и старых образов. Они могут занимать гигабайты места.

    Есть «волшебная» команда для очистки мусора:

    Docker спросит подтверждение. Если вы согласитесь, он удалит:

  • Все остановленные контейнеры.
  • Все сети, которые не используются контейнерами.
  • Все «висячие» (dangling) образы (у которых нет имени и тега).
  • Кэш сборки.
  • Шпаргалка по командам

    | Команда | Описание | | :--- | :--- | | docker pull <image> | Скачать образ из реестра | | docker images | Показать список скачанных образов | | docker run <image> | Создать и запустить контейнер | | docker run -d | Запустить в фоновом режиме | | docker run -p host:container | Пробросить порты | | docker ps | Показать запущенные контейнеры | | docker ps -a | Показать все контейнеры (и остановленные) | | docker stop <name> | Остановить контейнер | | docker start <name> | Запустить остановленный контейнер | | docker rm <name> | Удалить контейнер | | docker rmi <image> | Удалить образ | | docker exec -it <name> bash | Зайти внутрь контейнера | | docker logs <name> | Посмотреть логи контейнера |

    Заключение

    Сегодня мы освоили «пульт управления» Docker. Вы научились скачивать образы, запускать веб-серверы, делать их доступными через браузер и заглядывать внутрь контейнеров. Эти команды — это 90% вашей ежедневной работы с Docker.

    Но пока мы использовали только чужие, готовые образы. А что, если вы хотите запустить свое приложение? В следующей статье мы разберем создание собственных образов с помощью файла Dockerfile.

    Переходите к заданиям, чтобы закрепить навыки работы с CLI!

    3. Создание собственных образов: синтаксис Dockerfile и оптимизация сборки

    Создание собственных образов: синтаксис Dockerfile и оптимизация сборки

    В предыдущих статьях мы научились пользоваться готовыми решениями: скачивали образы из Docker Hub и запускали их одной командой. Это отлично подходит для стандартных инструментов вроде баз данных или веб-серверов. Но главная сила Docker заключается в возможности упаковать ваше уникальное приложение в контейнер, который будет работать везде.

    Сегодня мы переходим от роли пользователя к роли творца. Мы разберем, как создавать свои собственные образы с помощью Dockerfile, изучим его синтаксис и узнаем, как сделать сборку быстрой и эффективной.

    Что такое Dockerfile?

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

    Процесс выглядит так:

  • Вы пишете код приложения.
  • Вы создаете файл с именем Dockerfile (без расширения) в корне проекта.
  • Вы запускаете команду docker build.
  • Docker создает образ, готовый к запуску.
  • !Процесс превращения исходного кода и инструкций Dockerfile в готовый образ.

    Основные инструкции Dockerfile

    Давайте разберем словарь, на котором мы общаемся с Docker. Инструкции принято писать ЗАГЛАВНЫМИ БУКВАМИ (хотя это не строгое требование, но стандарт индустрии).

    1. FROM

    С этой инструкции начинается любой (почти любой) Dockerfile. Она задает базовый образ.

    Здесь мы говорим: «Возьми за основу Linux с уже установленным Python 3.9». Мы используем версию slim, так как она весит меньше полной версии.

    2. WORKDIR

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

    Все файлы, которые мы скопируем позже, попадут в /app.

    3. COPY и ADD

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

    * COPY — просто копирует файлы. Это предпочтительный вариант. * ADD — умеет делать то же самое, но еще умеет распаковывать архивы (.tar.gz) и скачивать файлы по URL. Из-за этой «магии» её рекомендуют использовать только в особых случаях.

    4. RUN

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

    Результат выполнения этой команды сохраняется в слое образа.

    5. CMD и ENTRYPOINT

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

    * CMD — задает команду по умолчанию. Её можно легко переопределить при запуске контейнера. * ENTRYPOINT — задает исполняемый файл, который будет запущен всегда. Аргументы CMD передаются ему как параметры.

    Для начала используйте CMD, это проще:

    > Обратите внимание: аргументы лучше передавать в виде JSON-массива (в квадратных скобках и кавычках). Это называется exec form и позволяет избежать проблем с обработкой сигналов остановки.

    6. ENV

    Устанавливает переменные окружения, которые будут доступны внутри контейнера.

    7. EXPOSE

    Эта инструкция носит информационный характер. Она говорит человеку, читающему Dockerfile, и самому Docker'у, что приложение слушает определенный порт.

    Она не пробрасывает порт автоматически (для этого все равно нужен флаг -p при запуске).

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

    Давайте соберем реальный образ. Создайте пустую папку и в ней два файла.

    1. main.py (наше приложение):

    2. Dockerfile:

    Теперь откройте терминал в этой папке и запустите сборку:

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

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

    Вывод должен быть:

    Оптимизация сборки и слои (Layers)

    Образы Docker состоят из слоев. Каждая инструкция (RUN, COPY, ADD) создает новый слой. Слои доступны только для чтения и накладываются друг на друга.

    Главный секрет скорости Docker — кэширование слоев.

    Когда вы пересобираете образ, Docker проверяет, изменилось ли что-то в инструкции или файлах, которые она затрагивает. Если нет — он берет готовый слой из кэша. Если да — он пересобирает этот слой и все последующие.

    Правильный порядок инструкций

    Рассмотрим плохой пример:

    В этом примере при любом изменении кода Docker будет заново скачивать и устанавливать все библиотеки. Это долго.

    А теперь хороший пример:

    Такой подход называется Layer Caching Optimization. Самые редко изменяемые вещи должны идти в начале Dockerfile, а часто изменяемые (исходный код) — в конце.

    !Визуализация принципа кэширования слоев: изменения в верхних слоях не требуют пересборки нижних.

    Файл .dockerignore

    Когда вы пишете COPY . ., Docker копирует всё из контекста сборки. Часто в папке проекта лежит мусор: папка .git, локальные виртуальные окружения (venv, node_modules), временные файлы, логи.

    Копирование этого мусора:

  • Увеличивает размер образа.
  • Может перезаписать файлы внутри контейнера.
  • Замедляет отправку контекста демону Docker.
  • Чтобы этого избежать, создайте файл .dockerignore (синтаксис как у .gitignore):

    Multi-stage builds (Многоэтапная сборка)

    Это продвинутая техника для уменьшения размера итогового образа. Представьте, что вы пишете на Go или C++. Для сборки вам нужен компилятор (GCC, Go), который весит сотни мегабайт. Но для запуска программы компилятор не нужен — нужен только бинарный файл.

    Многоэтапная сборка позволяет использовать один образ для компиляции, а потом скопировать результат в другой, чистый и легкий образ.

    Пример (концептуально):

    В итоге у нас будет крошечный образ на базе Alpine Linux, без тяжелого компилятора Go.

    Резюме

  • Dockerfile — это рецепт создания образа.
  • Используйте COPY вместо ADD.
  • RUN выполняется при сборке (установка ПО), CMD — при запуске контейнера.
  • Следите за порядком команд: сначала копируйте файлы зависимостей (package.json, requirements.txt), устанавливайте их, и только потом копируйте исходный код. Это ускоряет повторные сборки.
  • Используйте .dockerignore, чтобы не тащить мусор в образ.
  • Теперь вы умеете упаковывать свои приложения. Но что, если приложению нужно хранить данные, которые не должны исчезнуть после удаления контейнера? Или если контейнерам нужно общаться друг с другом? Об этом мы поговорим в следующей статье, посвященной Docker Volumes и Networking.

    4. Хранение данных и сетевое взаимодействие: Volumes и Networks

    Хранение данных и сетевое взаимодействие: Volumes и Networks

    В предыдущих статьях мы научились упаковывать приложения в образы и запускать их. Мы выяснили, что контейнеры — это эфемерные (временные) сущности. Это прекрасно для обновления приложений: вы просто удаляете старый контейнер и запускаете новый из свежего образа. Но здесь кроется главная проблема: что происходит с данными?

    Представьте, что вы запустили базу данных в контейнере. Пользователи зарегистрировались, создали заказы. Вы решили обновить версию базы данных, удалили старый контейнер... и все данные исчезли вместе с ним. Это катастрофа.

    В этой статье мы решим две фундаментальные задачи:

  • Как сохранить данные, чтобы они пережили удаление контейнера (Volumes).
  • Как научить контейнеры общаться друг с другом и с внешним миром (Networks).
  • Проблема постоянства данных

    По умолчанию все файлы, созданные внутри контейнера, хранятся в так называемом записываемом слое (writable layer). У этого слоя есть два недостатка: * Он умирает вместе с контейнером. Если контейнер удален, слой уничтожается. * Низкая производительность. Запись в этот слой происходит через драйвер файловой системы (Storage Driver), что медленнее, чем прямая запись на диск.

    Docker предлагает два основных механизма для решения этой проблемы: Volumes (Тома) и Bind Mounts (Монтирование папок).

    !Схема различий между Volumes, Bind Mounts и tmpfs mount.

    1. Volumes (Тома)

    Volumes — это лучший способ хранения данных. Тома создаются и управляются самим Docker. Они хранятся в специальной директории на хосте (обычно /var/lib/docker/volumes/ на Linux), и обычным пользователям или другим программам не следует туда лезть.

    Преимущества томов: * Легко делать резервные копии и миграцию. * Можно безопасно использовать один том несколькими контейнерами. * Работают одинаково на Linux и Windows.

    #### Управление томами

    Создадим том:

    Посмотрим список томов:

    Теперь запустим контейнер (например, PostgreSQL) и подключим этот том. PostgreSQL хранит свои данные в папке /var/lib/postgresql/data. Нам нужно «подменить» эту папку внутри контейнера на наш том.

    Флаг -v (или --volume) имеет синтаксис: имя_тома:путь_внутри_контейнера.

    Теперь, даже если вы удалите контейнер postgres-db командой docker rm -f, а затем запустите новый с тем же томом, все ваши таблицы и данные останутся на месте.

    2. Bind Mounts (Монтирование папок)

    Bind Mounts появились в Docker раньше томов. Они позволяют пробросить любую папку или файл с вашего компьютера внутрь контейнера. В отличие от томов, вы точно знаете, где лежат файлы, и можете их редактировать любым редактором кода.

    Это идеальный вариант для разработки (Development). Вы пишете код на хосте, а контейнер сразу видит изменения (hot reload).

    Пример запуска веб-сервера Nginx с вашим html-файлом:

    bash docker network create my-net bash docker run -d \ --name web-server \ --network my-net \ nginx bash docker run --rm -it \ --network my-net \ alpine ping web-server bash docker network inspect my-net ``

    Вы получите большой JSON-объект с конфигурацией, где в разделе Containers будут перечислены все участники.

    Резюме

    Сегодня мы сделали наши контейнеры надежными и коммуникабельными.

  • Используйте Volumes (-v volume_name:/path), когда нужно сохранить данные базы данных или логи. Это надежно и управляется Docker.
  • Используйте Bind Mounts (-v /host/path:/container/path), когда разрабатываете код и хотите видеть изменения сразу. Это удобно для локальной разработки.
  • Создавайте свои сети (docker network create`), чтобы контейнеры могли «видеть» друг друга по имени. Это избавляет от необходимости хардкодить IP-адреса.
  • Теперь у нас есть все кирпичики: образы, контейнеры, хранилище и сеть. Но запускать каждый контейнер отдельной длинной командой в терминале — это утомительно, особенно если их пять или десять. В следующей статье мы изучим Docker Compose — инструмент, который позволит описать всю архитектуру проекта в одном файле и запускать её одной командой.

    5. Оркестрация локальной разработки с помощью Docker Compose

    Оркестрация локальной разработки с помощью Docker Compose

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

    Вспомните команду из прошлой статьи, где мы соединяли веб-сервер и базу данных. Нам приходилось:

  • Создавать сеть вручную.
  • Запускать базу данных с длинным набором флагов.
  • Запускать приложение, указывая сеть и переменные окружения.
  • Следить за порядком запуска (сначала база, потом приложение).
  • Если вы ошибетесь в одной букве, придется начинать заново. А теперь представьте, что у вас микросервисная архитектура из 10 контейнеров. Запускать их вручную каждое утро — неэффективно.

    Сегодня мы изучим инструмент, который решает эту проблему раз и навсегда — Docker Compose.

    Что такое Docker Compose?

    Docker Compose — это инструмент для определения и запуска многоконтейнерных приложений Docker. С его помощью вы используете файл YAML для настройки служб вашего приложения. Затем, с помощью одной команды, вы создаете и запускаете все службы из вашей конфигурации.

    Если Dockerfile — это рецепт приготовления одного блюда (образа), то Docker Compose — это меню для целого банкета, где расписано, какие блюда подавать, в каком порядке и как сервировать стол.

    !Docker Compose берет файл конфигурации и превращает его в работающую инфраструктуру из контейнеров, сетей и томов.

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

  • Инфраструктура как код (IaC): Вся конфигурация запуска хранится в файле, который можно положить в Git. Ваш коллега скачает проект, введет одну команду и получит точно такое же окружение, как у вас.
  • Упрощение разработки: Вам не нужно помнить длинные команды docker run.
  • Изоляция сред: Compose автоматически создает изолированную сеть для каждого проекта, предотвращая конфликты портов и имен.
  • Формат YAML и файл docker-compose.yml

    Конфигурация описывается в файле docker-compose.yml (или compose.yaml в новых версиях). Этот файл использует формат YAML — человекочитаемый язык сериализации данных. Главное правило YAML: отступы имеют значение. Обычно используется 2 пробела для каждого уровня вложенности.

    Давайте разберем структуру файла на примере классического стека: веб-приложение на Python и база данных Redis для кэширования.

    Структура файла

    Разберем каждую секцию подробно.

    1. Version (Версия)

    version: "3.8" указывает версию синтаксиса файла Compose. Хотя в последних версиях Docker это поле стало необязательным, его часто указывают для совместимости.

    2. Services (Сервисы)

    Это сердце вашего файла. Здесь мы перечисляем контейнеры, которые хотим запустить. В нашем примере два сервиса: web-app и redis-db.

    Сервис web-app: * build: .: Вместо скачивания готового образа, мы говорим Docker: «Собери образ из Dockerfile, который лежит в текущей папке (.)». * ports: Аналог флага -p. Мы пробрасываем порт 5000 хоста на порт 5000 контейнера. * depends_on: Указывает порядок запуска. Docker сначала запустит redis-db, и только потом web-app. Это важно, если ваше приложение падает при отсутствии базы данных.

    Сервис redis-db: * image: "redis:alpine": Здесь мы не собираем образ, а берем готовый из Docker Hub. * volumes: Подключаем именованный том для сохранения данных.

    3. Volumes (Тома) и Networks (Сети)

    Внизу файла мы объявляем тома и сети верхнего уровня. В примере мы объявили том redis-data. Если бы мы хотели создать особую сеть, мы бы добавили секцию networks.

    > Обратите внимание: Docker Compose автоматически создает сеть для вашего проекта и добавляет туда все сервисы. Вам не нужно писать network create вручную.

    Основные команды

    Теперь, когда у нас есть файл docker-compose.yml, магия начинается в терминале. В современных версиях Docker Desktop команда пишется как docker compose (через пробел), в более старых — docker-compose (через дефис).

    Запуск (up)

    Самая главная команда. Она делает всё: скачивает образы, собирает их (если нужно), создает сети, тома и запускает контейнеры.

    Если вы хотите запустить всё в фоновом режиме (как -d в docker run), добавьте флаг:

    Остановка (down)

    Эта команда останавливает контейнеры и удаляет их, а также удаляет созданную сеть. Тома при этом сохраняются (чтобы не потерять данные).

    Если вы хотите удалить и тома тоже, добавьте флаг -v: docker compose down -v (будьте осторожны с этим!).

    Просмотр статуса (ps)

    Показывает список контейнеров, относящихся только к текущему проекту (текущей папке с yaml-файлом).

    Просмотр логов (logs)

    Если вы запустили проект в фоновом режиме, логи можно посмотреть так:

    Флаг -f (follow) позволяет следить за логами в реальном времени.

    Сетевое взаимодействие в Compose

    В прошлой статье мы узнали, что контейнеры могут общаться по именам, если они находятся в одной пользовательской сети. Docker Compose делает это поведение стандартом.

    В нашем примере внутри Python-кода мы будем обращаться к Redis не по localhost (так как localhost внутри контейнера — это сам контейнер), и не по IP-адресу, а просто по имени сервиса: redis-db.

    Пример кода на Python (Flask):

    Docker Compose автоматически настраивает DNS так, чтобы имя redis-db разрешалось во внутренний IP-адрес контейнера с Redis.

    Переменные окружения

    Хардкодить пароли и настройки в docker-compose.yml — плохая практика, особенно если вы выкладываете код в публичный репозиторий. Compose поддерживает чтение переменных из файла .env.

  • Создайте файл .env в той же папке:
  • Используйте переменные в docker-compose.yml с помощью синтаксиса {POSTGRES_USER}
  • POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} yaml services: web: image: nginx:alpine ports: - "8080:80" volumes: - ./index.html:/usr/share/nginx/html/index.html `

  • Запустите проект командой docker compose up -d.
  • Откройте браузер по адресу http://localhost:8080.
  • Вы должны увидеть ваш HTML-файл. Попробуйте изменить текст в файле index.html на компьютере и обновить страницу в браузере. Текст изменится! Мы только что настроили среду разработки с «горячей перезагрузкой» статики, используя всего один конфигурационный файл.

    Заключение

    Docker Compose — это «дирижер» вашего оркестра контейнеров. Он избавляет от рутины, уменьшает количество ошибок и делает запуск сложных проектов тривиальной задачей. Вместо того чтобы писать инструкции на 5 страниц «как запустить мой проект», вы просто даете коллеге файл docker-compose.yml и говорите: «Напиши docker compose up`».

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

    А теперь проверьте, как хорошо вы усвоили материал!