0% нашли этот документ полезным (0 голосов)
6 просмотров415 страниц

Docker

Загружено:

utrash126
Авторское право
© All Rights Reserved
Мы серьезно относимся к защите прав на контент. Если вы подозреваете, что это ваш контент, заявите об этом здесь.
Доступные форматы
Скачать в формате PDF, TXT или читать онлайн в Scribd
0% нашли этот документ полезным (0 голосов)
6 просмотров415 страниц

Docker

Загружено:

utrash126
Авторское право
© All Rights Reserved
Мы серьезно относимся к защите прав на контент. Если вы подозреваете, что это ваш контент, заявите об этом здесь.
Доступные форматы
Скачать в формате PDF, TXT или читать онлайн в Scribd

11 марта 2025 г.

Прежде всего, вступайте в телеграм-чат нашего курса

Почему мы решили сделать курс по Docker?


Docker давно стал стандартом контейнеризации в мире DevOps, но мы увидели,
что многие обучающие программы либо слишком поверхностны (ограничиваются
установкой и запуском контейнера), либо уводят в сторону других инструментов и
теряют фокус на самом Docker. В итоге у людей складывается иллюзия: «Ну, я умею
писать Dockerfile, значит, уже всё знаю». А на практике оказывается, что
архитектура, сети, volumes, оптимизация сборки, безопасность и регистры
остаются за кадром.

Мы хотим восполнить этот пробел и показать Docker во всей глубине — от


тонкостей Dockerfile и кастомных сетей до продвинутых фишек (multi-stage builds,
сбор логов, работа с локальным реестром, мониторинг, управление ресурсами).
Другими словами, здесь вы найдете максимум знаний, которые позволят уверенно
использовать Docker на любом этапе: разработка, тесты, продакшен.

Что такое Docker и какие практические задачи он решает?

Раньше для запуска сервисов использовались тяжеловесные виртуальные машины.


Это означало:

Дублирование всей ОС для каждого проекта;


Медленное масштабирование и долгий прогрев новых окружений;
Проблемы с зависимостями: в одной VM одни версии библиотек, в другой —
другие.

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

1. Лёгкий перенос приложения между машинами (dev-среда, тест, прод) — без


истории “у меня локально всё работает, а у вас нет”.
2. Мгновенная масштабируемость: нужно 10 контейнеров — запускаем 10
копий, не размножая целую ОС.
3. Повторяемость: весь процесс сборки и запуска прописан в Dockerfile.

1/2
Но Docker не ограничивается docker run. Чтобы реально использовать
возможности контейнеризации, нужно разбираться в слоях образа, правильном
кэшировании, сетевых драйверах, томах для хранения данных, безопасности и
многом другом. Именно это и делает курс полноценным.

Кому подойдёт и чему вы научитесь?

Новичкам, которые изучают DevOps с нуля.


Уже знакомым с DevOps: если вы используете Docker по верхам, но хотите
развернуть сквозную инфраструктуру с тонкими настройками (логирование,
мониторинг, оптимизация), здесь вы найдёте детальный разбор.
Разработчикам и тимлидам, которым нужны продвинутые практики сборки/
запуска приложений в Docker, чтобы ускорить релизы и избавляться от
“Сюрприз! Всё упало на проде из-за конфликта библиотек”.

По итогам курса вы:

1. Полноценно освоите Dockerfile: узнаете, как оптимизировать образы, что


такое multi-stage, как правильно передавать переменные окружения и не
копировать лишние файлы.
2. Поймёте логику контейнерных сетей (bridge, host, overlay), научитесь
устранять конфликты портов и использовать DNS внутри Docker.
3. Разберётесь с сохранением данных (volumes, bind mounts), научитесь
грамотно подключать базы и другие сервисы.
4. Сможете уверенно применять docker-compose для запуска целых стэков
(бэкенд, фронтенд, база), а также использовать Docker registry для хранения
и распространения образов.
5. Углубитесь в практики безопасности: пользователь в контейнере, cgroups,
seccomp, настройка HTTPS в локальном реестре.
6. Научитесь продвинутому мониторингу: сбор логов, метрик, интеграции с
Prometheus и прочими инструментами.

Итог

Если вы хотите по-настоящему прокачаться в Docker, а не просто научиться


запускать контейнеры, — вы на правильном пути. Мы сделаем всё, чтобы после
курса вы досконально понимали, как всё устроено, и внедрять Docker в свои
проекты на уровне production-ready.

2/2
1.3 Подготовка окружения

Для прохождения курса вам нужна виртуальная машина с


Linux или MacBook

Часть 1: Установка виртуальной машины Linux на Windows

Шаг 1: Выбор программы для виртуализации


Для создания виртуальной машины вам понадобится специальное ПО. Наиболее
популярны:

VirtualBox (бесплатно)
VMware Workstation Player (бесплатно для личного использования)
Hyper-V (встроен в Windows 10/11 Pro, Enterprise и Education)

В этой инструкции мы будем использовать Oracle VirtualBox.

Шаг 2: Загрузка и установка VirtualBox

1. Перейдите на официальный сайт VirtualBox


2. Скачайте установщик для Windows
3. Запустите загруженный файл
4. Следуйте инструкциям мастера установки:
Примите лицензионное соглашение
Выберите компоненты для установки (рекомендуется оставить все по
умолчанию)
Подтвердите предупреждения о сетевых интерфейсах
Нажмите "Установить"
Разрешите установку драйверов при запросе
5. После завершения установки нажмите "Готово"

Шаг 3: Загрузка ISO-образа Linux

Вам нужно выбрать дистрибутив Linux. Рекомендуем Ubuntu:

1. Перейдите на сайт Ubuntu


2. Скачайте последнюю версию Ubuntu Desktop (например, Ubuntu 22.04 LTS)
3. Сохраните ISO-файл в удобном месте

Шаг 4: Создание новой виртуальной машины

1. Запустите VirtualBox
2. Нажмите кнопку "Создать" или "New"

1/7
3. Настройте параметры новой ВМ:
Имя: Ubuntu (или любое другое)
Тип: Linux
Версия: Ubuntu (64-bit)
Нажмите "Далее"
4. Выделите память (RAM):
Рекомендуется минимум 2048 МБ (2 ГБ)
Оптимально 4096 МБ (4 ГБ)
Нажмите "Далее"
5. Создайте виртуальный жесткий диск:
Выберите "Создать новый виртуальный жесткий диск"
Нажмите "Создать"
6. Выберите тип жесткого диска:
Выберите VDI (VirtualBox Disk Image)
Нажмите "Далее"
7. Выберите формат хранения:
Рекомендуется "Динамический виртуальный диск"
Нажмите "Далее"
8. Укажите размер и расположение диска:
Минимум 20 ГБ, рекомендуется 30-50 ГБ
Выберите расположение диска
Нажмите "Создать"

Шаг 5: Настройка виртуальной машины


1. Выберите созданную ВМ в списке и нажмите "Настроить" (Settings)
2. Перейдите в раздел "Система" (System):
Вкладка "Процессор" (Processor): выделите 2 или более ядер, если
возможно
3. Перейдите в раздел "Дисплей" (Display):
Увеличьте видеопамять до 128 МБ
Включите 3D-ускорение, если доступно
4. Перейдите в раздел "Носители" (Storage):
Выберите пустой оптический привод (CD/DVD)
Нажмите на значок диска справа
Выберите "Выбрать образ диска" (Choose a disk file)
Укажите путь к скачанному ISO-образу Ubuntu
5. Нажмите "OK", чтобы сохранить настройки

Шаг 6: Установка Linux


1. Выберите ВМ в списке и нажмите "Запустить" (Start)
2. Откроется окно с загрузкой Ubuntu
3. Выберите "Install Ubuntu"
4. Выберите язык и нажмите "Continue"

2/7
5. В разделе "Updates and other software":
Выберите "Normal installation"
Отметьте "Download updates while installing Ubuntu"
Нажмите "Continue"
6. В разделе "Installation type":
Выберите "Erase disk and install Ubuntu" (не беспокойтесь, это относится
только к виртуальному диску)
Нажмите "Install Now"
Подтвердите изменения, нажав "Continue"
7. Выберите часовой пояс и нажмите "Continue"
8. Настройте учетные данные:
Введите ваше имя
Придумайте имя компьютера
Укажите имя пользователя
Создайте пароль
Нажмите "Continue"
9. Дождитесь завершения установки
10. Нажмите "Restart Now", когда появится соответствующее сообщение
11. Когда система попросит извлечь установочный носитель, просто нажмите Enter

Шаг 7: Настройка гостевых дополнений (Guest Additions)

Гостевые дополнения позволяют улучшить интеграцию между хостовой и гостевой


системами:

1. После загрузки Ubuntu войдите в систему


2. В меню VirtualBox выберите "Устройства" -> "Подключить образ диска
Дополнений гостевой ОС"
3. В Ubuntu откроется окно файлового менеджера
4. Щелкните правой кнопкой мыши в окне файлового менеджера и выберите
"Open in Terminal"
5. Выполните команды:

sudo apt update


sudo apt install -y build-essential dkms linux-headers-$(uname -r)
cd /media/$USER/VBox_GAs_*
sudo ./[Link]

1. Перезапустите виртуальную машину:

sudo reboot

Часть 2: Установка Docker на виртуальную машину Linux

Способ 1: Однострочная установка (рекомендуется)

Самый простой способ установки Docker — использование официального скрипта:

3/7
1. Откройте терминал в Ubuntu (Ctrl+Alt+T)
2. Запустите скрипт установки:

curl -fsSL [Link] | sh

1. После завершения установки добавьте вашего пользователя в группу docker,


чтобы использовать Docker без sudo:

sudo gpasswd -a $USER docker


newgrp docker

Если скрипт сообщает, что ваш дистрибутив не поддерживается, остановите


процесс и используйте Способ 2 или 3.

Способ 2: Официальная установка для Ubuntu


Если однострочный метод не сработал, можно установить Docker вручную:

1. Обновите пакеты и установите необходимые зависимости:

sudo apt update && sudo apt install -y ca-certificates curl gnupg lsb-release

1. Добавьте GPG-ключ Docker и официальный репозиторий:

curl -fsSL [Link] | sudo gpg --dearmor -o


/usr/share/keyrings/[Link]
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-
[Link]] [Link] $(lsb_release -cs)
stable" | sudo tee /etc/apt/[Link].d/[Link] > /dev/null

1. Установите Docker и Docker Compose:

sudo apt update && sudo apt install -y docker-ce docker-ce-cli [Link]
docker-compose-plugin

1. Настройте доступ без sudo:

sudo gpasswd -a $USER docker


newgrp docker

Способ 3: Официальная установка для Debian


Если вы используете Debian вместо Ubuntu:

sudo apt update && sudo apt install -y ca-certificates curl gnupg lsb-release
curl -fsSL [Link] | sudo gpg --dearmor -o
/etc/apt/keyrings/[Link]
echo "deb [arch=$(dpkg --print-architecture)]
[Link] $(lsb_release -cs) stable" | sudo tee
/etc/apt/[Link].d/[Link] > /dev/null
sudo apt update && sudo apt-get install -y docker-ce docker-ce-cli [Link]
docker-compose-plugin
sudo gpasswd -a $USER docker
newgrp docker

4/7
Проверка установки
После установки проверьте, что Docker работает корректно:

docker run hello-world

Если всё установлено правильно, вы увидите сообщение:

Hello from Docker!


This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:


1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

Дополнительные настройки

1. Настройка автозапуска Docker при старте системы:

sudo systemctl enable docker

1. Проверка версии Docker и Docker Compose:

docker --version
docker compose version

Часть 3: Установка Docker на Mac

Шаг 1: Проверка системных требований


Убедитесь, что ваш Mac соответствует требованиям Docker:

macOS 11 (Big Sur) или более новая версия


Процессор Intel или Apple Silicon (M1/M2)
Не менее 4 ГБ RAM

Шаг 2: Загрузка Docker Desktop


1. Перейдите на официальный сайт Docker
2. Нажмите "Download for Mac"
3. Выберите правильную версию:
"Mac with Intel chip" для компьютеров с процессорами Intel
"Mac with Apple chip" для компьютеров с процессорами M1/M2

Шаг 3: Установка Docker Desktop

1. Откройте загруженный файл .dmg

5/7
2. Перетащите иконку Docker в папку Applications
3. Закройте окно установщика

Шаг 4: Запуск Docker Desktop


1. Откройте Finder и перейдите в папку Applications
2. Запустите Docker Desktop
3. При первом запуске:
Может потребоваться ввод пароля администратора
Нужно принять лицензионное соглашение

Шаг 5: Настройка Docker Desktop (для Apple Silicon)

Если у вас Mac с процессором M1/M2, могут понадобиться дополнительные


настройки:

1. Откройте меню Docker Desktop (иконка в строке меню)


2. Выберите "Preferences" или "Settings"
3. Перейдите на вкладку "General"
4. Убедитесь, что опция "Use Rosetta for x86/amd64 emulation on Apple Silicon"
включена, если вам нужна поддержка контейнеров x86

Шаг 6: Проверка установки

1. Откройте терминал (Applications > Utilities > Terminal)


2. Выполните команду:

docker --version

Вы должны увидеть версию установленного Docker.

1. Запустите тестовый контейнер:

docker run hello-world

Шаг 7: Настройка ресурсов

1. Откройте настройки Docker Desktop


2. Перейдите на вкладку "Resources"
3. Настройте доступные ресурсы:
CPU: рекомендуется выделить не менее 2 ядер
Memory: рекомендуется выделить не менее 2 ГБ (2048 МБ)
Swap: рекомендуется 1 ГБ (1024 МБ)
Disk image size: по умолчанию 60 ГБ
4. Нажмите "Apply & Restart"

Дополнительные советы

6/7
Для виртуальной машины Linux
1. Для удобной работы используйте общие папки между Windows и VM:

В настройках VM выберите "Shared Folders"


Добавьте папку на вашем компьютере и настройте автоподключение
В Linux папка будет доступна в /media/sf_[имя_папки]
2. Включите поддержку буфера обмена:

В настройках VM выберите "General" > "Advanced"


Настройте "Shared Clipboard" на "Bidirectional"
3. Если вы планируете использовать Docker Swarm или Kubernetes:

Настройте сеть VM в режим "Bridged Adapter" для доступа из локальной


сети

7/7
2.1 Контейнеризация vs Виртуализация

В предыдущем, вводном уроке мы разобрались, зачем вообще нужен Docker и


какой путь прошли технологии изоляции.

Теперь давайте сравним подходы «старой школы» — виртуальные машины (VM) —


с «новой школой» контейнеризации - Docker.

1. Классический путь: виртуальные машины (VM)

Что такое VM: На физическом сервере (или десктопе) установлен гипервизор


(VirtualBox, VMware, Hyper-V), и поверх него вы создаёте несколько
полноценных операционных систем - каждая со своим ядром, драйверами,
процессами.
Плюсы:
Высокий уровень изоляции — ведь у каждого приложения отдельная ОС.
Зрелые технологии (VMware, VirtualBox) и многолетний опыт их
использования.
Минусы:
Большое потребление ресурсов: каждая VM несёт полный
багаж операционной системы, раздувая RAM и дисковое пространство.
Долгий запуск: грузится ядро и все процессы, как в обычном компьютере.
Масштабирование требует времени.
Сложность обновлений: нужно патчить каждую гостевую ОС, следить за
множеством VM.

2. Контейнеризация: быстрая и лёгкая альтернатива

Что такое контейнер: Контейнер не несёт в себе ядро ОС — он использует


ядро хоста (Linux), но имеет свою файловую систему, процессы и сетевые
настройки, благодаря namespaces, cgroups, capabilities. То есть всё
изолировано, но без дублирования ядра.
Плюсы:
Лёгковесность: нет накладных расходов на полную ОС, поэтому можно
запустить десятки и сотни контейнеров на одном сервере.
Быстрый старт: не надо загружать ядро - контейнер активируется за
секунды, а это идеально для CI/CD.
Управление: Docker и другие движки контейнеров упрощают упаковку
приложения в образ — то, что работает у вас, будет работать везде.

1/4
Минусы:
Чуть менее жёсткая изоляция, так как одно общее ядро. Хотя
namespaces и seccomp дают хорошую безопасность, формально VM-
контейнеры — более глубоко изолированная среда.
Контейнеры обычно зависят от ОС хоста (Linux/Windows). Но для
macOS/Windows есть Docker Desktop, который под капотом всё равно
запускает Linux VM.

2.1 Технологии под капотом контейнеризации (Docker в частности) — cgroups,


namespaces и capabilities

Docker использует три основные технологии ядра Linux, которые делают


контейнеризацию возможной:

Namespaces (пространства имён) — это механизм изоляции


ресурсов в Linux.
Благодаря неймспейсам процессы в контейнере думают, что они работают
в обычной системе, хотя на самом деле они изолированы от всего
остального.
Cgroups (control groups) — это механизм для контроля и
ограничения ресурсов, выделяемых группам процессов
Сgroups позволяют устанавливать лимиты на использование CPU,
ограничивать потребление оперативки и т.д. По сути они обеспечивают
справедливое распределение ресурсов между контейнерами на одной
машине и защищают от ситуации, когда один контейнер потребляет все
доступные ресурсы
Capabilities — это механизм разделения полных привилегий
суперпользователя (root) на отдельные права.
Вместо работы с полными правами root, процесс получает только
необходимые ему привилегии, например можно слушать только те
порты, которые ниже 1024 (80, 443). С Capabilities значительно
повышается безопасность, даже если процесс внутри контейнера
запущен от root

Как эти технологии работают вместе в Docker — при запуске контейнера Docker
создаёт набор namespaces для изоляции, настраивает cgroups для ограничения
ресурсов и назначает минимальный набор capabilities для безопасной работы.

2/4
3. Сравнительная таблица

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

Изоляция Полная гостевая ОС (каждая Общее ядро, процессы в


VM со своим ядром) namespaces

Запуск Как загрузка ОС (десятки Секунды или доли секунд


секунд +)

Ресурсы Требует много RAM/диска: Легковесно, нет


каждая VM несёт ОС дублирования ядра

Масштабирование Сложнее клонировать VM Мгновенное: контейнеры


быстро размножаются

Управление Каждую VM нужно обновлять/ Docker tools (Swarm,


патчить отдельно Kubernetes) упрощают всё

Технологии Гипервизор с полной Namespaces, cgroups,


изоляции виртуализацией capabilities

4. Когда VM всё ещё актуальны?

Полная ОС-изоляция: для задач, где нужна абсолютно независимая среда, и


регулятор требует «разделить ядра».
Старые энтерпрайз приложения, которые не контейнеризуются без
серьёзного рефакторинга.

3/4
Банковские/государственные требования, когда узаконена конкретная
модель безопасности с VM.

Но для большинства новых разработок и DevOps-подходов контейнеры удобнее и


быстрее, чем VM.

5. Почему DevOps любят контейнеры

CI/CD: Можно поднять контейнер для тестов, прогнать всё и снести за


секунды. VM так шустро не поднимаются.
Простая упаковка (Dockerfile): DevOps может описать, как собрать образ, и
приложение будет надёжно и одинаково работать у всех.
Оркестрация (Kubernetes): Автоматическое масштабирование, перезапуск
упавших контейнеров, распределение по узлам.

Итог
Виртуализация (VM) — полноценная гостевая ОС внутри гипервизора,
надёжная, но тяжёлая по ресурсам.
Контейнеризация (Docker) — общий ядро-хост, лёгкое и быстрое, идеально
для DevOps, микросервисов, CI/CD.
Технологии в основе — namespaces изолируют ресурсы, cgroups
контролируют потребление, capabilities обеспечивают безопасность.

Сегодня Docker и контейнеры в целом фактически стали стандартом для разработки


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

4/4
2.2 Что такое Docker?

На прошлом уроке мы познакомились с идеей контейнеризации и поняли, почему


она часто предпочтительнее, чем классическая виртуализация. Теперь пора узнать
про Docker — инструмент, который фактически сделал контейнеры массовым
стандартом.

Зачем вообще нужен Docker?

Docker — это не просто программа для контейнеров. Это целая экосистема, которая
упрощает:

Упаковку приложения вместе со всеми зависимостями: библиотеками,


конфигурациями и т.д.
Перенос этого упакованного приложения — контейнера между средами:
локальная машина, сервер, облако.

Идея в том, что вы один раз описываете, какое окружение нужно вашему
приложению, а Docker создаёт среду (контейнер), которая точно совпадает и на
вашем ноутбуке, и на тестовом сервере, и в продакшене. Это даёт уверенность:
“Если работает здесь, значит будет работать везде” (есть случаи, когда это не
так).

Почему Docker стал таким популярным?

1. Простота. Раньше контейнеры на Linux могли настроить только опытные


администраторы через множество команд (cgroups, namespaces). Docker же
предоставил удобный интерфейс: установили — и пользуетесь.
2. Быстрый старт. Запуск контейнера занимает секунды, что идеально для
практики частых релизов и быстрой проверки нововведений.
3. Широкая поддержка. Почти все современные языки и фреймворки имеют
готовые примеры для Docker. Большинство облачных платформ из
коробки дружат с Docker-контейнерами.
4. Гибкость. Можно включить в контейнер всё необходимое — нужные версии
библиотек, системные утилиты, и запустить на любой машине с Docker.

Простая аналогия

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

Вывод
Docker — это инструмент контейнеризации, который упростил процесс создания и
запуска контейнеров в Linux (а через Docker Desktop и на macOS и Windows). Он
решает проблему: как упаковать приложение в лёгкую, но изолированную среду и
как гарантировать одинаковую работу кода и зависимостей в разных окружениях.
Поэтому о Docker говорят все — это базис для современных DevOps-практик и
микросервисной архитектуры.

2/2
10 апреля 2025 г.

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

Теперь, когда мы знаем, что Docker упрощает работу с контейнерами и зачем он


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

Общая схема
Можно представить Docker как систему из нескольких основных компонентов:

1. Docker Daemon
2. Docker Client (CLI или API)
3. Docker Engine (часто подразумевается как объединение Daemon + механизмы
работы с контейнерами)
4. Registry (реестр образов)

Когда мы вводим команду вроде docker run, она идёт к Docker Daemon, который, в
свою очередь, управляет контейнерами, образами, сетью и т. д.

1. Docker Daemon: сердце системы

Docker Daemon — это фоновый процесс, который непрерывно работает в


операционной системе и:

Принимает запросы от клиента.


Управляет жизненным циклом контейнеров: создание, запуск, остановка.
Отвечает за работу с образами — тянет их из реестра, кеширует, удаляет и т.
д..
Ведёт учёт сетей, томов и других ресурсов.

Если Daemon упал или не запущен, команды docker не будут работать, потому
что некому их исполнять.

1/3
2. Docker Client (CLI) и API

CLI — это командная строка, где мы вводим docker run, docker ps, docker
images и прочие команды.
Под капотом все эти команды идут по Docker API. То есть CLI просто
оборачивает HTTP-запросы, которые отправляются Daemon.

Другими словами, когда вы запускаете команду из терминала, она фактически


шлёт запрос к Daemon: Создай контейнер из такого-то образа, и Daemon уже
делает всю тяжёлую работу.

3. Docker Engine

Иногда под Docker Engine подразумевают и Daemon, и все механизмы,


позволяющие работать с контейнерами. В более узком смысле Docker Engine — это:

Docker Daemon — процесс, управляющий контейнерами.


Containerd — вспомогательный слой, который создаёт и контролирует
процессы контейнеров.
runc — инструмент, который непосредственно запускает контейнеры,
используя возможности ядра Linux.

2/3
Однако, в базовом понимании достаточно знать, что Docker Engine — это общий
движок Docker, отвечающий за взаимодействие клиента, ядра системы и
контейнеров.

4. Registry (реестр образов)

Каждый контейнер стартует из образа (image). Образ хранится в реестре (registry).


Это может быть:

Docker Hub — публичный реестр, где лежат миллионы образов


(официальные, пользовательские).
Локальный реестр (self-hosted), где компания хранит свои образы.
GitHub Container Registry или другие облачные решения.

Когда мы делаем docker pull, Daemon скачивает образ из реестра. Когда делаем
docker push, мы, наоборот, отправляем образ (собранный у нас) в реестр, чтобы
другие могли его использовать.

Как выглядит взаимодействие?

1. Вы пишете в терминале: docker run nginx


2. Docker Client принимает команду и шлёт её на Docker Daemon.
3. Daemon проверяет, есть ли образ nginx локально. Если нет — скачивает из
Registry (Docker Hub).
4. Daemon создаёт контейнер на основе этого образа, настраивает сеть,
файловую систему и запускает процесс nginx внутри контейнера.
5. Вам возвращается результат: в docker ps видно, что контейнер работает.

Почему надо понимать архитектуру?

1. Устранение ошибок: если что-то не работает, вы будете знать, где искать


причину (Daemon не запущен? Образ не найден в реестре?).
2. Безопасность: осознание того, что Daemon выполняет привилегированные
действия, важно для настройки прав и безопасности.
3. Автоматизация: зная, что есть Docker API, вы понимаете, как инструменты
CI/CD (например, Jenkins) могут управлять контейнерами без ручных
действий.

3/3
Мы уже знаем, что Docker помогает быстро собирать и запускать контейнеры. Но из
чего состоит эта контейнерная магия? В Docker есть несколько ключевых
сущностей, каждая со своей ролью. Сейчас их кратко рассмотрим, а в следующих
уроках углубимся в детали.

1. Образы (Images)
Образ — это шаблон контейнера. Он содержит:

Базовую файловую систему (например, основанную на ubuntu или alpine),


Установленные программы и библиотеки,
Набор инструкций (какой порт открыть, какой процесс запустить).

Когда вы запускаете контейнер, Docker фактически берёт образ и оживляет его.

Образы хранятся в локальном кэше на вашей машине.


Если нужного образа нет, Docker Daemon скачивает его из реестра (Docker Hub
или иного).
Образы состоят из слоёв layered filesystem: чтобы изменить один слой, не
нужно перестраивать все остальные.

Почему это важно?

Переиспользование: если несколько образов используют одну базовую ОС


(например, Alpine Linux), то эти слои хранятся только один раз, экономя место.
Версионность: вы можете иметь разные теги (версии) (например, myapp:v1,
myapp:v2) и переключаться между ними.

2. Контейнеры (Containers)

Контейнер — это запущенный экземпляр образа. В момент запуска:

Docker создаёт отдельное пространство процессов, сети, монтирования


(namespaces).
Создаётся слой записи (Copy-on-Write), чтобы изменения в контейнере не
портили исходный образ.
Запускается основной процесс (например, nginx, python [Link]), который
живёт в своей мини-среде.

1/3
Если образ — это чертёж, то контейнер — это уже построенный дом (и, кстати, дом
можно перестраивать, не затрагивая чертёж).

Когда контейнер останавливается, процессы внутри него завершаются.


При удалении контейнера, вы теряете его временные изменения — но
исходный образ остаётся.

Главная фишка: один и тот же образ может быть использован для запуска многих
контейнеров (параллельно или по очереди).

3. Сети (Networks)
В Docker каждый контейнер, по умолчанию, получает собственный изолированный
сетевой стек. Это значит, что:

Контейнер имеет свой внутренний IP-адрес.


Общение между контейнерами организуется через виртуальные сети, которые
Docker Daemon настраивает автоматически.
При необходимости вы пробрасываете порты хоста внутрь контейнера
(например, -p 8080:80), чтобы приложение было доступно извне.

Основные типы сетей (позже разберём подробнее):

bridge — дефолтный, чаще всего используемый,


host — контейнер использует сеть хоста напрямую,
none — контейнер без сети,
overlay — для кластера Docker Swarm, когда контейнеры разбросаны по
разным хостам.

2/3
4. Тома (Volumes) и хранение данных
По умолчанию, файлы внутри контейнера живут только в слое записи и
пропадают, когда контейнер удалён. Если нужно сохранять данные дольше
(например, база данных), Docker предлагает:

Volumes — специальные тома, которые существуют вне контейнера, но


монтируются внутрь.
Bind mounts — монтирование директории хоста прямо в контейнер.

Это позволяет:

Сохранять данные между перезапусками контейнеров.


Обмениваться файлами между контейнером и хостом (в том числе для
разработки).

5. Dockerfile, docker-compose, registry…

Dockerfile: это шаблон для создания образа: скопируй вот этот файл,
поставь такую-то библиотеку, запусти команду...
docker-compose: инструмент для описания (в YAML) нескольких сервисов,
сетей и томов. Полезно, когда нужно поднимать, например, веб-приложение и
базу данных одновременно.
Registry (реестр): хранилище образов (Docker Hub, GitHub Container Registry,
локальный registry).
Docker CLI: набор команд (run, ps, build, pull, push).

Эти вещи мы рассмотрим в следующих уроках более подробно. Пока нужно


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

3/3
5. Первая практика: пробуем Docker в деле
Пора перейти от теории к практике — пусть и самой базовой. Идея в том, чтобы
убедиться, что Docker установлен и работает, и почувствовать, как выглядят
простейшие операции. В этом задании не нужно писать Dockerfile или понимать все
тонкости. Главное — запустить пару команд и увидеть, что у нас живой Docker.

Подготовка

1. Установите Docker (если ещё не установили).


Windows/Mac: скачайте Docker Desktop.
Linux: в зависимости от дистрибутива (Ubuntu, Debian, Fedora) следуйте
инструкциям на [Link].
2. Убедитесь, что Docker Daemon запущен (Docker Desktop с зелёной иконкой,
на Linux — сервис запущен).

Шаг 1: Проверяем версию Docker

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

docker version

Если всё в порядке, вы увидите информацию о клиенте Docker (Client) и


демоне (Server), их версии и т. д.
Если появилась ошибка «Cannot connect to the Docker daemon» или подобная,
проверьте, запущен ли Docker.

Шаг 2: Hello World от Docker

Самое простое демо — запустить официальный тестовый контейнер hello-world:

docker run hello-world

Что происходит?

1. Docker проверяет, есть ли образ hello-world локально. Если нет — скачивает


(pull) из Docker Hub.
2. Запускает контейнер. Контейнер выводит сообщение в консоль и завершается.

В результате вы должны увидеть что-то вроде:

Hello from Docker!


This message shows that your installation appears to be working correctly...

Шаг 3: Запускаем постоянный контейнер


Давайте запустим настоящий сервис, например, Nginx (веб-сервер). Попробуем
запустить контейнер, который не сразу завершается:

1/3
docker run --name mynginx -d -p 8080:80 nginx

--name mynginx даёт контейнеру понятное имя (mynginx).


-d (detached) запускает контейнер в фоновом режиме.
-p 8080:80 значит: «Привяжи порт 8080 на хосте к порту 80 внутри
контейнера».

Проверка

1. Введите docker ps — вы увидите список запущенных контейнеров. Там будет


mynginx.
2. Откройте в браузере [Link] Вы должны увидеть дефолтную
стартовую страницу Nginx («Welcome to nginx»).

Шаг 4: Останавливаем и удаляем контейнер


Когда вы закончите с Nginx, сделайте:

docker stop mynginx

(Остановит контейнер)

docker rm mynginx

(Удалит контейнер из списка остановленных).

Если хотите убедиться, что контейнер удалён, проверьте снова:

docker ps -a

Он больше не должен отображаться.

Шаг 5: Посмотреть образы


По ходу вы уже скачали пару образов: hello-world и nginx. Узнать, что локально
хранится, можно командой:

docker images

Там вы увидите список образов (Repository, Tag, Image ID, Size).

Итог
Что мы сделали:

1. Проверили установленный Docker (docker version).


2. Запустили «Hello, world!» контейнер (он завершился сразу).
3. Запустили веб-сервер Nginx, увидели реальный сервис на [Link]
4. Остановили и удалили контейнер.
5. Посмотрели, какие образы скачаны.

2/3
Таким образом вы убедились, что Docker у вас работает, и что команда docker run
действительно поднимает контейнер. В следующих практиках мы начнём
разбираться глубже: как строится образ (Dockerfile), как конфигурируется
контейнер (сеть, тома), и прочие “хитрости” Docker. Но этот небольшой шаг уже
показывает основной принцип: одна команда — и у вас в считанные секунды
запущен полноценный сервис.

3/3
2.6 Ключевые команды Docker

На предыдущем шаге мы сделали первую пробную практику, просто запустив


несколько контейнеров и остановив их. Теперь давайте системно разберём
базовые команды Docker, без которых не обходится ни один день работы с
контейнерами.

1. docker run

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

Пример:

docker run nginx

Если образ nginx не найден локально, Docker подтянет его из реестра (Docker
Hub).
Запустит контейнер, выполняя внутри него основной процесс (у nginx это веб-
сервер).

Полезные флаги:

1/5
-d (detached):
Запуск в фоновом режиме. Это значит, что ваш терминал сразу вернётся к
приглашению командной строки, а контейнер продолжит работать в фоне.
Пример: docker run -d nginx
После такого запуска nginx не будет занимать ваш терминал, и вы сможете
продолжить вводить другие команды.
Если бы вы не использовали -d, то при запуске nginx вы видели бы логи прямо
в консоли, и контейнер захватил бы терминал до тех пор, пока вы не
остановите процесс (Ctrl+C или docker stop).

-p 8080:80 (перенаправление портов):


Указывает, что на хостовой машине (где установлен Docker) порт 8080 будет
проброшен в порт 80 внутри контейнера.
Пример: docker run -d -p 8080:80 nginx
Это значит, что когда вы зайдёте в браузере по адресу [Link]
вы попадёте на работающий внутри контейнера веб-сервер, который слушает
порт 80.
Таким образом, вы прокидываете внутренний порт контейнера наружу, чтобы
он стал доступен извне.
Можно назначить любой свободный порт на хосте, например 3000:80 или
5000:80 и т.д.

--name <имя>:
Позволяет задать контейнеру понятное имя вместо случайного ID.
Пример: docker run -d --name mynginx -p 8080:80 nginx
Так мы будем знать, что именно этот контейнер называется mynginx, и сможем
удобнее обращаться к нему (остановить, посмотреть логи и т.д.).

-e KEY=VALUE (передача переменных окружения):


Позволяет задать в контейнере переменные среды, которые могут
использоваться приложением.
Пример: docker run -d -e ENV=prod -p 8080:80 myimage
Здесь ENV=prod может сигнализировать приложению, что оно запущено в
«production»-режиме.

2. docker ps
Отображает список запущенных контейнеров.

docker ps — показывает только активные (running).


docker ps -a — показывает все контейнеры (включая остановленные).

Пример:

docker ps

2/5
Результатом будет таблица с колонками (CONTAINER ID, IMAGE, COMMAND,
CREATED и т. д.), чтобы видеть, какие контейнеры в данный момент живут и
работают.

3. docker stop и docker rm


В повседневных задачах часто нужно останавливать и удалять контейнеры:

docker stop mynginx


docker rm mynginx

stop говорит контейнеру аккуратно завершиться (посылает ему сигнал


SIGTERM). Если контейнер реагирует на этот сигнал, он может корректно
«сохранить данные» и освободить ресурсы.
rm убирает контейнер из списка (Docker перестаёт помнить о нём). Когда
контейнер удалён, вы не сможете вернуться к его состоянию, логам и т.п.

4. docker pull

Команда, которая явно скачивает образ из реестра (Docker Hub или другого
реестра, если указаны соответствующие настройки):

docker pull redis

Если нужного тега не указано, Docker подтянет latest (при наличии). Например:

docker pull node:14

— вы скачиваете образ [Link] с тегом 14.

3/5
Обычно docker run сам делает pull, если образа нет локально. Но docker pull
полезен, когда вы заранее хотите убедиться, что нужная версия образа у вас есть (к
примеру, в скриптах или CI/CD-процессах).

5. docker images
Чтобы посмотреть, какие образы локально хранятся, используйте:

docker images

Покажет таблицу с колонками REPOSITORY, TAG, IMAGE ID, CREATED, SIZE.

Если образ вам больше не нужен, можно удалить командой docker rmi <image>.
(Осторожно: если этот образ используется каким-то контейнером, Docker не
даст просто так его удалить.)

6. docker rmi

Для удаления образа из локального хранилища:

docker rmi node:14

Если у вас останется контейнер, основанный на этом образе, Docker не позволит


удалить образ (или придётся использовать -f, что может быть рискованно, так как
может нарушить контейнеры).

7. docker pull, docker rmi, docker images вместе

Почему важно понимать их в связке:

docker pull подтягивает образы из реестра.


docker images показывает, что у нас уже скачано.
docker rmi удаляет ненужные образы, чтобы экономить дисковое
пространство.

4/5
8. Дополнительные команды в контексте
1. docker kill:
Резко завершает контейнер (в отличие от stop, который посылает SIGTERM).
kill сразу посылает SIGKILL, не давая приложению времени на корректное
закрытие.

2. docker rename:
Переименовывает контейнер. Не часто используется, но бывает полезно, если
дали неудобное имя.
Пример: docker rename old_name new_name

3. docker port, docker inspect:


docker port <container> — показать порты, которые контейнер слушает и как
они проброшены на хост.
docker inspect <container> — вывести детальную информацию в формате
JSON (сеть, смонтированные тома, переменные окружения и т.д.).

4. docker logs:
Выводит логи контейнера: всё, что контейнер напечатал (stdout / stderr) с
момента запуска.
Пример: docker logs mynginx
Можно использовать флаг -f (follow), чтобы подписаться на логи в режиме
реального времени:
docker logs -f mynginx.

Мы вернёмся к ним позже, когда будем глубже настраивать и отлаживать


контейнеры.

Итог
Вот тот минимальный набор команд, без которых невозможно прожить день в
Docker:

docker run — запуск контейнера (с флагами -d, -p, -e и т. д.).


docker ps — список (активных) контейнеров, с -a чтобы видеть все (включая
остановленные).
docker stop/docker rm — останавливаем и удаляем контейнеры, когда они
больше не нужны.
docker pull, docker images, docker rmi — управление образами (скачать,
просмотреть локальные, удалить).
docker logs — просмотреть вывод (логи), который контейнер печатает во
время работы.

5/5
4 марта 2025 г.

2.7 Практика

Проверяем базовые команды Docker с секретным кодом


В прошлом уроке мы изучили команды которые повседневно используются в при
работе с Docker. Теперь давайте зафиксируем эти знания на практике. Мы
используем специальный образ, который выдаёт секретный код при правильном
запуске. Код подтвердит, что вы корректно выполнили все шаги.

Задание

1. Скачайте (pull) образ из публичного Docker-репозитория —


prostodevops/docker-basics-checker:1.0
2. Запустите контейнер под именем checker, причём:
Запустите его в фоновом режиме
Передайте переменную окружения KEY=devops2025
3. Убедитесь, что контейнер действительно работает — просмотрите список
контейнеров.
4. Откройте логи контейнера, чтобы увидеть сообщение от приложения. Там
будет секретный код.
5. Остановите контейнер, затем удалите его, чтобы не оставался мусор.
6. Впишите секретный код в форму ответа (тот код, который вы увидели в
логах).

Подсказки (не раскрывайте, пока сами не попробуете)


1. Pull образа

docker pull prostodevops/docker-basics-checker:1.0

2. Запуск контейнера

docker run -d \
--name checker \
-e KEY=devops2025 \
prostodevops/docker-basics-checker:1.0

-d означает фоновый режим;


--name checker даёт имя «checker»;
-e KEY=devops2025 — переменная окружения, без неё код не
отобразится.
3. Смотрим список контейнеров

docker ps

1/2
4. Логи

docker logs mychecker

Ищем строку вида «Поздравляю! Твой секретный код это


ВЫ_МЫ_ТЫ-2312213».

5. Остановка и удаление

docker stop mychecker


docker rm mychecker

6. Ввод кода
Введите, например, «1234-ABCD» в форму ответа.

Результат

Если всё сделано правильно, вы увидите в логах строку «Поздравляю! Твой


секретный код это: …».

2/2
2.7 Практика

Проверяем базовые команды Docker с расширенными


флагами
В прошлом задании вы запустили контейнер и нашли секретный код в логах.

Давайте немного усложним задачу: теперь контейнер будет поднимать простой веб-
сервис на порту 80, а мы «пробросим» этот порт на хост (например, на 9999), чтобы
получить доступ к секретному коду по адресу [Link] Также
необходимо передать переменную окружения KEY=supersecret, без которой код не
отобразится.

Задание
1. Скачайте (pull) образ из публичного Docker-репозитория —
prostodevops/docker-basics-checker:2.0.
2. Запустите контейнер с именем secretserver. Нужно:
работать в фоновом режиме (-d),
пробросить порт 9999 (хост) на 80 (контейнер),
передать переменную окружения KEY=supersecret (иначе код не
появится),
дать контейнеру читаемое имя (--name secretserver).
3. Убедитесь, что контейнер действительно запущен — посмотрите список
активных контейнеров.
4. Проверьте работу веб-сервиса, открыв [Link] (или через
curl). Если контейнер правильно поднялся и переменная окружения задана,
вы увидите строку вида: «Поздравляю! Ваш код: 1111-AAAA».
5. Остановите контейнер, затем удалите его, чтобы не оставалось «мусора».
6. Вставьте секретный код (тот, который вы увидели в сообщении) в форму
ответа.

Подсказки (скройте, пока сами не попробуете)


1. Скачивание образа:

docker pull prostodevops/docker-basics-checker:2.0

1/2
2. Запуск контейнера (пример, чтобы открыть на localhost:9999):

docker run -d \
--name secretserver \
-p 9999:80 \
-e KEY=supersecret \
prostodevops/docker-basics-checker:2.0

-d — фоновый режим
-p 9999:80 — проброс порта (хост:контейнер)
-e KEY=supersecret — переменная окружения, без неё сервис не
покажет код
--name secretserver — задаём читаемое имя контейнера
3. Список контейнеров:

docker ps

4. Проверка сервиса в браузере/терминале:

curl [Link]

В ответе найдёте строку вида «Поздравляю! Ваш код: 1111-AAAA».

5. Остановка и удаление:

docker stop secretserver


docker rm secretserver

6. Вставьте код вида «1111-AAAA» в форму ответа.

Результат

Если вы всё сделали правильно, то при обращении к [Link] и/или


просмотре логов контейнера увидите строку типа «Поздравляю! Ваш код: …».

Именно этот код нужно отправить как подтверждение, что вы:

Умеете запускать контейнер в фоновом режиме (-d),


Понимаете, как пробрасывать порты (-p),
Передавать переменные окружения (-e KEY=...),
И просматривать логи (docker logs).

2/2
2.7 Практика

Более продвинутые команды


В этом задании секретный код будет спрятан в метаданных контейнера, доступных
через docker inspect.

Задание
1. Скачайте (pull) образ из публичного Docker-репозитория
prostodevops/docker-basics-checker:3.0.
2. Запустите контейнер под определённым именем в фоновом режиме,
пробросив соответствующие порты и передав переменную окружения
KEY=inspecttest.
3. Убедитесь, что контейнер действительно запущен — проверьте список
активных контейнеров.
4. Переименуйте контейнер (потренируйтесь в использовании docker rename) и
убедитесь, что новое имя отобразилось в списке.
5. Проверьте проброшенные порты при помощи docker port, чтобы
убедиться, что внутренняя портовая конфигурация контейнера соответствует
вашей настройке.
6. Изучите метаданные контейнера через docker inspect, чтобы найти
секретный код. Он может быть спрятан в Labels или среди переменных
окружения.
7. Посмотрите логи контейнера (хотя кода там нет, стоит удостовериться, что
контейнер запущен корректно и не даёт ошибок).
8. Остановите контейнер и удалите его, чтобы не оставался мусор.
9. При желании, удалите образ, если он больше не нужен.
10. Вставьте секретный код, найденный при docker inspect, в форму ответа.

Подсказки (раскрывайте, если застряли)


1. Скачивание образа:

docker pull prostodevops/docker-basics-checker:3.0

2. Запуск контейнера:

docker run -d \
--name meta-checker \
-p 7777:80 \
-e KEY=inspecttest \
prostodevops/docker-basics-checker:3.0

3. Переименование контейнера:

docker rename meta-checker my-meta-hero

1/2
4. Просмотр портов:

docker port my-meta-hero

5. Изучение метаданных (поиск кода):

docker inspect my-meta-hero

Обратите внимание на разделы Labels и Env.

6. Логи контейнера:

docker logs my-meta-hero

7. Остановка и удаление:

docker stop my-meta-hero


docker rm my-meta-hero

или:

docker kill my-meta-hero


docker rm my-meta-hero

8. Удаление образа (необязательно):

docker rmi prostodevops/docker-basics-checker:3.0

2/2
2.8 Работа внутри контейнера

Ранее мы рассмотрели базовые команды docker run, docker ps, docker stop,
docker rm, docker pull и т. д. Теперь поговорим о том, как взаимодействовать с уже
запущенным контейнером: заходить внутрь, выполнять команды, а также
копировать файлы между хостом и контейнером.

1. docker exec – выполнение команд внутри контейнера

Если контейнер уже запущен, и вы хотите выполнить в нём дополнительную


команду (например, посмотреть содержимое директории или запустить маленькую
утилиту), используйте docker exec.

docker exec <имя_или_id_контейнера> <команда>

Пример:

docker exec mynginx ls -l /usr/share/nginx/html

Команда ls -l будет выполнена внутри контейнера mynginx.

2. Интерактивный доступ: docker exec -it

Часто нужно зайти в контейнер, чтобы выполнить несколько команд подряд в shell –
чем-то похоже на SSH. Для этого добавляем флаги -i (interactive) и -t (pseudo-
TTY):

docker exec -it mynginx /bin/bash

/bin/bash – запускает bash в контейнере (если он там установлен). В Alpine-


образах зачастую доступен только /bin/sh.
Теперь вы внутри контейнера, можете выполнять команды (cd, ls, apt-get и т.
д.).
Чтобы выйти, наберите exit.

1/3
Важно: docker exec – это запуск дополнительного процесса внутри контейнера,
который не мешает основному.

3. Разница docker attach и docker exec


docker attach подключается к главному процессу контейнера. Если этот процесс —
например, ваш сервер, вы начнёте видеть его stdout/stdin (что не всегда удобно).

docker exec, напротив, создаёт новый процесс (shell, утилиту), что безопаснее и
гибче для отладки. В большинстве случаев сейчас используют именно docker exec
-it для интерактивного входа.

4. Копирование файлов: docker cp

Бывает нужно перекинуть файлы внутрь контейнера или изнутри контейнера на


хост. Для этого существует команда docker cp:

docker cp [локальный_путь] [container_name]:[путь_в_контейнере]


docker cp [container_name]:[путь_в_контейнере] [локальный_путь]

Пример (хост → контейнер):

2/3
docker cp [Link] mynginx:/usr/share/nginx/html/[Link]

Файл [Link] с вашего компьютера попадает в каталог


/usr/share/nginx/html внутри контейнера mynginx.
Пример (контейнер → хост):

docker cp mynginx:/usr/share/nginx/html/[Link]
./index_from_container.html

Вы забираете файл [Link] из контейнера и сохраняете его под именем


index_from_container.html у себя.

Ограничение: если контейнер удалён, файлы в нём (если они не были в volume)
пропадают. Поэтому docker cp — удобно для быстрого обмена, но для постоянного
хранения данных надо использовать тома (volumes). Мы поговорим о них в
следующих уроках.

5. Как найти контейнер: docker ps -a

Помните, если вы не видите ваш контейнер в docker ps, возможно, он остановлен.


Используйте docker ps -a, чтобы увидеть все контейнеры и найти нужный
container_name_or_id для команд docker exec или docker cp.

Итог
docker exec -it [container] bash — интерактивный доступ внутрь
контейнера (shell).
docker cp — копирование файлов в/из контейнера.
docker attach — «подключение» к главному процессу (используется реже).

Теперь вы умеете заходить в контейнер, настраивать или отлаживать его изнутри, а


также копировать файлы без пересборки образа. Позже мы рассмотрим логику
хранения данных (volumes) и другие аспекты постоянного хранения.

3/3
11 марта 2025 г.

2.9 Практика

Практика: Собираем пазл внутри контейнера, копируем файл


— и получаем код
В этом задании нужно не только зайти в контейнер, но и скопировать файл в него,
чтобы разблокировать секретный код. Представьте, что внутри контейнера есть
скрипт, который ищет специальный файл (например, /tmp/[Link]) — если
он найден, контейнер в логах выводит секретный код, иначе пишет: “Пазл не
решён!”

Задание

1. Скачайте (pull) образ prostodevops/cp-puzzle:1.0.


2. Запустите контейнер в фоновом режиме под именем puzzle (без портов —
нам достаточно логов и копирования файлов).
3. Проверьте логи контейнера. Скорее всего, он пожалуется: «Файл
/tmp/[Link] не найден».
4. Создайте на хосте файл [Link] с любым содержимым (хотя бы
“Puzzle solved!”).
5. Скопируйте этот файл в контейнер — именно в /tmp/[Link], чтобы
скрипт “увидел” его.
6. Посмотрите логи контейнера после этого. При необходимости перезапустите
контейнер, чтобы скрипт заново проверил файл. Теперь должен появиться
секретный код.
7. Зайдите в контейнер интерактивно (через shell), чтобы убедиться, что файл
действительно там.
8. Остановите и удалите контейнер.
9. Вставьте «секретный код» (из логов) в форму ответа.

Подсказки (смотрите только при необходимости)


1. Pull образа:

docker pull prostodevops/cp-puzzle:1.0

2. Запуск контейнера (фоновый режим, с именем puzzle):

docker run -d \
--name puzzle \
prostodevops/cp-puzzle:1.0

3. Проверка логов (скорее всего, увидите «Пазл не решён!»):

docker logs puzzle

4. Создание файла на хосте (например, в текущей директории):

1/2
echo "Puzzle solved!" > [Link]

5. Копирование файла внутрь контейнера:

docker cp [Link] puzzle:/tmp/[Link]

6. Перезапуск (если нужно) и повторная проверка логов:

docker restart puzzle


docker logs puzzle

Теперь должен появиться «Поздравляю! Ваш код: …» 7. Зайти внутрь контейнера


(для проверки):

docker exec -it puzzle bash


ls /tmp
cat /tmp/[Link]
exit

8. Остановка и удаление:

docker stop puzzle


docker rm puzzle

2/2
12 марта 2025 г.

2.9 Практика

Запускаем Python-скрипт внутри контейнера — ищем


зашифрованный код
В этом задании у вас есть контейнер, который содержит Python, но не имеет
основного скрипта /opt/app/[Link]. Без него в логах пишется: «Скрипт не найден,
ничего не делаю». Однако на самом деле секретный код спрятан внутри самого
контейнера и откроется, если вы загрузите и запустите нужный Python-скрипт. Ваша
задача — скопировать [Link] в контейнер, запустить его и таким образом
вытащить код.

Сценарий
Предположим, образ prostodevops/python-runner:1.0 умеет проверять, есть ли
/opt/app/[Link]. Если есть, он даёт возможность запустить скрипт, который
обращается к скрытой функции (внутри образа), которая выдаёт сообщение вида
«Поздравляю! Ваш код: ...». Без [Link] контейнер просто выводит: «No [Link]
found, I'm idle...». Вам нужно «разбудить» контейнер, передав ему этот скрипт и
запустив его.

Задание
1. Сделайте pull образа — prostodevops/python-runner:1.0. По легенде, он
содержит Python и скрытую логику выдачи кода.
2. Запустите контейнер (фоновый режим, имя pycontainer) и посмотрите логи.
Скорее всего, увидите «No [Link] found, I'm idle...».
3. Создайте локально файл [Link]. В нём может быть, например:

#!/usr/bin/env python3
print("Активируем скрытый код...")
import secret
secret.show_code()

На самом деле, настоящий секрет уже в образе, а этот [Link]


пробуждает его.

4. Скопируйте [Link] в контейнер (/opt/app/[Link]), чтобы контейнер смог


обнаружить его.
5. Запустите скрипт внутри контейнера (через docker exec), используя Python.
Если всё ок, контейнер теперь «увидит» [Link] и распечатает: «Поздравляю!
Ваш код: ЕУУУУ-211231».
6. Проверьте логи контейнера — возможно, там тоже появится дублирующее
сообщение с кодом.
7. Остановите и удалите контейнер.
8. Впишите секретный код (например, ЕУУУУ-211231) в форму ответа.

1/3
Подсказки (раскрывайте при необходимости)
1. Pull образа:

docker pull prostodevops/python-runner:1.0

2. Запуск контейнера:

docker run -d --name pycontainer prostodevops/python-runner:1.0


docker logs pycontainer

Скорее всего, «No [Link] found, I'm idle...». Или что-то подобное.

3. Создайте скрипт [Link] (у себя на хосте):

cat <<EOF > [Link]


#!/usr/bin/env python3
print("Активируем скрытый код...")
import secret
secret.show_code()
EOF

4. Копируйте скрипт внутрь контейнера:

docker cp [Link] pycontainer:/opt/app/[Link]

5. Запустите его внутри контейнера:

docker exec pycontainer python3 /opt/app/[Link]

Если всё ок, “скрытый код” внутри образа среагирует и выведет: «Поздравляю!
Ваш код: ЕУУУУ-211231 (пример).

6. Проверьте логи:

docker logs pycontainer

В логах может быть то же сообщение.

7. Остановка и удаление:

docker stop pycontainer


docker rm pycontainer

Результат

Если в результате увидите строку вида «Поздравляю! Ваш код:


ЕУУУУ-211231 (или другую), значит контейнер принял [Link] и раскрыл зашитый
код. Вы подтвердили, что умеете:

запускать контейнер (docker run) и смотреть его логи,


копировать скрипт внутрь (docker cp),
запускать его через docker exec,
и при необходимости проверять логи снова (docker logs).

2/3
Впишите секретный код в форму ответа, и вы успешно завершите практику!

3/3
16 марта 2025 г.

2.9 Практика

💥 Тройная проверка через переменные, конфиг и лицензию


В этом задании у вас есть контейнер, который содержит в себе скрытый код .
Однако, чтобы контейнер раскрыл этот код, ему нужны три сигнала:

1. Переменная окружения SECRET_KEY


2. Файл конфигурации по пути /app/config/[Link]
3. Файл лицензии /app/license

Только если все три условия выполнены, приложение (зашитое в образ) генерирует
код и выводит его в логах (и/или при перезапуске). Иначе оно сообщает, чего не
хватает. Ваша задача — удовлетворить эти три условия: передать переменную,
смонтировать или скопировать файлы — и получить код

Сценарий

Образ prostodevops/triple-check:1.0 запускает скрипт, который проверяет:

SECRET_KEY (ENV)
наличие /app/config/[Link]
наличие /app/license

Если всё найдено, контейнер радуется и выводит, к примеру: «All checks passed!
SuperCode: 2023-FULL-PASS» (или аналогичную строку). Иначе ругается, указывая,
что отсутствует.

Задание
1. Сделайте pull образа prostodevops/triple-check:1.0.
2. Подготовьте локально:
Файл [Link] (например, secret_data: "Some config info"),
Файл license (к примеру, Licensed to devops-user).
3. Запустите контейнер (имя tripleapp) так, чтобы:
Передать переменную окружения SECRET_KEY (например, mysuperkey).
Примонтировать (bind mount) ваш [Link] внутрь
/app/config/[Link]. (Или используйте volume, если хотите.)
Пока без файла /app/license — посмотрим, что скажет логи.
4. Проверьте логи контейнера. Скорее всего, увидите фразу вроде: «License
missing» или «File /app/license not found».
5. Скопируйте лицензию (файл license) в контейнер через docker cp —
положите её в /app/license. Возможно, нужно перезапустить контейнер,
чтобы скрипт заново проверил условия.

1/3
6. Снова гляньте логи. Теперь должно появиться «All checks passed! SuperCode:
2023-FULL-PASS» (пример). Это значит, что все три условия выполнились.
7. Зайдите (при желании) внутрь контейнера (через docker exec -it tripleapp
bash), чтобы посмотреть, действительно ли файлы на месте.
8. Остановите и удалите контейнер.
9. Впишите код (например, 2023-FULL-PASS) в форму ответа.

Подсказки (используйте только при необходимости)


1. Pull образа:

docker pull prostodevops/triple-check:1.0

2. Создайте локальные файлы:

echo 'secret_data: "Some config info"' > [Link]


echo 'Licensed to devops-user' > license

3. Запуск контейнера (пример bind mount + ENV):

docker run -d --name tripleapp \


-e SECRET_KEY=mysuperkey \
-v $(pwd)/[Link]:/app/config/[Link] \
prostodevops/triple-check:1.0

Проверяем логи, likely «License missing!». Если приложение проверяет


лицензию только при старте, то после копирования лицензионного файла
сделайте docker restart tripleapp.

4. Копирование license:

docker cp license tripleapp:/app/license


docker restart tripleapp

5. Снова логи:

docker logs tripleapp

Если всё три условия (ENV, config, license) соблюдены, «All checks passed!
SuperCode: ???»

6. Остановка и удаление:

docker stop tripleapp


docker rm tripleapp

Результат
Таким образом, вы применили сразу несколько приёмов:

-e SECRET_KEY=… (ENV),
-v (bind mount) или volumes,
docker cp (копирование файла license),

2/3
docker restart (чтобы логика пересканировала условия),
docker logs и docker exec для проверки.

Когда все три сигнала собрались, контейнер отдал код. Вставьте этот код в форму
ответа, подтверждая успех!

3/3
2.10 Логи

В предыдущих уроках мы научились запускать контейнеры, заходить в них и


копировать файлы. Но чтобы понимать, что происходит в контейнере во время
работы, нужно уметь смотреть логи и базовые метрики. Docker даёт для этого
несколько инструментов: docker logs, docker stats, а также дополнительные
команды вроде docker top и docker events. Рассмотрим их по порядку.

1. docker logs: где и как смотреть вывод приложения?


Когда контейнер запускается, всё, что приложение пишет в STDOUT/STDERR,
попадает во внутренний лог Docker. Команда docker logs показывает этот вывод:

docker logs <container_name_or_id>

Пример: docker logs mynginx – вы увидите все записи, которые Nginx вывел с
момента запуска.
Если контейнер говорлив, лог может быть очень длинным.

Полезные флаги:

-f (follow): аналог tail -f – прямая трансляция новых строк лога.


--tail <N>: показать последние N строк (удобно, если лог огромный).
--since <timestamp>: показать логи, начиная с указанного времени
(поддерживается в более новых Docker-версиях).

Важно: если контейнер завершился, docker logs всё равно доступен, пока вы не
удалили контейнер. После docker rm логи тоже исчезают.

1/4
2. Анализ логов: кратко о продвинутых возможностях

Фильтрация или поиск ошибок: docker logs myapp | grep ERROR.


Для крупных проектов есть лог-драйверы (Fluentd, Syslog, AWS Logs), которые
перенаправляют STDOUT/STDERR в централизованные системы. Это уже
более высокий уровень логирования.
Log rotation: если контейнер генерирует гигабайты логов, стоит настраивать
ротацию или подключать внешнюю систему. В противном случае логи могут
заполнить диск.

3. docker stats: базовый мониторинг ресурсов

Чтобы увидеть, сколько CPU, RAM, сети, IO – используют ваши контейнеры в


реальном времени, применяйте:

docker stats

Отобразятся все запущенные контейнеры со столбцами (CONTAINER, CPU %,


MEM USAGE / LIMIT, NET I/O и т. д.).
Можно указать конкретные контейнеры, например: docker stats mynginx
myredis.
CTRL + C завершает просмотр.

Когда это нужно: если контейнер тормозит систему или падает из-за переполнения
памяти, docker stats быстро подскажет, где проблема. Для более сложного
мониторинга существуют инструменты вроде cAdvisor, Prometheus/Grafana.

4. docker top: какие процессы внутри контейнера?

Если вы захотите увидеть, какие процессы сейчас работают внутри контейнера:

docker top <container>

Покажет PID, команду, время запуска и т. д. Это полезно, если в контейнере


запущено несколько процессов
В некоторых ОС (особенно Windows) вывод может отличаться.

2/4
5. docker events: отслеживание событий Docker

Это более продвинутая команда, позволяющая слушать события Docker — запуск/


остановка контейнеров, создание сети, монтирование томов и т. д.:

docker events

Вы увидите поток событий: container start, container stop, network create


и проч.
В большинстве случаев на базовом уровне не нужно, но иногда полезно для
отладки в реальном времени.

6. Разница между файловыми логами и docker logs

docker logs читает именно потоки STDOUT/STDERR контейнера. Если ваше


приложение пишет во /var/log/[Link], то Docker не видит это по умолчанию,
если вы не настроили перенаправление (или log driver).
Многие рекомендуют писать логи в поток (stdout/stderr), чтобы docker
logs мог всё поймать, и дальше перенаправлять в нужную систему.

Итог
docker logs <container>: смотрим текущие или прошлые логи приложения, -
f для живого режима.
docker stats: базовые метрики ресурсов (CPU, RAM, сети, диска).
docker top: какие процессы внутри контейнера?
docker events: мониторинг событий Docker.

С этими командами вы сможете анализировать, что происходит в контейнере


прямо во время работы: понимать, почему он падает или съедает ресурсы, и
быстро обнаруживать ошибки, выведенные приложением.

3/4
4/4
31 марта 2025 г.

2.11 Практика

Разворачиваем веб-приложение и исследуем его логи,


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

Сценарий задания

Запустите контейнер на основе любого образа с веб-сервером или


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

Что сдавать?

Перечислите все команды, с помощью которых вы выполнили задание

Подсказка
Для запуска контейнера с пробросом порта можно использовать -d и -p.
Логи контейнера удобно смотреть командой docker logs, при необходимости с
флагом -f.
Чтобы узнать о потреблении ресурсов, примените docker stats.
Список процессов доступен через docker top.
Для отслеживания событий Docker запустите docker events, а затем
остановите или перезапустите контейнер.

1/1
3.1 Что такое Image?

Мы уже упомянули, что контейнер — это запущенное приложение, а образ


(image) — это шаблон или слепок для его запуска. Давайте разберём подробнее,
что такое слои образа и как всё работает.

Слои (layers)
Представьте, что у вас есть:

Базовый слой — например, минимальная система (Alpine/Ubuntu), с ядром и


базовыми утилитами.
Второй слой, где вы устанавливаете нужные пакеты (RUN apt-get install ...).
Третий слой, куда вы копируете файлы приложения (COPY . /app).
И так далее... каждый шаг в Dockerfile создаёт новый слой (условно)

Как они складываются? Docker использует специальную технологию union file


system, которая позволяет наложить эти слои один на другой. Итоговое
представление кажется цельной файловой системой, но на самом деле это
несколько наложенных директорий.

1/5
Почему это удобно?

Кэширование: Если вы меняете что-то в верхнем слое, нижние слои не


пересобираются. Это экономит время и трафик при сборке образов.
Повторное использование: Один базовый слой (допустим, Alpine:3.17) может
использоваться в разных образах, не копируясь каждый раз заново.
Защита: Нижние слои только для чтения (read-only). Если контейнер что-то
изменяет, изменения попадают в дополнительный слой (Copy-on-Write). Это
предотвращает порчу исходных слоёв.

Copy-on-Write

2/5
Когда вы запускаете контейнер, Docker добавляет поверх всех слоёв ещё один слой
записи (COW layer). Представьте, что все нижние слои — read-only, а любые
изменения (создание файла, удаление, изменение) фактически записываются в
этот верхний слой записи. Так достигается:

Изоляция: вы не портите базовый образ; всё, что поменялось, касается только


конкретного контейнера.
Экономия: если у вас 10 контейнеров на основе одного образа, общий вес
слоёв не дублируется.

Пример

1. Базовый слой: Alpine Linux, вес ~5 МБ.


2. Слой установки пакетов: RUN apk add nodejs npm, вес добавится сверху,
скажем, ещё 50 МБ.
3. Слой копирования кода: COPY . /app, может быть ещё 10 МБ, если у вас
большой проект.

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

3/5
Базовые образы и официальные репозитории

Часто Dockerfile начинается строкой вроде:

FROM ubuntu:20.04

Это значит: «Возьми базовый слой с именем ubuntu:20.04 из Docker Hub (если нет
локально) и стройся поверх него». Пользуясь такими официальными образами, вы
можете быть уверены в минимальном наборе необходимых инструментов
(например, для Ubuntu — базовая система), а далее уже делаете RUN, COPY и т. д.
для вашего приложения.

В Docker Hub есть масса официальных репозиториев (Ubuntu, Alpine, Debian,


Python, Node, Nginx и т. д.), которые сообщество и вендоры поддерживают в
актуальном состоянии. Это облегчает жизнь — не нужно вручную собирать базовую
ОС в каждом проекте.

Что происходит при docker run?

4/5
1. Docker проверяет, есть ли локальный образ (image). Если нет — скачивает
слои из реестра (Docker Hub).
2. Собирает эти слои вместе (read-only) и добавляет слой записи COW.
3. Запускает ваш процесс (ENTRYPOINT/CMD) внутри сформированной
файловой системы.

Почему важно понимать это устройство?

Оптимизация сборки: Зная, как работают слои, вы сможете


правильно строить Dockerfile, чтобы не пересобирать все слои из-за одной
мелочи.
Общий кэш: если два образа используют FROM node:14 — они делят этот
слой, экономя место.
Безопасность: Взяв официальный образ Alpine или Ubuntu, вы знаете, что
внутри, а взяв случайный образ — не всегда (проверяйте Docker Hub!).

Итог
Docker Image — это собрание слоёв, каждый шаг сборки формирует новый
слой.
Copy-on-Write позволяет контейнерам не портить исходные слои.
Официальные и базовые образы (ubuntu, alpine, node и др.) — фундамент,
на котором вы строите свою среду.
При docker run, Docker складывает слои и добавляет слой записи для
контейнера.

Осознав концепцию слоёв и базовых образов, вы будете более осмысленно


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

5/5
21 марта 2025 г.

3.2 Практика

Создаём свой первый Dockerfile

Что нужно сделать?


1. Создать рабочую папку проекта.
Например, создайте папку my-first-dockerfile. В ней будете хранить все
файлы для этого задания.
2. Подготовить простой скрипт или программу.
Для наглядности возьмём самый простой пример:

echo "Hello, Docker World!"

Сохраните это в файл [Link].


3. Написать Dockerfile.
Создайте файл с именем Dockerfile (без расширения). В нём будет несколько
строк:

FROM alpine:3.17 # Базовый образ


COPY [Link] / # Копируем наш скрипт в образ
CMD ["sh", "/[Link]"] # Команда, которая запустится в контейнере

FROM alpine:3.17: используем официальный образ Alpine Linux.


COPY [Link] /: переносим наш скрипт в корень файловой системы
контейнера.
CMD ["sh", "/[Link]"]: по умолчанию запускаем скрипт
[Link].
4. Собрать образ и запустить контейнер.
Выполните в терминале (находясь в директории с Dockerfile):

docker build -t my-first-image .


docker run --rm my-first-image

docker build создаёт образ, используя инструкции из Dockerfile. Флаг -t


даёт образу имя (my-first-image), а точка (.) говорит «бери Dockerfile из
текущей директории».
docker run запускает контейнер из образа. Флаг --rm автоматически
удаляет контейнер после остановки.
На экране вы увидите Hello, Docker World

В качестве ответа указать "сделал"

1/1
3 апреля 2025 г.

3.3 Dockerfile

Если ранее мы выяснили, что Docker Image — это набор слоёв (layers), то
Dockerfile — это инструкция, как эти слои собрать. В этом уроке подробно разберём
ключевые инструкции Dockerfile (FROM, RUN, COPY/ADD, CMD, ENTRYPOINT, ENV,
EXPOSE) и поймём, как они совместно формируют ваш образ.

1. FROM — базовый слой

Зачем? Любой образ (кроме scratch) строится поверх существующего базового


образа. FROM говорит Docker, с чего мы начинаем.

FROM <image>[:<tag>]

Пример: FROM alpine:3.17 — значит, берём базовую Alpine Linux 3.17 в


качестве отправной точки.
Если не указать тег, Docker возьмёт :latest, если он существует.
FROM scratch — особый случай, пустой базовый образ, чаще всего для
сборки минимальных бинарников (например, Go).

Важно: выбирайте базовый образ с умом: ubuntu:22.04 или alpine — это влияет на
размер, набор утилит, совместимость.

2. RUN — исполнение команд в процессе сборки

Синтаксис:

RUN <команда>

Каждый RUN создает новый слой в образе.


Типичные примеры: установка пакетов (RUN apt-get update && apt-get
install -y curl), компиляция исходников, удаление временных файлов и т. д.

Оптимизация: старайтесь объединять команды, чтобы не плодить лишние слои.


Пример:

RUN apt-get update && \


apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*

Таким образом, всё выполнится за один слой.

1/4
3. COPY и ADD — перенесём файлы в образ

3.1. COPY

COPY <локальный_путь> <путь_в_контейнере>

Простой перенос файлов из хостовой директории в образ.


Если надо копировать только определённые файлы (исключая node_modules
и прочее), используйте .dockerignore.

Пример:

COPY [Link] /app/


RUN npm install
COPY . /app

Сначала копируем только [Link] (чтобы не перегенерировать слой при


любом изменении кода), ставим зависимости, а потом копируем всё остальное.

3.2. ADD

ADD <локальный_или_URL> <путь_в_контейнере>

Возможность распаковать tar-архив или скачать файл по URL.


Если не нужны эти фишки, COPY лучше (более предсказуемо).

4. CMD & ENTRYPOINT — определяем, что запускать

Когда мы делаем docker run yourimage, Docker ищет, какую команду внутри
контейнера нужно стартовать.

CMD

Пример:

CMD ["npm", "start"]

По умолчанию, контейнер запустит npm start.


Если пользователь при docker run передаст другую команду (bash), то CMD
перезапишется.

ENTRYPOINT

Пример:

ENTRYPOINT ["python", "[Link]"]

Аргументы, которые пользователь указывает при docker run, добавляются к


этому ENTRYPOINT.
ENTRYPOINT не так просто перезаписать, если только не использовать --
entrypoint.

2/4
Совет: Имеет смысл использовать ENTRYPOINT для «главного» запуска, а CMD — для
«дефолтных» аргументов. Например:

ENTRYPOINT ["python", "[Link]"]


CMD ["--port", "8080"]

5. ENV — задаём переменные окружения

ENV <ключ> <значение>

Пример: ENV NODE_ENV production, ENV PORT 3000.


При запуске контейнера эти переменные будут доступны. Можно
переопределить флагом -e.

6. EXPOSE — заявление о порте

EXPOSE <port>

Говорим, что контейнер слушает на таком-то порту, но само по себе это не


публикует порт наружу. Нужен флаг -p при docker run, чтобы связать хост-
порт с контейнер-портом.
Нужно больше для документации и для Docker-кластеров (Swarm), где EXPOSE
учитывается при сервисах.

Дополнительно: WORKDIR, USER и другие инструкции

WORKDIR /app: переключает рабочую директорию. Все последующие


RUN/CMD/ENTRYPOINT выполняются в /app.
USER node: меняет пользователя, от которого будут выполняться команды
(безопасность: не оставаться root).
VOLUME /data: указывает, что /data является точкой монтирования тома
(volumes). Но это мы разберём позже.

Пример итогового Dockerfile

3/4
FROM alpine:3.17

# Устанавливаем нужные пакеты (слой RUN)


RUN apk add --no-cache nodejs npm

# Меняем рабочую директорию


WORKDIR /app

# Копируем [Link] и устанавливаем зависимости


COPY package*.json ./
RUN npm install

# Копируем остальной код


COPY . .

# Задаём переменные окружения


ENV NODE_ENV=production

# Указываем порт (декларативно)


EXPOSE 3000

# Команда по умолчанию
CMD ["npm", "start"]

Собираем: docker build -t my-node-app .


Запускаем: docker run -p 3000:3000 my-node-app

Итог
FROM — стартовый слой (базовый образ).
RUN — команды во время сборки (установка пакетов, компиляция и т. д.).
COPY / ADD — перенос файлов в образ (COPY обычно предпочтительнее).
CMD / ENTRYPOINT — как контейнер будет запускаться; CMD можно
перезаписать внешней командой, ENTRYPOINT — менее гибок, но даёт
добавление аргументов.
ENV — переменные окружения внутри образа.
EXPOSE — порт, который приложение слушает (для информации/
документации).

С этими инструкциями вы опишете, как собрать свой образ, добавите свой код и
зависимости, и определите, как запускать контейнер. В следующем уроке мы
обсудим оптимизацию, чтобы ваши образы были лёгкими и быстрыми:
объединение команд, multi-stage builds и прочие тонкости.

4/4
27 марта 2025 г.

3.4 Практика - Dockerfile в реальных задачах

В прошлом уроке мы рассмотрели базовый Dockerfile и основные инструкции (FROM,


RUN, COPY/ADD, CMD, ENTRYPOINT, ENV, EXPOSE). Теперь давайте посмотрим, как
решаются приближённые к реальным рабочим задачам сценарии с помощью
этого же набора инструкций.

1. Подготовка базового окружения для Python-сервиса

Сценарий: У вас есть микросервис на Python (например, Flask или FastAPI),


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

FROM: Выбираем образ Python нужной версии (python:3.9 или другой).


RUN apt-get update: Устанавливаем нужные системные библиотеки,
драйверы.
ENV: Задаём переменные окружения, например, для логирования или
рабочего окружения.
COPY: Копируем сам код сервиса и файлы зависимостей ([Link]).
RUN pip install -r [Link]: Устанавливаем Python-зависимости.
CMD: Запускаем сервис (например, Flask) на нужном порту.

FROM python:3.9

RUN apt-get update && apt-get install -y libpq-dev imagemagick


ENV PYTHONUNBUFFERED=1

WORKDIR /app
COPY [Link] /app/
RUN pip install --no-cache-dir -r [Link]

COPY . /app
EXPOSE 8000

CMD ["python", "[Link]"]

Что получаем: контейнер с нужными системными пакетами и предустановленными


Python-зависимостями, готовый для запуска сервиса.

2. Сборка и запуск [Link]-приложения (REST API или Frontend)

1/4
Сценарий: У вас есть приложение на [Link] ([Link], [Link] или
React/Vue/Angular), где нужно установить зависимости и запустить сервер/
собранный бандл.

FROM: Выбираем node:16 или актуальную версию Node.


WORKDIR: Устанавливаем рабочую директорию для удобства.
COPY: Сначала переносим [Link] и [Link], чтобы
кэшировался слой с зависимостями.
RUN npm install: Устанавливаем зависимости. Для фронтенда, например
React, на этом этапе можно сделать npm run build.
EXPOSE: Документируем порт (например, 3000).
CMD: Запускаем сервер или готовый билд.

FROM node:16

WORKDIR /app
COPY package*.json ./
RUN npm install

COPY . .
EXPOSE 3000

CMD ["npm", "start"]

Пример использования: docker build -t my-node-app ., затем docker run -p


3000:3000 my-node-app.

3. Сборка Java-приложения (Spring Boot)

Сценарий: У вас есть Spring Boot-приложение, которое компилируется через Gradle


или Maven в единый исполняемый JAR-файл (например, [Link]), и вам нужно
упаковать его в контейнер.

FROM: Можно использовать openjdk:17-jdk, если нужно собирать проект


внутри контейнера, либо openjdk:17-jre, если JAR уже собран локально.
COPY: Переносим JAR-файл в контейнер.
EXPOSE: Если приложение слушает на порту 8080, указываем EXPOSE 8080.
CMD: Запускаем JAR командой java -jar /[Link].

FROM openjdk:17-jdk
WORKDIR /app

COPY target/[Link] /app/[Link]

EXPOSE 8080
CMD ["java", "-jar", "/app/[Link]"]

Реальный кейс: Так удобно запускать микросервис или веб-приложение на Spring


Boot, не заботясь о том, какая версия Java и зависимости установлены на хост-
системе.

2/4
4. Подключение статических файлов и настройка Nginx
Сценарий: У вас есть статический сайт или фронтенд (React/Vue/Angular) после
сборки, и вы хотите отдать эти файлы через Nginx. Также может потребоваться
сконфигурировать Nginx, чтобы работать с бэкендом на другом сервисе.

FROM: Используем nginx:alpine как базу.


COPY: Копируем статические файлы (HTML, CSS, JS) в
/usr/share/nginx/html и при необходимости собственный конфиг Nginx
(например, для проксирования API-запросов).
EXPOSE: Обычно 80 или 443.
CMD: Запускаем Nginx (по умолчанию он уже настроен на daemon off;).

FROM nginx:alpine
WORKDIR /usr/share/nginx/html

COPY dist/ /usr/share/nginx/html/


COPY [Link] /etc/nginx/conf.d/[Link]

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Почему удобно: Один образ с Nginx можно быстро запускать в любом окружении
(стейдж, прод), всё уже упаковано внутри.

5. Задание ENTRYPOINT и использование аргументов запуска

Сценарий: У вас есть Python или Go-утилита, которая принимает CLI-аргументы


для разных режимов работы. Хотите, чтобы при запуске контейнера просто
дописывали нужные опции (например, --debug).

ENTRYPOINT: Указываем исполняемый файл или интерпретатор (Python, Go)


и сам скрипт.
CMD: Задаём аргументы по умолчанию, чтобы при запуске без аргументов
контейнер работал в обычном режиме.
Пользователь при docker run ... может переопределить только CMD
(добавить свои аргументы).

FROM python:3.9

WORKDIR /app
COPY [Link] /app/[Link]

ENTRYPOINT ["python", "[Link]"]


CMD ["--mode", "default"]

Пример запуска: docker run <image> --mode debug — аргумент --mode debug
будет добавлен к команде python [Link].

6. Использование ENV для конфигурации API

3/4
Сценарий: Ваше приложение нуждается в базе данных или стороннем API, и вы
хотите задать URL и режим работы при сборке, но при запуске на проде
переопределить эти переменные.

ENV: Задаём базовые переменные окружения (например, URL для API).


В docker run можно указать -e KEY=value, чтобы переопределить.

FROM node:16
WORKDIR /app

ENV APP_MODE=production
ENV API_URL=[Link]

COPY [Link] .
RUN npm install

COPY . .
EXPOSE 3000

CMD ["npm", "start"]

Важный момент: ENV в Dockerfile — это дефолтные значения. На проде их легко


заменить командой -e, не пересобирая образ.

Подведём итоги
Выбирайте базовый образ с учётом задач (Node, Python, OpenJDK, Nginx) и
версий языка/технологии.
Устанавливайте нужные пакеты через RUN, чтобы окружение было
консистентным для всех разработчиков и сред.
Копируйте и собирайте проект (npm install, pip install, gradle/mvn package)
ещё на этапе сборки образа.
Используйте ENTRYPOINT/CMD и ENV для гибкости запуска и конфигурации.
EXPOSE полезен, чтобы явно сообщать, какой порт слушает контейнер.

Таким образом вы сможете упаковывать приложения (бэкенд, фронтенд,


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

4/4
3.5 Продвинутые инструкции Dockerfile

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


В прошлом уроке мы разобрались с базовыми инструкциями (FROM, RUN, COPY/ADD,
CMD, ENTRYPOINT, ENV, EXPOSE, WORKDIR) и на их примерах увидели, как собирать
образы для Python, [Link], Java (Spring Boot), статических сайтов и т. д. Теперь
перейдём к инструкциям, которые не были затронуты ранее. Они менее
распространены в базовых сценариях, но часто используются в реальной практике.

Обратите внимание, что все эти инструкции относятся к сборке (build time).
То есть они влияют на образ и на то, как контейнер будет работать.

1. ARG

Сценарий: Вам нужно передать некий параметр только на этапе сборки, который не
должен утекать в контейнер как переменная окружения. Например, версию пакета
или секретный ключ для скачивания зависимостей (который потом стирается).

ARG — объявляет переменную сборки (build-time variable). Её можно


переопределить флагом --build-arg при docker build.
ENV видит эти переменные, если объявлять их после ARG, но сами ARG не
сохраняются в слои для runtime.

FROM python:3.9

ARG MY_PACKAGE_VERSION=1.2.3
RUN echo "Installing my_package version $MY_PACKAGE_VERSION"
RUN pip install my_package==$MY_PACKAGE_VERSION

Запуск сборки:

docker build --build-arg MY_PACKAGE_VERSION=2.0.0 -t my-image .

Зачем это нужно? Часто для более гибкого build-процесса, чтобы не жёстко
прописывать версии прямо в Dockerfile.

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

LABEL — позволяет подписать образ ключами и значениями (аналогично


метаданным). Это удобно для автоматических систем, которые ищут
информацию в образах.

1/4
FROM node:16
LABEL maintainer="prosto@[Link]" \
version="1.0.0" \
description="My [Link] Service"

При просмотре образа (например, docker image inspect <image>) эти поля будут
видны в разделе Labels.

3. USER
Сценарий: По умолчанию многие базовые образы запускаются под root. Но в
продакшене или в средах с повышенными требованиями к безопасности бывает
нужно запускать процессы от непривилегированного пользователя.

USER — позволяет указать пользователя (и группу), от имени которого будет


запускаться CMD или ENTRYPOINT внутри контейнера.
Перед этим нередко вызывают RUN useradd (в Alpine — adduser), чтобы
создать нового пользователя.

FROM alpine:3.17

RUN adduser -D myuser #флаг -D означает не назначать пароль


USER myuser

WORKDIR /home/myuser
COPY app ./

CMD ["./app"]

Теперь приложение внутри контейнера не может навредить, если оно


скомпрометировано, так как не имеет привилегий root.

4. VOLUME
Сценарий: Ваше приложение пишет логи или хранит данные, и вы хотите вынести
их во вне (host machine / Docker volume), чтобы не терять данные при пересоздании
контейнера.

VOLUME — объявляет точку монтирования. При запуске docker run, Docker


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

FROM postgres:15
VOLUME /var/lib/postgresql/data

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


Но на практике обычно используют [Link] для монтирования, а в
Dockerfile просто указывают VOLUME.

5. ONBUILD

2/4
Сценарий: Вы делаете базовый образ, который в будущем будут наследовать
другие Dockerfile с FROM your-image. И хотите, чтобы при следующей сборке (в
дочернем образе) автоматически выполнилась некая команда (например, COPY или
RUN).

ONBUILD — указывает инструкцию, которая сработает только при


использовании этого образа как базового.

FROM node:16
ONBUILD COPY package*.json /app/
ONBUILD RUN npm install

Теперь, если кто-то напишет в другом Dockerfile:

FROM your-node-base:latest
COPY . /app
CMD ["npm", "start"]

То при сборке этого дочернего образа сработают ONBUILD COPY и ONBUILD RUN,
заложенные в родительском образе.

Предупреждение: ONBUILD бывает удобно для шаблонных базовых образов, но в


реальных проектах его использование не самое популярное, так как создаёт
неявную логику.

6. STOPSIGNAL
Сценарий: При остановке контейнера Docker отправляет SIGTERM (по умолчанию)
главному процессу. Если приложение ожидает другой сигнал или вы хотите
изменить поведение — используйте STOPSIGNAL.

FROM ubuntu:22.04

STOPSIGNAL SIGQUIT
CMD ["/usr/bin/my-daemon"]

Теперь, когда вы сделаете docker stop, Docker пошлёт SIGQUIT вместо обычного
SIGTERM. Это полезно, если ваше приложение не понимает стандартный сигнал или
обрабатывает другой.

7. HEALTHCHECK
Сценарий: Ваш контейнер должен сигнализировать Docker, жив ли он. Для этого
можно настроить healthcheck — периодическую проверку состояния (например,
HTTP-запрос к эндпоинту /health).

HEALTHCHECK — указывает команду (обычно curl или другую утилиту),


интервал проверки, таймаут и т. д. Если проверка возвращает код 0 —
контейнер считается healthy, иначе — unhealthy.

3/4
FROM nginx:alpine

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \


CMD curl -f [Link] || exit 1

CMD ["nginx", "-g", "daemon off;"]

Теперь, если [Link] вернёт 200 OK, контейнер будет healthy,


иначе unhealthy. Можно проверить командой docker ps — в столбце STATUS будет
healthy/unhealthy.

8. SHELL
Сценарий: По умолчанию Dockerfile интерпретирует RUN, CMD, ENTRYPOINT как запуск
через sh -c (в Linux-образах) или cmd /S /C (в Windows-образах). Но если вы
хотите использовать другой shell (например, powershell в Windows или bash в
Linux), можно изменить поведение с помощью SHELL.

FROM ubuntu:22.04

SHELL ["/bin/bash", "-c"]

RUN echo "Now we use bash" && set -eux


...

Часто это применяется в Windows-образах, когда хотят работать не через [Link], а


через PowerShell.

Подведём итоги
Теперь вы знаете о более тонких возможностях Dockerfile:

ARG — переменные, актуальные только на этапе сборки.


LABEL — метаданные для образа.
USER — запуск контейнера от конкретного пользователя (не root).
VOLUME — объявление директории, куда можно примонтировать внешний
том.
ONBUILD — заготовки для дочерних образов.
STOPSIGNAL — настройка сигнала при остановке контейнера.
HEALTHCHECK — проверка живости контейнера (healthy/unhealthy).
SHELL — выбор типа шелла для выполнения команд (RUN, CMD, ENTRYPOINT).

4/4
13 апреля 2025 г.

3.6 Практика

Добавляем продвинутые инструкции в базовый [Link]-образ


В этом задании вы создадите Dockerfile на базе образа node:16

Техническое задание

1. Базовый образ — node:16.


2. ARG (ARG APP_VERSION=1.0): вывести значение этой переменной на этапе RUN,
чтобы убедиться, что ARG передано, текст такой "Building version
$APP_VERSION".
3. LABEL: Добавить метаданные (maintainer, version) для описания образа.
4. USER: Создать непривилегированного пользователя (через RUN adduser или
useradd) и переключиться на него. Затем WORKDIR должен указывать на
домашнюю папку нового пользователя (например, /home/nodeuser/app).
5. VOLUME: Объявить папку для данных (например, /home/nodeuser/data),
чтобы при запуске контейнера туда можно было монтировать внешний том.
6. Копировать локальные файлы [Link], [Link] и [Link]
(или другой код) в рабочую папку нового пользователя.
7. RUN npm install — установить зависимости. Примечание: Можно добавить
скрипт запуска: "start": "node [Link]" в [Link], чтобы потом
выполнять npm start.
8. CMD (или ENTRYPOINT) — запустить npm start или node [Link], в
зависимости от того, как вы предпочитаете.

1/3
9. Итог: При запуске контейнера (например, docker run -d -p 3000:3000 my-
advanced-node) ваше [Link]-приложение должно работать, а в образе будут
метаданные LABEL, объявленный VOLUME и непривилегированный
пользователь.

Структура папки

Dockerfile — вы создаёте по требованиям выше.


[Link], [Link] — базовый файл зависимостей:

{
"name": "advanced-node-practice",
"version": "1.0.0",
"scripts": {
"start": "node [Link]"
},
"dependencies": {
"express": "^4.18.2"
}
}

[Link] — простой код на [Link], который слушает, например, на порту


3000:

const express = require("express");


const app = express();

[Link]("/", (req, res) => {


[Link]("Hello from an advanced [Link] Dockerfile!");
});

[Link](3000, () => {
[Link]("Server running on port 3000");
});

Что нужно сдать?

1. Dockerfile с вышеописанными инструкциями.


2. Проверить запуск:

docker build -t my-advanced-node .


docker run -d -p 3000:3000 --name advnode my-advanced-node

Перейдите в браузере на [Link] и убедитесь, что ваше


приложение отвечает «Hello from an advanced [Link] Dockerfile!» (или любой
другой текст).

Подсказка 1 (здесь все нужные инструкции без параметров)

2/3
FROM
ARG
LABEL
RUN
USER
VOLUME
WORKDIR
COPY
CMD

Подсказка 2 (здесь пример Dockerfile, открывать только если совсем затык)

FROM node:16

ARG APP_VERSION=1.0
RUN echo "Building version $APP_VERSION"

LABEL maintainer="you@[Link]" \
version=$APP_VERSION

RUN useradd -m -s /bin/bash nodeuser


USER nodeuser
WORKDIR /home/nodeuser/app

VOLUME /home/nodeuser/data

COPY package*.json ./
RUN npm install

COPY [Link] ./

CMD ["npm", "start"]

Напишите текст

3/3
Задействуем продвинутые инструкции в Dockerfile
В этом задании вы создадите Dockerfile, в котором будут использоваться не только
базовые (FROM, RUN, COPY, CMD, ENV), но и дополнительные инструкции (ARG, LABEL,
USER, VOLUME, ONBUILD, STOPSIGNAL, HEALTHCHECK, SHELL). Мы объединим их в один
образ, чтобы на практике увидеть, как каждая инструкция влияет на процесс сборки
и работу контейнера.

Техническое задание (ТЗ)


1. Базовый образ — python:3.9.
2. ARG: Укажите переменную для сборки ARG BUILD_MODE=dev. Выведите её
значение во время RUN, чтобы видеть, что ARG сработал.
3. LABEL: Добавьте метаданные об образе: version, maintainer.
4. USER: Создайте непривилегированного пользователя (через RUN adduser или
useradd) и переключитесь на него. Файлы приложения должны находиться в
его домашней директории.
5. VOLUME: Объявите папку, куда приложение будет записывать данные
(например, /home/myapp/data).
6. ONBUILD: Укажите хотя бы одну инструкцию (например, ONBUILD COPY .
/home/myapp/extra), которая сработает, если этот образ будет использоваться
как базовый.
7. STOPSIGNAL: Задайте иной сигнал (например, SIGQUIT) для остановки
контейнера.
8. HEALTHCHECK: Настройте проверку «живости» контейнера. Например,
используйте curl или python-скрипт, чтобы «проверять» ответ от приложения
на порту 8080.
9. SHELL: Переключитесь на ["/bin/bash", "-c"], чтобы все RUN команды
выполнялись в bash (это продемонстрирует альтернативный шелл).
10. Скопировать файл [Link] (лежит рядом с Dockerfile) в папку пользователя
/home/myapp. В нём пусть будет простой Flask-приложение (или любой другой
код), которое слушает на порту 8080 и отвечает «Hello from Advanced
Dockerfile!». Используйте ENV или ARG для задания порта, если хотите гибкости.
11. EXPOSE порт 8080 (чтобы указать, что контейнер слушает здесь).
12. CMD или ENTRYPOINT — запуск [Link] (Flask или любое другое приложение).
Убедитесь, что внутри [Link] указано port=8080.
13. Итог: При запуске контейнера (например, -p 8080:8080) вы сможете перейти
по адресу [Link] и получить ответ от вашего приложения
«Hello from Advanced Dockerfile!». В Docker-инспекторе (docker image inspect)
будут видны LABEL. Если вы используете docker ps, то при наличии
HEALTHCHECK статус будет «healthy» или «unhealthy». Остановка контейнера
пошлёт сигнал SIGQUIT, а в образе будет объявлена папка-том для данных.

1/3
Структура папки

Dockerfile — вы создаёте, используя все перечисленные инструкции.


[Link] — простое Python-приложение, например:

from flask import Flask


import os

app = Flask(__name__)

@[Link]("/")
def index():
return "Hello from Advanced Dockerfile!"

if __name__ == "__main__":
port = int([Link]("PORT", "8080"))
[Link](host="[Link]", port=port)

Что нужно сдать?


1. Dockerfile, соответствующий всем пунктам ТЗ.
2. Проверить запуск:

docker build -t advanced-dockerfile .


docker run -d -p 8080:8080 --name advtest advanced-dockerfile

Зайдите на [Link] и убедитесь, что приложение отвечает


«Hello from Advanced Dockerfile!».
3. Посмотрите логи или docker ps, чтобы увидеть статус (healthy) (при
корректном HEALTHCHECK), а также попробуйте остановить контейнер и
проверьте, что посылается сигнал SIGQUIT (можно посмотреть в логах
приложения, если оно обрабатывает такой сигнал).

Подсказка 1 (здесь все нужные инструкции без параметров)

FROM
ARG
LABEL
RUN
USER
VOLUME
ONBUILD
STOPSIGNAL
HEALTHCHECK
SHELL
COPY
ENV (или ARG)
EXPOSE
CMD (или ENTRYPOINT)

Подсказка 2 (здесь полноценный Dockerfile, открывать только если совсем затык)

2/3
FROM python:3.9

# Переключаемся на bash
SHELL ["/bin/bash", "-c"]

# Объявляем build-time переменную


ARG BUILD_MODE=dev
RUN echo "Building with mode: $BUILD_MODE"

# Добавляем метаданные
LABEL maintainer="you@[Link]" \
version="1.0" \
description="Advanced Dockerfile Example"

# Создаём пользователя и директории


RUN useradd -m -s /bin/bash myapp
USER myapp
WORKDIR /home/myapp

# Объявляем volume
VOLUME /home/myapp/data

# ONBUILD инструкция
ONBUILD COPY . /home/myapp/extra

# STOPSIGNAL
STOPSIGNAL SIGQUIT

# HEALTHCHECK (каждые 30с, таймаут 5с, макс 3 попытки)


HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f [Link] || exit 1

# Копируем приложение
COPY [Link] /home/myapp/

# Устанавливаем зависимости
RUN pip install flask

# EXPOSE
EXPOSE 8080

# Команда (Python-приложение на порту 8080)


CMD ["python", "[Link]"]

3/3
2 апреля 2025 г.

3.6 Практика

Nginx-приложение с собственной командной оболочкой,


HealthCheck и StopSignal
В этом задании вы создадите Dockerfile для мини-сервера на Nginx, где
продемонстрируете использование продвинутых инструкций: SHELL, STOPSIGNAL и
HEALTHCHECK. Также добавим небольшой HTML-файл для проверки статической
отдачи.

Зачем?

SHELL: По умолчанию в nginx:alpine используется оболочка sh. Мы хотим


переключиться на bash или другую желаемую оболочку (хотя в Alpine может
потребоваться установка пакета bash).
STOPSIGNAL: Docker обычно посылает SIGTERM при остановке контейнера.
Мы хотим отправлять SIGQUIT или SIGUSR1, чтобы Nginx делал graceful
shutdown по-особому.
HEALTHCHECK: Проверка, что Nginx действительно работает — например,
периодически заходить на [Link] Если страница вернёт
200, то всё хорошо.

Техническое задание (ТЗ)

1. Базовый образ — nginx:alpine (или nginx:latest, если хотите).


2. SHELL: Переключитесь на ["/bin/bash", "-c"] (для Alpine обычно нужно
сначала RUN apk add --no-cache bash).
3. STOPSIGNAL: Укажите SIGQUIT, чтобы при остановке контейнера Docker
послал не стандартный SIGTERM, а иной сигнал.
4. HEALTHCHECK: Каждые 30s отправлять запрос curl -f
[Link] (или к другому эндпоинту, который вы пропишете в
Nginx). Если получаем код 200, «healthy», иначе «unhealthy».
5. COPY: Копируйте локальный [Link] в /usr/share/nginx/html. Создайте
там файл [Link] — при обращении к /health будет отдаваться (если вы
в конфиге так настроите). Либо можно оставить /health = /[Link].
6. CMD (или оставьте дефолтный Nginx) — можете ничего не менять, ведь
nginx:alpine уже знает, как запускаться, но при желании можно прописать CMD
["nginx", "-g", "daemon off;"].
7. Итог: При запуске контейнера (например, -p 8080:80) зайдите на
[Link] и убедитесь, что отдан ваш [Link]. Контейнер
будет получать SIGQUIT при остановке. Проверка docker ps покажет, что у
контейнера есть статус healthcheck (healthy/unhealthy).

Структура папки

1/3
Dockerfile — вы создаёте по требованиям выше.
[Link] — простой HTML:

<!DOCTYPE html>
<html>
<head><title>Nginx Advanced Demo</title></head>
<body>
<h1>Hello from Nginx with custom SHELL & StopSignal!</h1>
</body>
</html>

Что нужно сдать?


1. Dockerfile, корректно реализующий все пункты ТЗ.
2. Проверить запуск:

docker build -t nginx-advanced .


docker run -d -p 8080:80 --name advnginx nginx-advanced

Откройте [Link] в браузере, чтобы увидеть ваш [Link].


3. Посмотрите статус в docker ps — при корректном HealthCheck будет
отображаться (healthy) или (unhealthy). При остановке контейнера (docker
stop advnginx) Docker пошлёт SIGQUIT вместо SIGTERM.

Подсказка 1 (здесь все нужные инструкции без параметров)

FROM
RUN
SHELL
STOPSIGNAL
HEALTHCHECK
COPY
CMD

Подсказка 2 (пример Dockerfile, если совсем затык)

FROM nginx:alpine

# Установим bash (по желанию) и переключимся на него


RUN apk add --no-cache bash
SHELL ["/bin/bash", "-c"]

# Задаём StopSignal
STOPSIGNAL SIGQUIT

# Healthcheck: каждые 30s пробуем зайти на /health, код 0 => healthy


HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f [Link] || exit 1

# Копируем [Link] (и, если хотите, [Link])


COPY [Link] /usr/share/nginx/html/

# По умолчанию nginx:alpine уже имеет CMD, но если хотим - переопределяем


# CMD ["nginx", "-g", "daemon off;"]

2/3
3/3
6 марта 2025 г.

3.6 Практика

Используем ARG и USER в Alpine для обработки текстовых


файлов
Предположим, вы пишете небольшой инструмент, который считывает текстовые
файлы из /data и обрабатывает их скриптом [Link]. Вы хотите иметь гибкость в
выборе пакета для обработки текста, а также запускать контейнер от лица обычного
пользователя, а не root.

Техническое задание (ТЗ)

1. Базовый образ — alpine:3.17.


2. ARG ARG TEXT_TOOL=grep: Использовать эту переменную, чтобы установить
нужный пакет. По умолчанию TEXT_TOOL=grep, но можно заменить на sed, awk и
т. д.
Например, если TEXT_TOOL=grep, то в контейнере устанавливаем grep.
Если TEXT_TOOL=sed, то ставим sed.
Используйте RUN apk add --no-cache $TEXT_TOOL или любую другую логику.

3. Создать пользователя myuser, который не имеет прав root.


4. USER myuser: переключиться на этого пользователя.
5. Обозначить рабочую директорию /home/myuser/app (соответственно, папка
должна принадлежать myuser).
6. Скопировать файл [Link] (и сделать его исполняемым) в папку
приложения. Внутри [Link] вы можете обращаться к переменной
TEXT_TOOL (как $TEXT_TOOL) или просто запускать установленную утилиту.
В качестве примера: ./[Link] /data/[Link] ищет, например,
строки, содержащие «ERROR» (через grep), или модифицирует текст
(через sed).
7. VOLUME /data: объявите директорию, откуда будет читаться входной файл.
При запуске контейнера вы сможете подмонтировать локальную папку в /data.
8. CMD — пусть запускает ["/home/myuser/app/[Link]",
"/data/[Link]"]. В результате при запуске контейнера без аргументов
будет обрабатываться файл /data/[Link].
9. Итог: При сборке можно указывать пакет (grep, sed и т. д.) флагом --build-arg
TEXT_TOOL=sed. При запуске контейнера (например, с монтированием
локальной папки /local_data в /data), ваш скрипт [Link] под
пользователем myuser обрабатывает файл /data/[Link] выбранным
инструментом.

Структура папки

Dockerfile

1/3
[Link] (простой скрипт, что-то вроде):

#!/bin/sh
echo "Running with $TEXT_TOOL on $1"

# Если TEXT_TOOL=grep, можно искать ERROR:


if [ -z "$TEXT_TOOL" ]; then
echo "No TEXT_TOOL set, defaulting to grep"
TEXT_TOOL="grep"
fi

$TEXT_TOOL "ERROR" $1

(Вы можете придумать более креативную логику — главное, чтобы


показывало, что TEXT_TOOL влияет на результат.)

Что нужно сдать?


1. Dockerfile, удовлетворя требованиям ТЗ.
2. Проверить сборку с разными TEXT_TOOL:

docker build --build-arg TEXT_TOOL=grep -t my-text-processor .


docker build --build-arg TEXT_TOOL=sed -t my-text-processor-sed .

3. Проверить запуск, смонтировав папку:

docker run --rm -v /local_data:/data my-text-processor

На экране увидите результат обработки файла /local_data/[Link].

Подсказка 1 (здесь все нужные инструкции без параметров)

FROM
ARG
RUN
USER
WORKDIR
COPY
VOLUME
CMD

Подсказка 2 (пример Dockerfile, если совсем затык)

2/3
FROM alpine:3.17

ARG TEXT_TOOL=grep
RUN apk add --no-cache $TEXT_TOOL

RUN adduser -D myuser


USER myuser

WORKDIR /home/myuser/app
COPY [Link] .
RUN chmod +x [Link]

VOLUME /data

CMD ["./[Link]", "/data/[Link]"]

3/3
3.6 Практика

Java + ONBUILD для базового шаблона Spring Boot


В этом задании вы создаёте Dockerfile, который выступает как базовый образ для
Java-приложений на Spring Boot. Суть в том, что вы задаёте несколько ONBUILD-
инструкций, а при использовании этого образа в другом дочернем Dockerfile они
автоматически срабатывают. Это полезно, если вы хотите упростить сборку Spring
Boot проектов для вашей команды.

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

ONBUILD — инструкции, которые не выполняются при создании родительского


образа, но сработают в том Dockerfile, который напишет FROM my-java-
base:latest.
LABEL — метаданные о вашем базовом образе (контакт, версия и т. д.).
ENV — можно задать дефолтный порт или переменные окружения, которые
ваши Spring Boot-приложения будут использовать.

Техническое задание (ТЗ)

1. Базовый образ — openjdk:17-jdk (или eclipse-temurin:17, если


предпочитаете «официальный» Temurin).
2. LABEL: Добавьте метаданные, например, maintainer, version, description.
3. ENV: Задайте SPRING_PROFILES_ACTIVE=default или SERVER_PORT=8080, чтобы
указать типичный профиль и порт для Spring Boot.
4. Добавьте строку, которая в дочернем Dockerfile скопирует JAR-
файл — target/*.jar /app/[Link].
5. Объявите порт 8080, который будут слушать все дочерние образы.
6. CMD: Запуск java -jar /app/[Link], но учтите, что сама JAR-ка появится
только в дочернем образе.
7. Итог: При сборке этого Dockerfile ONBUILD-инструкции не выполняются. Но
когда кто-то напишет:

FROM my-java-base:latest
COPY . .
RUN mvn clean package
CMD ["java", "-jar", "/app/[Link]"]

Тогда сработают ONBUILD COPY, ONBUILD EXPOSE и т. д., заложенные в базовом


образе.

Структура базового образа

Dockerfile — родительский.

1/2
Дополнительно ничего не нужно. Вы просто собираете этот образ командой:

docker build -t my-java-base .

А при использовании пишете в другом Dockerfile:

FROM my-java-base:latest
# Ваши шаги: например, COPY кода, mvn package, CMD и т. д.

Что нужно сдать?


1. Dockerfile, удовлетворяющий всем пунктам ТЗ.
2. Проверить сборку базового образа:

docker build -t my-java-base .

3. Создать дочерний Dockerfile, который от него наследуется, и проверить, что


ONBUILD-инструкции действительно срабатывают, когда вы пишете FROM my-
java-base.

Подсказка 1 (здесь все нужные инструкции без параметров)

FROM
LABEL
ENV
ONBUILD
CMD

Подсказка 2 (пример базового Dockerfile, если совсем затык)

FROM openjdk:17-jdk

LABEL maintainer="you@[Link]" \
version="1.0" \
description="Base Spring Boot Dockerfile with ONBUILD"

ENV SPRING_PROFILES_ACTIVE=default
ENV SERVER_PORT=8080

ONBUILD COPY target/*.jar /app/[Link]


ONBUILD EXPOSE 8080

CMD ["java", "-jar", "/app/[Link]"]

2/2
Создаём Python-приложение с HEALTHCHECK, USER и
VOLUME
В этом задании вы настроите Dockerfile для Python-приложения (Flask / FastAPI /
любой другой фреймворк), где продемонстрируете использование нескольких
продвинутых инструкций:

HEALTHCHECK — чтобы каждые N секунд проверять эндпоинт вашего


приложения, определяя его статус как healthy или unhealthy.
USER — переключиться на непривилегированного пользователя для
безопасности.
VOLUME — объявить директорию, в которую приложение пишет логи или файлы
(например, /data/logs), чтобы при запуске контейнера туда можно было
подключать том.

Сценарий

Допустим, у вас есть Flask-приложение, которое:

1. Отвечает на порту 5000.


2. Имеет эндпоинт /health для проверки живости.
3. Пишет логи в папку /data/logs.

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


healthy, если эндпоинт /health возвращает 200 OK, а при желании подключать том
для логов.

Техническое задание (ТЗ)

1. Базовый образ — python:3.9 или любая другая версия Python.


2. Создайте непривилегированного пользователя webuser, чтобы приложение
работало не от root.
3. Переключитесь на этого пользователя (после установки зависимостей, чтобы
не требовались root-права).
4. HEALTHCHECK:
Например, каждые 30s отправляем curl -f
[Link]
Если curl вернёт код 0 (HTTP 2xx), значит контейнер «healthy». Иначе
«unhealthy».
5. Объявите том /data/logs, куда Flask-приложение пишет логи.
6. Копируйте файлы [Link] и [Link] в контейнер и устанавливайте
Flask или FastAPI (на ваше усмотрение).
7. EXPOSE порт 5000.
8. CMD — запустите ваше Python-приложение["python", "[Link]"].

1/3
9. Итог:
При запуске контейнера (например, -p 5000:5000), открываете
[Link] — видите вашу главную страницу, а
[Link] возвращает 200 OK для HealthCheck.
docker ps показывает статус healthy или unhealthy.
В /data/logs ваши логи живут независимо от пересоздания контейнера,
если вы смонтируете том.

Структура папки

Dockerfile — со всеми перечисленными инструкциями.


[Link] — c Flask (или FastAPI), если нужно. Пример:

Flask==2.2.3

[Link] — простое приложение:

from flask import Flask


import os

app = Flask(__name__)

@[Link]("/")
def index():
return "Hello from a health-checked Flask!"

@[Link]("/health")
def health():
return "OK", 200

if __name__ == "__main__":
# Логи можно писать в /data/logs, если хотите
port = 5000
[Link](host="[Link]", port=port)

Что нужно сдать?

1. Dockerfile, корректно реализующий все пункты ТЗ (HEALTHCHECK, USER,


VOLUME и т.д.).
2. Проверить запуск:

docker build -t flask-health .


docker run -d -p 5000:5000 --name fh -v $(pwd)/logs:/data/logs flask-health

Потом docker ps — через некоторое время статус будет healthy, если


эндпоинт /health отвечает 200 OK.

Подсказка 1 (здесь все нужные инструкции без параметров)

2/3
FROM
RUN
USER
HEALTHCHECK
VOLUME
COPY
EXPOSE
CMD

Подсказка 2 (пример Dockerfile, открывать только если совсем затык)

FROM python:3.9

RUN apt-get update && apt-get install -y curl

RUN useradd -m webuser

WORKDIR /home/webuser/app
COPY [Link] .
RUN pip install --no-cache-dir -r [Link]

COPY [Link] .
EXPOSE 5000

# HEALTHCHECK: каждые 30 секунд, таймаут 5 секунд, 3 попытки


HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f [Link] || exit 1

VOLUME /data/logs

USER webuser

CMD ["python", "[Link]"]

3/3
27 марта 2025 г.

Rust CLI с продвинутыми инструкциями (SHELL, ARG,


STOPSIGNAL)
В этом задании вы создадите Dockerfile для небольшого CLI-приложения на Rust.
Задача — показать, как использовать SHELL, ARG, STOPSIGNAL и LABEL в одном
образе.

Сценарий
Допустим, у вас есть Rust-приложение (файл [Link]), которое при запуске выводит
информацию и обрабатывает входящие аргументы. При остановке контейнера вы
хотите отправлять не стандартный сигнал SIGTERM, а SIGINT, чтобы Rust-
приложение корректно завершалось.

Также вы хотите иметь ARG BUILD_MODE (например, debug или release), чтобы
определять, как компилировать приложение. Плюс вы любите пользоваться bash
вместо sh, поэтому настройте это через SHELL.

Техническое задание (ТЗ)

1. Базовый образ — rust:1.68 (или любая актуальная версия Rust).


2. ARG BUILD_MODE=debug:
Если BUILD_MODE=release, то компилируем cargo build --release.
Иначе компилируем cargo build (debug).
Добавьте RUN, который «выбирает» режим в зависимости от BUILD_MODE.

3. SHELL: Переключитесь на ["/bin/bash", "-c"], если хотите использовать


команды bash (например, if/else логика в RUN).
4. Укажите SIGINT в качестве сигнала остановки.
5. LABEL: Укажите maintainer и version, чтобы в docker image inspect было
понятно, кто отвечает за образ.
6. COPY [Link], [Link], [Link] (в папку /app) и компилируйте код. В
итоге получите бинарник, скажем, /app/target/release/mycli или
/app/target/debug/mycli.
7. CMD — пусть указывает на итоговый бинарник. Например,
["/app/target/debug/mycli"] (если BUILD_MODE=debug) или
["/app/target/release/mycli"] (если BUILD_MODE=release). Можно
прописать это в RUN логике или через ENV. Или просто выбрать одно из двух на
этапе сборки.

1/3
8. Итог:
При запуске контейнера (например, docker run --rm my-rust-cli) ваш
CLI-приложение будет работать. Если вы сделаете docker stop, Docker
пошлёт SIGINT, и приложение может корректно завершиться.
Если захотите собрать в релизном режиме:

docker build --build-arg BUILD_MODE=release -t my-rust-cli .

Тогда получатся оптимизированные бинарники (Cargo при release сам


положит их в target/release).

Структура папки

Dockerfile — со всеми указанными инструкциями.


[Link], [Link] (опционально). Пример:

[package]
name = "mycli"
version = "0.1.0"
edition = "2021"

[dependencies]

[Link] — небольшой CLI-код, например:

fn main() {
println!("Hello from Rust CLI!");
let args: Vec<String> = std::env::args().collect();
if [Link]() > 1 {
println!("Got arguments: {:?}", &args[1..]);
}
// Here you might simulate a loop or do some work until SIGINT is
received
}

Что нужно сдать?


1. Dockerfile, удовлетворя всем пунктам ТЗ (ARG, SHELL, STOPSIGNAL, LABEL,
и сборка Rust-проекта).
2. Проверить сборку:

docker build --build-arg BUILD_MODE=release -t my-rust-cli .

3. Проверить запуск:

docker run --rm my-rust-cli arg1 arg2

Посмотреть, выводит ли программа список аргументов. При docker stop


убедиться, что посылается SIGINT, а не SIGTERM (можно отследить по логам,
если вы обрабатываете сигнал в Rust).

Подсказка 1 (здесь все нужные инструкции без параметров)

2/3
FROM
SHELL
ARG
RUN
COPY
LABEL
STOPSIGNAL
CMD

Подсказка 2 (пример Dockerfile, если совсем затык)

FROM rust:1.68

LABEL maintainer="you@[Link]" version="1.0"

# Переключаемся на bash
SHELL ["/bin/bash", "-c"]

ARG BUILD_MODE=debug
STOPSIGNAL SIGINT

WORKDIR /app
COPY [Link] [Link] ./
COPY [Link] src/

RUN if [ "$BUILD_MODE" = "release" ]; then \


cargo build --release ; \
else \
cargo build ; \
fi

# Выбираем CMD
# Если релиз, у нас /app/target/release/mycli
# Иначе debug
CMD if [ "$BUILD_MODE" = "release" ]; then \
/app/target/release/mycli ; \
else \
/app/target/debug/mycli ; \
fi

3/3
3.7 Best Practices

В Dockerfile каждая инструкция (FROM, RUN, COPY и т. д.) порождает новый слой.
Docker кэширует эти слои, чтобы не пересобирать всё при каждом изменении
файлов. Правильное расположение (упорядочение) инструкций может существенно
ускорить сборку и уменьшить итоговый размер образа. Ниже — основные приёмы и
советы.

1. Механизм кэширования в Docker


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

Если вы меняете одну из верхних инструкций (или её результат), все


инструкции ниже придётся пересобирать заново.
Правильное упорядочение команд может свести к минимуму пересборку.

2. «Самое редко меняющееся» — выше

Классический пример: [Link]-приложение. Обычно [Link] меняется реже,


чем остальной код, а установка зависимостей (npm install) обычно тяжёлая. Значит:

# Плохая практика:
COPY . /app
RUN npm install

Здесь, если вы меняете любой файл в проекте, при сборке Docker увидит, что
инструкция COPY . /app изменилась, и заново запустит RUN npm install. Это
теряет кэш.

# Хорошая практика:
COPY package*.json /app
RUN npm install
COPY . /app

Теперь, если вы поправили [Link] (но не трогали [Link]), npm install


возьмётся из кэша.
Сборка будет быстрее и экономней.

3. Группировать команды RUN

1/5
Каждая RUN создаёт слой. Чем больше слоёв, тем больше размер образа (иногда
незначительно, но всё же). Часто объединяют несколько команд в один RUN:

# Вместо:
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*

# Делают так:
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

Теперь это всё — один слой, и кэш Docker проверяет результат целиком.
Если что-то поменялось в этих командах, пересобирается только один слой, а
не несколько.

4. Порядок: FROM → ENV → RUN (установка) → COPY (минимальный)


→ RUN (сборка) → COPY (оставшееся) → CMD
Типичная структура Dockerfile (для Node/Go/Java) часто выглядит так:

FROM node:14-alpine

WORKDIR /app

# (1) копируем только [Link] (редко меняется)


COPY package*.json ./
RUN npm install

# (2) теперь копируем остальное (часто меняется)


COPY . .

# (3) настройки
ENV NODE_ENV=production

# (4) порт и команда


EXPOSE 3000
CMD ["npm", "start"]

Таким образом, если вы поменяли лишь 1 файл кода, инструкции до COPY . .


останутся в кэше.
Если вы часто меняете [Link], то, увы, npm install будет пересобираться;
но если не трогаете, то нет.

5. Минимизировать «мусор» (clean-up)


В Linux-системах, когда вы что-то скачиваете (apt-get update, apk add и т.д.),
возникают кеши, временные файлы. Стоит их удалять в той же команде RUN, чтобы
они не оставались в слое:

RUN apt-get update && apt-get install -y curl && \


rm -rf /var/lib/apt/lists/*

2/5
Если вы сделаете rm -rf в отдельном RUN, то «мусор» уже запечатается в
предыдущем слое и останется в образе.

6. Multi-stage builds
Ещё один приём: multi-stage build. Вы можете сначала собрать (скомпилировать,
сделать npm install) в одном этапе, а потом скопировать результат в финальный
образ. Это помогает убрать компиляторы и лишние зависимости.

# Пример (Go), упрощённо


FROM golang:1.18-alpine AS build
WORKDIR /app
COPY . .
RUN go build -o myapp

FROM alpine:3.17
COPY --from=build /app/myapp /usr/local/bin/myapp
CMD ["myapp"]

В итоге финальный образ очень лёгкий, ведь не содержит golang toolchain и


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

7. Используйте .dockerignore

Если в корне проекта лежит большой объём ненужных для сборки файлов (логов,
документации, temp-файлов) — их желательно исключить из контекста сборки. Для
этого создайте файл .dockerignore и перечислите там всё, что не нужно копировать
в образ:

# .dockerignore
.git
node_modules
logs
*.log
*.tmp

Благодаря этому сокращается время копирования, размер контекста сборки и


итогового образа. Также это уменьшает риск случайно утянуть конфиденциальные
файлы в образ.

8. Выбирайте минимальные базовые образы


Одно из простейших улучшений — использовать лёгкие базовые образы (например,
alpine вместо полноценных дистрибутивов). Это снижает финальный размер образа
и улучшает безопасность (меньше потенциальных уязвимостей в пакетах).

3/5
# Вместо:
FROM ubuntu:latest

# Можно:
FROM alpine:3.17

Но нужно помнить, что в Alpine может отсутствовать часть утилит по умолчанию, а


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

9. Используйте пользователя non-root


Чтобы повысить безопасность, избегайте работы под root внутри контейнера.
Создавайте и используйте непривилегированного пользователя (как было описано в
предыдущих уроках):

FROM node:16-alpine

RUN addgroup -S appgroup && adduser -S appuser -G appgroup


USER appuser

WORKDIR /app
COPY . .

CMD ["npm", "start"]

Таким образом вы снижаете риски, если кто-то получит доступ к контейнеру или
найдёт уязвимость в приложении.

10. Не храните секреты в Dockerfile


Никогда не прописывайте пароли, ключи и токены напрямую в Dockerfile или в ENV.
Этот файл попадает в систему контроля версий, а образы — в реестры.
Используйте переменные среды и секреты Docker (или Kubernetes Secrets, если вы
используете Kubernetes) либо механизмы CI/CD для безопасного хранения
чувствительных данных.

# Нельзя делать так:


ENV DB_PASSWORD="supersecretpassword"

# Лучше передавать переменные окружения при запуске контейнера:


# docker run -e DB_PASSWORD=supersecretpassword your_image

11. Закрепляйте версии зависимостей

Чтобы сборка была воспроизводимой, желательно фиксировать версии (pinning)


пакетов и базовых образов. Например, вместо FROM node:alpine указывать FROM
node:16-alpine или даже конкретный патч-уровень. То же самое касается apt-get
install или npm install — используйте зафиксированные версии или lock-файлы
([Link], [Link] и т. д.).

4/5
Итог
Правильная структура Dockerfile и набор бестпрактис позволяет максимально
использовать кэш Docker, избегать лишних слоёв и временных файлов, защищать
образ от уязвимостей и сохранять секреты. В результате вы получаете более
быстрые сборки, меньшие по размеру образы и повышенную безопасность. Эти
приёмы особенно важны в CI/CD, где контейнеры пересобираются часто.

5/5
24 марта 2025 г.

3.8 Практика

Исправляем Dockerfile с ошибками (Python-версия)


Ниже приведен сломанный Dockerfile для Python-приложения ([Link]). В нем
встречаются опечатки, неверный порядок инструкций и другие проблемы. Ваша
задача — переписать Dockerfile так, чтобы он стал рабочим и соответствовал best
practices.

Сломанный Dockerfile

FROM python:3.9-sli

ENV HELLO_MSG "Hello from Docker"

RUN python install -r [Link]

WORKDR /app

CMD python [Link]

COPY . /ap

EXPOSE 80

1. Найдите и исправьте все ошибки и опечатки (базовый образ, команды,


рабочую директорию и т. д.).
2. Упорядочьте инструкции так, чтобы Dockerfile был логичным: скопировать
файлы, установить зависимости, задать переменные окружения, только затем
выставлять порт и запускать.

Контекст

В директории с Dockerfile также лежат [Link] (список


зависимостей) и [Link].
[Link] запускается через python [Link] и может слушать 80 или 5000 порт.

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


Используйте эти файлы, чтобы протестировать ваш Dockerfile после исправления.

[Link]

1/2
from flask import Flask
import os

app = Flask(__name__)

@[Link]("/")
def index():
# Считываем значение переменной окружения, заданной в Dockerfile
message = [Link]("HELLO_MSG", "No message found")
return f"App says: {message}"

if __name__ == "__main__":
# По умолчанию Flask слушает порт 5000
# Если хотите слушать 80, измените порт либо используйте gunicorn
[Link](host="[Link]", port=5000)

[Link]

Flask==2.2.3

Что сдаем

Окончательный Dockerfile с исправленными инструкциями.


При желании — краткое объяснение, какие были ошибки и как вы их
устранили.

Подсказка (не раскрывайте, пока не попытаетесь)

FROM python:3.9-slim
WORKDIR /app
COPY [Link] .
RUN pip install --no-cache-dir -r [Link]
COPY . .
ENV HELLO_MSG=Hello_from_Docker
EXPOSE 80
CMD ["python","[Link]"]

2/2
9 апреля 2025 г.

3.8 Практика

Исправляем Dockerfile с ошибками (Node-версия)


Ниже приведен сломанный Dockerfile для некоего Node-приложения ([Link]). В нем
есть опечатки, неверные инструкции и неправильный порядок. Ваша задача —
переписать (исправить) его так, чтобы получился рабочий Dockerfile.

Сломанный Dockerfile

FROM node:14-silm

EENV NODE_ENV production

WORKDR /src

COPY . /src

RUN npm install --save

EXPOSE 3000

CMD ["npm","[Link]"]

Обратите внимание на название базового образа, опечатку в инструкциях,


некорректно заданную переменную окружения и другие несоответствия.

1. Найдите и исправьте все ошибки и опечатки.


2. Упорядочьте команды так, чтобы Dockerfile следовал best practices:
скопировать нужные файлы (или только [Link]), установить
зависимости, а затем копировать исходники.

Контекст

В каталоге вместе с Dockerfile лежат [Link] (зависимости Node), [Link]


и прочие файлы проекта.
Приложение запускается через node [Link] или npm start, слушая порт 3000.

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

Ниже — упрощенное Node-приложение на основе Express, а также пример


[Link]. Скопируйте эти файлы, положите рядом с Dockerfile и используйте их
для проверки после исправления.

[Link]

1/2
const express = require("express");
const app = express();

// Считываем переменную окружения, заданную в Dockerfile


// Можно также использовать PORT из переменной окружения Docker
const port = [Link] || 3000;
const message = [Link].NODE_MSG || "Hello from Docker!";

[Link]("/", (req, res) => {


[Link](message);
});

[Link](port, () => {
[Link]("App is running on port " + port);
});

[Link]

{
"name": "node-docker-demo",
"version": "1.0.0",
"description": "A simple [Link] app for Docker demo",
"main": "[Link]",
"scripts": {
"start": "node [Link]"
},
"dependencies": {
"express": "^4.18.0"
}
}

Что сдаем

Итоговый рабочий Dockerfile


При желании — краткое пояснение, в чем были ошибки и как вы их устранили

Подсказка (раскрывайте только при затруднении)

FROM node:14-slim
ENV NODE_ENV=production
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["node","[Link]"]

2/2
3.8 Практика

Исправляем Dockerfile с ошибками (Go-версия)


Ниже приведен Dockerfile для простого Go-приложения, который должен собирать
исполняемый файл и запускать его в контейнере. Однако в нем есть несколько
ошибок, неверные инструкции и неправильный порядок. Исправьте их, чтобы
Dockerfile стал рабочим.

Сломанный Dockerfile

FROM golang:1.19-lin

ENV APP_HOME /go/src/


ENV PORT=8080

RUN cd /app
COPY [Link] /go/src/

RUN go get ./...

EXPOSE 80

CMD ["go", "run", "[Link]"]

1. Найдите и исправьте все ошибки: используйте корректный базовый образ,


верно копируйте файлы, настройте рабочую директорию и т. д.
2. Упорядочьте команды: задайте нужные ENV-переменные, скопируйте файлы,
установите зависимости, выставьте нужный порт и определите правильный
CMD.

Контекст

В одной папке с Dockerfile лежат [Link] и при необходимости другие файлы


проекта.
Приложение должно запускаться командой go run [Link] или вы можете
скомпилировать бинарник и вызвать его напрямую.
Предположим, что приложение слушает порт 8080, и вы хотите пробросить его
наружу.

Пример кода приложения ([Link])

Ниже — упрощенный пример Go-приложения, которое отвечает на запросы на 8080


порту. Можете положить этот файл рядом с Dockerfile и использовать для
тестирования после исправления.

1/2
package main

import (
"fmt"
"log"
"net/http"
"os"
)

func handler(w [Link], r *[Link]) {


[Link](w, "Hello from Go Docker container!")
}

func main() {
port := [Link]("PORT")
if port == "" {
port = "8080"
}

[Link]("/", handler)
[Link]("Starting server on port %s...", port)
[Link]([Link](":"+port, nil))
}

Что сдаем

Исправленный Dockerfile без комментариев — только рабочие инструкции.


Опционально: объяснение, в чем были ошибки и как вы их устранили.

Подсказка (раскрывайте только при необходимости)

# FROM golang:1.19-alpine
# ENV PORT=8080
# WORKDIR /app
# COPY [Link] [Link] ./
# RUN go mod download
# COPY . .
# EXPOSE 8080
# CMD ["go", "run", "[Link]"]

2/2
3.8 Практика

Собираем фронтенд-приложение на React и упаковываем в


Nginx (multi-stage build)
Ниже приведён Dockerfile, который должен выполнять сборку React-приложения
(через npm run build) и затем отдавать готовые статические файлы через
nginx:alpine. Однако в нём есть ошибки, неправильный порядок инструкций и
некорректные пути. Исправьте его, чтобы получить рабочий Dockerfile с двумя
стадиями — сборки и продакшн-развёртывания.

Сломанный Dockerfile

FROM node:16-buil AS bulid-stage

WORKDIR /usr/src/app
COPY . .

RUN npm install

RUN npm rum build

FROM nginx:alpine
COPY dist/ /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

1. Найдите опечатки и исправьте их (название образа, RUN команды, пути к


папкам и т. д.).
2. Упорядочьте инструкции, чтобы на первом этапе (build-stage) устанавливали
только нужные зависимости, а потом собирали проект.
3. Сделайте так, чтобы итоговые статические файлы из сборки (build или dist, в
зависимости от настроек React-приложения) копировались на второй этап
(nginx).
4. Предоставьте итоговый рабочий Dockerfile, без комментариев — только
исправленные инструкции для двух стадий.

Контекст

У вас есть простое React-приложение, где в [Link] определён скрипт


"build", создающий папку build со статическими файлами.
Папка node_modules не нужна на финальном (продакшн) этапе, так как всё уже
собрано.
Чтобы убедиться, что всё работает, соберите образ и запустите контейнер,
затем откройте [Link] (при пробросе порта 80 на хост).

Пример кода приложения

1/2
Для тестирования можете взять базовый React-проект, созданный командой npx
create-react-app my-app, а затем положить исходники в директорию с Dockerfile.

Что сдаем

Исправленный Dockerfile, работающий по многоступенчатой сборке.


При желании — краткий обзор, что именно было исправлено и почему.

Подсказка (раскрывайте только при затруднении)

# Первый этап:
# FROM node:16 as build-stage
# WORKDIR /usr/src/app
# COPY package*.json ./
# RUN npm install
# COPY . .
# RUN npm run build

# Второй этап:
# FROM nginx:alpine
# COPY --from=build-stage /usr/src/app/build /usr/share/nginx/html
# EXPOSE 80
# CMD ["nginx","-g","daemon off;"]

2/2
3.9 Multi-stage

В традиционном Dockerfile мы прописываем все шаги — от компиляции или


установки зависимостей до финального запуска приложения. Проблема в том, что
все инструменты сборки (компиляторы, dev-зависимости, кэш) оказываются в
готовом образе, раздувая его. Multi-stage builds позволяют разделить этап сборки
и этап исполнения (runtime) на разные стадии Dockerfile. В итоге финальный
образ содержит только то, что нужно для запуска.

Как это работает?

1. В Dockerfile мы пишем несколько FROM (несколько стадий).


Первая стадия (builder stage) содержит все инструменты (компиляторы,
зависимости), чтобы собрать / скомпилировать ваше приложение.
Вторая (и последующие) стадии берут результат (бинарник,
скомпилированный jar) из первой, но не тащат весь компилятор и
прочие зависимости.
2. В финальном легковесном образе остаётся лишь необходимое для запуска
(runtime). Никаких компиляторов, исходников, кэша.

Пример: Go

# СТАДИЯ 1: сборка
FROM golang:1.18-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp

# СТАДИЯ 2: минимальный runtime


FROM alpine:3.17
COPY --from=builder /app/myapp /usr/local/bin/myapp
CMD ["myapp"]

В FROM golang:1.18-alpine есть всё необходимое для компиляции Go-


приложения: go build.
После go build -o myapp у нас получается бинарник myapp.
Во втором FROM alpine:3.17 у нас чистая среда (без go-toolchain).
COPY --from=builder /app/myapp копирует бинарник из builder стадии.
Финальный образ очень маленький, ведь в нём нет компиляторов.

1/7
Более продвинутый пример Go: использование scratch и статическая
компиляция

Для Go-приложений мы можем создать ещё более минимальный образ, используя


базовый образ scratch (пустой образ) и статическую компиляцию:

# СТАДИЯ 1: сборка с полной статической компиляцией


FROM golang:1.18-alpine AS builder
WORKDIR /app
COPY . .
# Статическая компиляция без CGO для создания полностью независимого бинарника
RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o myapp

# СТАДИЯ 2: пустой scratch-образ


FROM scratch
# Копируем SSL-сертификаты, если приложение будет делать HTTPS-запросы
COPY --from=builder /etc/ssl/certs/[Link] /etc/ssl/certs/
COPY --from=builder /app/myapp /myapp
# Запускаем бинарник напрямую
CMD ["/myapp"]

Преимущества такого подхода:

Экстремально маленький финальный образ (несколько MB вместо ~100MB)


Нет базовой ОС, только ваш бинарник и сертификаты
Повышенная безопасность — отсутствие потенциально уязвимых компонентов

Пример: Node (production build)

В Node-проектах, при production build (React, Vue, Angular) можно собрать статику
в одной стадии, а раздавать её через Nginx в другой.

# СТАДИЯ 1: сборка (Node)


FROM node:16-alpine AS build
WORKDIR /app
COPY package*.json .
RUN npm install
COPY . .
RUN npm run build

# СТАДИЯ 2: раздача (Nginx)


FROM nginx:1.21-alpine
COPY --from=build /app/dist /usr/share/nginx/html

Первая стадия (build): node + npm install + npm run build => создаёт финальный
dist.
Вторая: minimal Nginx-образ. Копируем результат /app/dist прямо в
/usr/share/nginx/html.
Финальный образ в итоге не содержит Node или npm — только файлы статики
и Nginx.

Оптимизированный Node-пример с кэшированием зависимостей

2/7
Для улучшения производительности сборки и более эффективного использования
кэша Docker можно разделить установку зависимостей и сборку кода:

# СТАДИЯ 1: только зависимости


FROM node:16-alpine AS deps
WORKDIR /app
# Копируем только файлы зависимостей
COPY [Link] [Link] ./
# Установка зависимостей
RUN npm ci --only=production

# СТАДИЯ 2: сборка на основе зависимостей


FROM node:16-alpine AS builder
WORKDIR /app
# Копируем установленные зависимости из первой стадии
COPY --from=deps /app/node_modules ./node_modules
# Теперь копируем исходный код
COPY . .
# Сборка
RUN npm run build

# СТАДИЯ 3: production образ


FROM nginx:1.21-alpine AS production
# Настройка Nginx (опционально)
COPY [Link] /etc/nginx/conf.d/[Link]
# Копирование только собранных файлов
COPY --from=builder /app/dist /usr/share/nginx/html
# Запуск Nginx
CMD ["nginx", "-g", "daemon off;"]

Преимущества этого подхода:

При изменении только исходного кода (без изменения [Link]) Docker


переиспользует кэш первой стадии
Лучшая изоляция — каждая стадия имеет свою конкретную ответственность
Возможность выбора конкретной стадии при сборке с помощью --target

Пример: Java (Spring Boot)


Для Java-приложений multi-stage builds также дают существенную экономию
размера, разделяя JDK (для сборки) и JRE (для запуска):

3/7
# СТАДИЯ 1: сборка с Maven и полным JDK
FROM maven:3.8-openjdk-17 AS builder
WORKDIR /app
# Копируем только [Link] для кэширования зависимостей
COPY [Link] .
RUN mvn dependency:go-offline

# Копируем исходный код и собираем приложение


COPY src ./src
RUN mvn package -DskipTests

# СТАДИЯ 2: только JRE для запуска


FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# Копируем только JAR-файл из builder стадии
COPY --from=builder /app/target/*.jar [Link]
# Запуск приложения
ENTRYPOINT ["java", "-jar", "[Link]"]

Эффект: размер финального образа уменьшается с ~500MB до ~120MB.

Как Docker знает, что копировать?

Инструкция COPY --from=<stage> /path/in/stage /path/in/final позволяет


вытащить файлы из предыдущей стадии. Имя стадии задаётся после AS, например
FROM node:16-alpine AS build. При копировании пишут --from=build.

Также можно использовать индекс стадии вместо имени (не рекомендуется, так как
это менее наглядно):

# СТАДИЯ 0 (индексация с нуля)


FROM golang:1.18-alpine
# ... команды ...

# СТАДИЯ 1
FROM alpine:3.17
# Копирование из стадии 0 по индексу
COPY --from=0 /app/myapp /usr/local/bin/myapp

Работа с аргументами сборки (ARG) в Multi-stage


В Multi-stage Dockerfile можно передавать аргументы между стадиями, но есть
некоторые нюансы:

4/7
# Глобальный ARG (перед первым FROM)
ARG VERSION=latest

# СТАДИЯ 1
FROM golang:1.18-alpine AS builder
# Нужно снова объявить ARG внутри стадии
ARG VERSION
RUN echo "Building version $VERSION"
# ...

# СТАДИЯ 2
FROM alpine:3.17
# И здесь снова объявляем, если нужно использовать
ARG VERSION
RUN echo "Version: $VERSION" > /[Link]
# ...

Важно: каждый FROM создаёт новый контекст, поэтому аргумент нужно объявлять
заново в каждой стадии

Выбор конкретной стадии при сборке

Если у вас есть несколько стадий, вы можете указать, какую именно нужно собрать,
с помощью флага --target:

# Собрать только стадию builder


docker build --target builder -t myapp:builder .

# Собрать полный образ до финальной стадии


docker build -t myapp:latest .

Это полезно при создании образов для разработки, тестирования или отладки.

Работа с секретами в Multi-stage builds

Часто при сборке нужны секреты (токены доступа к приватным репозиториям и т.д.),
но их нельзя хранить в образе. Multi-stage builds помогают решить эту проблему:

5/7
# СТАДИЯ 1: используем секреты
FROM node:16-alpine AS builder
WORKDIR /app
COPY . .
# Используем секрет для доступа к приватному репозиторию
RUN --mount=type=secret,id=npm_token \
cat /run/secrets/npm_token > .npmrc && \
npm install && \
rm .npmrc # Удаляем файл с секретом

# СТАДИЯ 2: в этой стадии секрет не сохраняется


FROM node:16-alpine AS production
WORKDIR /app
# Копируем только нужные файлы, без .npmrc с секретом
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY [Link] .
CMD ["npm", "start"]

Сборка с использованием этого секрета:

docker build --secret id=npm_token,src=.npmrc -t myapp:latest .

Общие рекомендации

Старайтесь именовать стадии: FROM golang:1.18 AS builder, FROM


alpine:3.17 AS final, это читабельнее.
В builder-стадии можно использовать RUN, COPY столько угодно. В финальной
же минимальный базовый образ.
Если нужно более 2-х стадий (например, одна для тестов, вторая для сборки,
третья для runtime) — всё возможно.

Сравнение размеров и примеры экономии

Язык/фреймворк Без multi-stage С multi-stage Экономия

Go ~800MB (golang:1.18) ~10MB (scratch) ~99%

[Link] + React ~1GB (node:16) ~100MB (nginx:alpine) ~90%

Java (Spring) ~500MB (maven+JDK) ~120MB (JRE) ~75%

Python ~900MB (python) ~250MB (python:slim) ~70%

Отладка Multi-stage builds


Если что-то идёт не так при multi-stage сборке, используйте эти техники:

1. Соберите образ до конкретной стадии: docker build --target builder -t


debug-image .

6/7
2. Запустите интерактивную оболочку для исследования: docker run -it debug-
image sh
3. Проверьте размер каждого слоя: docker history myapp:latest
4. Используйте docker image ls для просмотра всех образов, включая
промежуточные

Итог
Multi-stage builds — мощный способ сократить размер образа и отделить этап
разработки/сборки от этапа запуска. Особенно полезно для языков с компиляцией
(Go, C++, Rust) или больших JS-фреймворков (React/Vue) где сборка статических
файлов происходит в одном этапе, а раздача — в другом. Рекомендуется
использовать для «production» образов, где важно, чтобы в финале остался только
«runtime».

7/7
2 апреля 2025 г.

3.10 Практика

Создаем эффективный Multi-stage Dockerfile для React-


приложения
В этом задании вы создадите Multi-stage Dockerfile для React-приложения,
который будет использовать разные стадии для сборки и запуска.

Техническое задание

1. Стадия 1 (сборка):
Базовый образ — node:16-alpine
Рабочая директория — /app
Скопировать сначала только [Link] и [Link] для
оптимизации кэширования
Установить зависимости через npm ci
Скопировать остальные файлы проекта
Запустить сборку через npm run build
Назвать эту стадию build
2. Стадия 2 (production):
Базовый образ — nginx:1.21-alpine
Скопировать готовую сборку из первой стадии (/app/build) в директорию
Nginx (/usr/share/nginx/html)
Скопировать кастомный конфиг Nginx (опционально)
Открыть порт 80

Структура проекта

Dockerfile — создаваемый вами multi-stage файл

1/3
[Link] — базовый файл конфигурации React-приложения:

{
"name": "react-docker-example",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --passWithNoTests",
"eject": "react-scripts eject"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

[Link] (опционально) — конфигурация для Nginx:

server {
listen 80;

location / {
root /usr/share/nginx/html;
index [Link] [Link];
try_files $uri $uri/ /[Link];
}
}

Что нужно сдать?


1. Dockerfile с правильно настроенными multi-stage инструкциями.
2. Проверить сборку и запуск:

docker build -t react-multistage .


docker run -d -p 8080:80 --name reactapp react-multistage

Проверьте в браузере [Link] и убедитесь, что ваше React-


приложение работает.

2/3
3. Дополнительно: проверить запуск только до стадии тестирования:

docker build --target test -t react-test .

Результат использования multi-stage


После выполнения задания сравните размеры образов:

docker images | grep react

Вы должны увидеть существенную разницу между размером полного образа с


[Link] (~1GB) и финального образа на основе Nginx (~150MB).

Подсказка 1 (основная структура Dockerfile)

# Стадия сборки
FROM node:16-alpine AS build
WORKDIR /app

...

# Стадия production
FROM nginx:1.21-alpine
...

Подсказка 2 (полное решение, открывать только при затруднениях)

# Стадия сборки
FROM node:16-alpine AS build
WORKDIR /app

# Копируем файлы зависимостей


COPY package*.json ./
RUN npm ci

# Копируем исходный код


COPY public/ ./public/
COPY src/ ./src/
COPY .env* ./
COPY tsconfig*.json ./

# Запускаем сборку
RUN npm run build

# Стадия production - финальный образ


FROM nginx:1.21-alpine
# Копируем кастомный конфиг Nginx, если есть
COPY [Link] /etc/nginx/conf.d/[Link]
# Копируем результат сборки из стадии build
COPY --from=build /app/build /usr/share/nginx/html
# Открываем порт
EXPOSE 80
# Запускаем Nginx (встроенная команда образа nginx)
CMD ["nginx", "-g", "daemon off;"]

3/3
9 апреля 2025 г.

3.10 Практика

Оптимизируем Golang API через Multi-stage builds и scratch


контейнер
В этом задании вы создадите минимальный Docker-образ для Golang API-
сервиса, используя multi-stage сборку и контейнер scratch.

Техническое задание

1. Стадия 1 (сборка):
Базовый образ — golang:1.18-alpine
Рабочая директория — /app
Скопировать файлы [Link] и [Link] (если есть) для кэширования
зависимостей
Выполнить go mod download для загрузки зависимостей
Скопировать остальные файлы проекта
Собрать статический бинарник без CGO с помощью флагов:
CGO_ENABLED=0
GOOS=linux
go build -a -ldflags '-extldflags "-static"'
Назвать эту стадию builder
2. Стадия 2 (production):
Базовый образ — scratch (пустой контейнер)
Скопировать SSL-сертификаты из builder-стадии для поддержки HTTPS
Скопировать собранный бинарный файл из builder-стадии
Открыть порт 8080
Настроить точку входа (ENTRYPOINT) для запуска вашего API
3. Бонусное задание: Добавить дополнительную стадию для проверки
безопасности:
Стадия после сборки, но перед финальной стадией
Использовать инструмент gosec для проверки кода на уязвимости
Назвать эту стадию security-check

Структура проекта

Dockerfile — создаваемый вами multi-stage файл

1/4
[Link] — файл зависимостей:

module api-service

go 1.18

require (
[Link]/gin-gonic/gin v1.8.1
[Link]/stretchr/testify v1.8.0
)

[Link] — исходный код API:

package main

import (
"[Link]/gin-gonic/gin"
"net/http"
"os"
)

func main() {
r := [Link]()

[Link]("/health", func(c *[Link]) {


[Link]([Link], gin.H{
"status": "healthy",
})
})

[Link]("/api/data", func(c *[Link]) {


[Link]([Link], gin.H{
"message": "This API is built using multi-stage
Docker builds",
"language": "Go",
})
})

port := [Link]("PORT")
if port == "" {
port = "8080"
}

[Link](":" + port)
}

Что нужно сдать?


1. Dockerfile с правильно настроенными multi-stage инструкциями.

2/4
2. Проверить сборку и запуск:

docker build -t go-api-minimal .


docker run -d -p 8080:8080 --name goapi go-api-minimal

Проверьте, что API работает:

curl [Link]
curl [Link]

3. Дополнительно: проверить запуск только до стадии проверки безопасности:

docker build --target security-check -t go-api-secure .

Сравнение размеров образов


После выполнения задания сравните размеры образов:

docker images | grep go-api

Вы должны увидеть, что финальный образ имеет размер всего несколько мегабайт,
в отличие от сотен мегабайт стандартного golang-образа.

Подсказка 1 (копирование SSL-сертификатов)

# В финальном scratch-образе для работы с HTTPS необходимо скопировать сертификаты


COPY --from=builder /etc/ssl/certs/[Link] /etc/ssl/certs/

Подсказка 2 (полное решение, открывать только при затруднениях)

3/4
# Стадия сборки
FROM golang:1.18-alpine AS builder
WORKDIR /app

# Установка сертификатов и инструментов


RUN apk add --no-cache ca-certificates git

# Копируем только файлы зависимостей для лучшего кэширования


COPY [Link] ./
COPY [Link] ./
RUN go mod download

# Копируем исходный код


COPY *.go ./

# Собираем статический бинарник


RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o api-
service .

# Стадия проверки безопасности (бонусное задание)


FROM builder AS security-check
RUN go install [Link]/securego/gosec/v2/cmd/gosec@latest
RUN gosec ./...

# Финальная стадия - минимальный образ


FROM scratch
# Копируем SSL-сертификаты для поддержки HTTPS
COPY --from=builder /etc/ssl/certs/[Link] /etc/ssl/certs/
# Копируем собранное приложение
COPY --from=builder /app/api-service /api-service
# Открываем порт
EXPOSE 8080
# Запускаем приложение
ENTRYPOINT ["/api-service"]

4/4
Создаем базовый Multi-stage Dockerfile для Spring Boot
приложения
В этом задании вы создадите простой Multi-stage Dockerfile для Java Spring Boot
приложения с двумя стадиями: сборка и запуск.

Техническое задание

1. Стадия 1 (сборка):
Базовый образ — maven:3.8-openjdk-17
Рабочая директория — /build
Скопировать [Link] для кэширования зависимостей Maven
Запустить mvn dependency:go-offline для загрузки зависимостей
Скопировать исходный код приложения
Собрать проект с помощью mvn package -DskipTests
Назвать эту стадию builder
2. Стадия 2 (запуск):
Базовый образ — eclipse-temurin:17-jre-alpine (только JRE без
полного JDK)
Создать рабочую директорию /app
Скопировать собранный JAR-файл из первой стадии
Открыть порт 8080
Запустить JAR-файл с помощью java -jar

Структура проекта
Предположим, что у вас есть стандартный Spring Boot проект со следующей
структурой:

[Link] — конфигурация Maven (уже существует в вашем проекте)


src/ — исходный код вашего Spring Boot приложения
Dockerfile — вы создадите этот файл

Пример содержимого [Link]:

1/5
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="[Link]
xmlns:xsi="[Link]
xsi:schemaLocation="[Link]
[Link]
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.6</version>
</parent>

<groupId>[Link]</groupId>
<artifactId>spring-boot-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-demo</name>
<description>Demo project for Spring Boot</description>

<properties>
<[Link]>17</[Link]>
</properties>

<dependencies>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>[Link]</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

Простое Spring Boot приложение


src/main/java/com/example/demo/[Link]:

2/5
package [Link];

import [Link];
import [Link];
import [Link];
import [Link];

@SpringBootApplication
@RestController
public class DemoApplication {

public static void main(String[] args) {


[Link]([Link], args);
}

@GetMapping("/")
public String hello() {
return "Hello from Spring Boot!";
}

@GetMapping("/info")
public String info() {
return "Spring Boot application built with multi-stage Docker";
}
}

Что нужно сдать?


1. Dockerfile с двумя стадиями (сборка и запуск).
2. Проверить сборку и запуск:

docker build -t springboot-app .


docker run -d -p 8080:8080 --name springapp springboot-app

Проверьте в браузере [Link] и [Link]

Сравнение размеров образов


После создания образа, отметьте его размер и сравните с тем, что получилось бы
при использовании единственного образа на базе maven:3.8-openjdk-17:

docker images | grep springboot-app

Образ на базе только JRE должен быть примерно на 300-400 МБ меньше, чем образ
с полным JDK и Maven.

Подсказка (структура Dockerfile)

3/5
# Стадия сборки
FROM maven:3.8-openjdk-17 AS builder
WORKDIR /build
# Копирование и сборка...

# Стадия запуска
FROM eclipse-temurin:17-jre-alpine
# Копирование JAR и запуск...

Подсказка по кэшированию зависимостей Maven

# Копируем только [Link] сначала


COPY [Link] .
# Загружаем зависимости отдельно от сборки
RUN mvn dependency:go-offline
# Теперь копируем исходный код
COPY src ./src/

Полное решение (открывать только при затруднениях)

# Стадия сборки
FROM maven:3.8-openjdk-17 AS builder
WORKDIR /build

# Копируем только [Link] для кэширования зависимостей


COPY [Link] .
RUN mvn dependency:go-offline

# Копируем исходный код


COPY src ./src/

# Собираем проект
RUN mvn package -DskipTests

# Стадия запуска
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app

# Копируем JAR из стадии сборки


COPY --from=builder /build/target/*.jar [Link]

# Открываем порт
EXPOSE 8080

# Запускаем приложение
ENTRYPOINT ["java", "-jar", "[Link]"]

Преимущества этого подхода


После выполнения задания обратите внимание на следующие преимущества
использования multi-stage сборки для Spring Boot приложений:

Значительное уменьшение размера финального образа (с ~800 МБ до ~400


МБ)
Отделение процесса сборки от среды выполнения

4/5
Повышение безопасности за счет минимизации компонентов в runtime-образе
Ускорение сборки благодаря кэшированию зависимостей Maven
Образ содержит только необходимый рантайм (JRE) без инструментов
разработки

Дополнительные задачи

1. Добавьте передачу профиля Spring Boot через переменную окружения


SPRING_PROFILES_ACTIVE
2. Настройте возможность изменения порта через переменную окружения
3. Добавьте метки (LABEL) с информацией о версии и сборке приложения

5/5
Создаем оптимизированный Multi-stage Dockerfile для Python
Flask приложения
В этом задании вы разработаете эффективный Multi-stage Dockerfile для Python
Flask приложения, который разделит процесс разработки, тестирования и
эксплуатации.

Техническое задание
1. Стадия 1 (base):
Базовый образ — python:3.10-slim
Рабочая директория — /app
Установить только базовые зависимости, необходимые для всех стадий
Назвать эту стадию base
2. Стадия 2 (builder):
Базироваться на стадии base
Установить дополнительные пакеты, необходимые для компиляции
(build-essential, python3-dev)
Скопировать [Link]
Преобразовать зависимости в wheel-пакеты с помощью pip wheel
Назвать эту стадию builder
3. Стадия 3 (development):
Базироваться на стадии base
Установить как основные, так и dev-зависимости из wheel-пакетов
Включить дополнительные инструменты для разработки (pytest, black)
Настроить переменную среды FLASK_ENV=development
Назвать эту стадию development
4. Стадия 4 (production):
Базироваться на стадии base
Установить только основные зависимости из wheel-пакетов, без dev-
инструментов
Создать непривилегированного пользователя flask и назначить
правильные разрешения
Скопировать исходный код приложения
Настроить переменные среды для production (FLASK_ENV=production,
GUNICORN_WORKERS=4)
Открыть порт 5000
Запускать приложение с помощью gunicorn

Структура проекта

Dockerfile — создаваемый вами multi-stage файл

1/5
[Link] — зависимости проекта:

flask==2.2.3
werkzeug==2.2.3
gunicorn==20.1.0
SQLAlchemy==2.0.7
psycopg2-binary==2.9.5
marshmallow==3.19.0

[Link] — дополнительные зависимости для разработки:

-r [Link]
pytest==7.3.1
black==23.3.0
flake8==6.0.0
isort==5.12.0

[Link] — простое Flask-приложение:

from flask import Flask, jsonify

app = Flask(__name__)

@[Link]('/api/status')
def status():
return jsonify({
'status': 'ok',
'service': 'flask-demo',
'environment': [Link]('ENV', 'production')
})

@[Link]('/api/data')
def data():
return jsonify({
'message': 'This is a multi-stage Flask application',
'items': [
{'id': 1, 'name': 'Item 1'},
{'id': 2, 'name': 'Item 2'},
{'id': 3, 'name': 'Item 3'}
]
})

if __name__ == '__main__':
[Link](host='[Link]', debug=True)

[Link] — скрипт запуска для gunicorn:

#!/bin/bash
set -e

WORKERS=${GUNICORN_WORKERS:-4}
PORT=${PORT:-5000}

# Запуск gunicorn с правильным числом воркеров


exec gunicorn --bind [Link]:$PORT --workers $WORKERS app:app

2/5
Что нужно сдать?
1. Dockerfile с правильно настроенными multi-stage инструкциями.
2. Проверить сборку и запуск для разработки:

docker build --target development -t flask-dev .


docker run -d -p 5000:5000 -v $(pwd):/app --name flask-dev-app flask-dev
python [Link]

3. Проверить сборку и запуск для production:

docker build -t flask-prod .


docker run -d -p 5000:5000 --name flask-prod-app flask-prod

Протестируйте API по адресу [Link] и


[Link]

Сравнение размеров образов и слоев


После выполнения задания сравните размеры образов:

docker images | grep flask

Также проанализируйте слои образов и их размеры:

docker history flask-dev


docker history flask-prod

Вы должны увидеть, что:

Образ для разработки больше production образа из-за дополнительных


инструментов
Обе стадии используют оптимизированные wheel-пакеты из builder-стадии, что
ускоряет установку
Production-образ не содержит компиляторов и dev-зависимостей

Подсказка 1 (работа с wheel-пакетами)

# В стадии builder создаем wheel-пакеты


RUN pip wheel --no-cache-dir --wheel-dir /wheels -r [Link] -r
[Link]

# В других стадиях устанавливаем пакеты из wheels без компиляции


COPY --from=builder /wheels /wheels
RUN pip install --no-cache --no-index --find-links=/wheels -r [Link]

Подсказка 2 (полное решение, открывать только при затруднениях)

3/5
# Базовая стадия с общими зависимостями
FROM python:3.10-slim AS base
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1

# Стадия с компиляцией и сборкой wheel-пакетов


FROM base AS builder
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
python3-dev \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*

COPY requirements*.txt ./
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r [Link] -r
[Link]

# Стадия для разработки


FROM base AS development
COPY --from=builder /wheels /wheels
COPY requirements*.txt ./
RUN pip install --no-cache --no-index --find-links=/wheels -r [Link]
\
&& rm -rf /wheels

ENV FLASK_ENV=development \
FLASK_DEBUG=1

VOLUME /app
EXPOSE 5000
CMD ["python", "[Link]"]

# Финальная production стадия


FROM base AS production
COPY --from=builder /wheels /wheels
COPY [Link] ./
RUN pip install --no-cache --no-index --find-links=/wheels -r [Link] \
&& pip install --no-cache --no-index --find-links=/wheels gunicorn \
&& rm -rf /wheels \
&& adduser --disabled-password --gecos "" flask \
&& chown -R flask:flask /app

COPY [Link] ./
COPY [Link] ./
RUN chmod +x [Link]

ENV FLASK_ENV=production \
GUNICORN_WORKERS=4

USER flask
EXPOSE 5000
ENTRYPOINT ["./[Link]"]

4/5
Дополнительные задачи для продвинутого уровня
1. Добавьте стадию test, которая запускает автоматические тесты перед
созданием production-образа
2. Настройте систему health-checks для Docker с использованием инструкции
HEALTHCHECK
3. Оптимизируйте размер слоев, объединив связанные команды RUN
4. Реализуйте передачу номера версии приложения через ARG и отображение её
в API

Ключевые преимущества multi-stage для Python-приложений

После выполнения задания вы должны понимать следующие преимущества multi-


stage для Python:

Разделение среды разработки и production


Предварительная компиляция wheel-пакетов для ускорения установки
Исключение инструментов компиляции из финального образа
Возможность выбора нужной стадии при разных сценариях использования
Улучшенная безопасность за счет минимизации компонентов в production

5/5
Оптимизируем сборку C++ приложения с помощью Multi-stage
Dockerfile
В этом задании вы создадите Multi-stage Dockerfile для сборки и запуска простого
C++ приложения.

Техническое задание

1. Стадия 1 (сборка):
Базовый образ — gcc:11.2
Установить необходимые инструменты для сборки: cmake, make,
библиотеки
Скопировать исходный код и [Link]
Создать директорию для сборки и перейти в неё
Запустить cmake и make для сборки приложения
Назвать эту стадию builder
2. Стадия 2 (runtime):
Базовый образ — debian:bullseye-slim (минимальный образ)
Установить только необходимые библиотеки для запуска (runtime
dependencies)
Скопировать исполняемый файл из стадии сборки
Настроить команду запуска приложения

Структура проекта
У вас есть простое C++ приложение со следующей структурой:

src/[Link] — исходный код


[Link] — конфигурация CMake для сборки
Dockerfile — файл, который вы должны создать

Содержимое [Link]:

1/4
#include <iostream>
#include <vector>
#include <string>
#include <ctime>

int main() {
std::cout << "=== C++ Application ===" << std::endl;

// Показать текущее время


time_t now = time(0);
std::string dt = ctime(&now);
std::cout << "Текущее время: " << dt << std::endl;

// Демонстрация работы с вектором


std::vector<std::string> messages = {
"Это приложение собрано с использованием multi-stage Docker сборки",
"Финальный образ не содержит компилятора GCC и инструментов сборки",
"Размер образа значительно меньше, чем при использовании единого образа"
};

std::cout << "Информация:" << std::endl;


for (size_t i = 0; i < [Link](); ++i) {
std::cout << " - " << messages[i] << std::endl;
}

std::cout << "\nПриложение успешно запущено!" << std::endl;


return 0;
}

Содержимое [Link]:

cmake_minimum_required(VERSION 3.10)
project(cpp_docker_demo)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(cpp_app src/[Link])

install(TARGETS cpp_app DESTINATION bin)

Что нужно сдать?


1. Dockerfile с двумя стадиями (сборка и runtime).
2. Проверить сборку и запуск:

docker build -t cpp-app .


docker run --rm cpp-app

Сравнение размеров образов


После создания образа, проверьте его размер:

docker images | grep cpp-app

2/4
Для сравнения, соберите также версию с одной стадией:

# Однофазовый Dockerfile для сравнения


FROM gcc:11.2
RUN apt-get update && apt-get install -y cmake make
WORKDIR /app
COPY . .
RUN mkdir -p build && cd build && \
cmake .. && \
make
CMD ["./build/cpp_app"]

Сравните размеры образов:

docker build -t cpp-app-single -f [Link] .


docker images | grep cpp-app

Вы должны увидеть разницу в размере более 1GB!

Подсказка 1 (структура Dockerfile)

# Стадия сборки
FROM gcc:11.2 AS builder
# Установка инструментов, сборка...

# Стадия runtime
FROM debian:bullseye-slim
# Установка только необходимых библиотек, копирование исполняемого файла...

Подсказка 2 (рабочие директории и CMake)

# В стадии сборки
WORKDIR /app
COPY . .
RUN mkdir -p build && cd build && \
cmake .. && \
make

# В стадии runtime
COPY --from=builder /app/build/cpp_app /usr/local/bin/

Полное решение (открывать только при затруднениях)

3/4
# Стадия сборки
FROM gcc:11.2 AS builder

# Установка инструментов сборки


RUN apt-get update && apt-get install -y \
cmake \
make \
&& rm -rf /var/lib/apt/lists/*

# Копирование исходного кода


WORKDIR /app
COPY . .

# Сборка приложения
RUN mkdir -p build && cd build && \
cmake .. && \
make

# Стадия runtime
FROM debian:bullseye-slim

# Установка только необходимых библиотек


RUN apt-get update && apt-get install -y \
libstdc++6 \
&& rm -rf /var/lib/apt/lists/*

# Копирование исполняемого файла из стадии сборки


COPY --from=builder /app/build/cpp_app /usr/local/bin/cpp_app

# Запуск приложения
CMD ["/usr/local/bin/cpp_app"]

Примечание о размере образов

C++ особенно выигрывает от использования multi-stage сборки, так как:

Компилятор GCC и инструменты разработки занимают более 1GB


Готовое C++ приложение обычно представляет собой один исполняемый файл
Для запуска нужны лишь несколько библиотек времени выполнения (runtime
libraries)

При использовании статической компиляции (статическая линковка библиотек)


можно добиться еще большего уменьшения размера, вплоть до использования
scratch в качестве базового образа.

4/4
Оптимизация Docker-образа для Ruby on Rails приложения с
multi-stage сборкой
В этом задании вы создадите эффективный Multi-stage Dockerfile для Rails-
приложения, который отделит этап сборки зависимостей и ассетов от этапа запуска.

Техническое задание

1. Стадия 1 (builder):
Базовый образ — ruby:3.1
Установить необходимые системные зависимости (nodejs, yarn, build-
essential)
Установить гемы Rails через Bundler
Скомпилировать ассеты с помощью rails assets:precompile
Очистить временные файлы после компиляции ассетов
Назвать эту стадию builder
2. Стадия 2 (runtime):
Базовый образ — ruby:3.1-slim (меньший размер за счёт отсутствия
компиляторов)
Установить минимальные необходимые системные зависимости
Скопировать установленные гемы из стадии builder
Скопировать код приложения и скомпилированные ассеты из builder
Создать непривилегированного пользователя для запуска приложения
Открыть порт 3000
Настроить запуск Rails-приложения в production-режиме

Структура проекта

Предположим, что у вас есть стандартный проект Rails со следующей структурой:

Gemfile и [Link] — список зависимостей Ruby


app/, config/, db/ и т.д. — стандартные директории Rails
[Link] и [Link] — зависимости JavaScript (если используется)
Dockerfile — файл, который вы создадите

Пример содержимого Gemfile:

1/5
source '[Link]
git_source(:github) { |repo| "[Link] }

ruby '3.1.2'

gem 'rails', '~> 7.0.4'


gem 'puma', '~> 5.0'
gem 'pg', '~> 1.4'
gem 'bootsnap', require: false
gem 'sassc-rails'
gem 'image_processing', '~> 1.2'
gem 'jsbundling-rails'
gem 'cssbundling-rails'

group :development, :test do


gem 'debug', platforms: %i[ mri mingw x64_mingw ]
gem 'rspec-rails'
end

group :development do
gem 'web-console'
end

group :test do
gem 'capybara'
gem 'selenium-webdriver'
end

Пример [Link]:

{
"name": "rails_app",
"private": true,
"dependencies": {
"@hotwired/stimulus": "^3.1.0",
"@hotwired/turbo-rails": "^7.2.0",
"@popperjs/core": "^2.11.6",
"bootstrap": "^5.2.2",
"esbuild": "^0.15.12",
"sass": "^1.55.0"
},
"scripts": {
"build": "esbuild app/javascript/*.* --bundle --sourcemap --
outdir=app/assets/builds --public-path=assets",
"build:css": "sass
./app/assets/stylesheets/[Link]:./app/assets/builds/applicatio
[Link] --no-source-map --load-path=node_modules"
}
}

Что нужно сдать?


1. Dockerfile с двумя стадиями (builder и runtime).

2/5
2. Проверить сборку и запуск:

docker build -t rails-app .


docker run -d -p 3000:3000 --name railsapp rails-app

Сравнение размеров образов

После создания образа, проверьте его размер и сравните с обычным подходом:

docker images | grep rails-app

Вы должны увидеть значительное уменьшение размера (около 200-300 МБ)


благодаря:

Использованию slim-версии Ruby без dev-инструментов


Отсутствию временных файлов сборки ассетов
Отсутствию инструментов компиляции в финальном образе

Подсказка (структура Dockerfile)

# Стадия сборки
FROM ruby:3.1 AS builder
# Установка системных зависимостей...
# Установка гемов...
# Компиляция ассетов...

# Стадия запуска
FROM ruby:3.1-slim
# Установка минимальных зависимостей...
# Копирование установленных гемов и приложения...

Подсказка (установка и копирование гемов)

# В стадии builder
ENV BUNDLE_PATH=/usr/local/bundle
COPY Gemfile [Link] ./
RUN bundle install --jobs=4 --without development test

# В стадии runtime
COPY --from=builder /usr/local/bundle /usr/local/bundle

Полное решение (открывать только при затруднениях)

3/5
# Стадия сборки
FROM ruby:3.1 AS builder

# Установка системных зависимостей


RUN apt-get update -qq && \
apt-get install -y nodejs npm build-essential libpq-dev && \
npm install -g yarn && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# Настройка рабочей директории


WORKDIR /app

# Установка гемов
ENV BUNDLE_PATH=/usr/local/bundle
COPY Gemfile [Link] ./
RUN bundle config set --local without 'development test' && \
bundle install --jobs=4

# Установка JavaScript зависимостей


COPY [Link] [Link] ./
RUN yarn install

# Копирование исходного кода и компиляция ассетов


COPY . .
RUN SECRET_KEY_BASE=dummy RAILS_ENV=production bundle exec rails assets:precompile
&& \
rm -rf tmp/cache node_modules app/javascript vendor/javascript

# Стадия запуска
FROM ruby:3.1-slim

# Установка минимальных зависимостей


RUN apt-get update -qq && \
apt-get install -y --no-install-recommends libpq5 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# Создание непривилегированного пользователя


RUN groupadd -r rails && useradd -r -g rails rails
USER rails

# Настройка рабочей директории


WORKDIR /app

# Копирование установленных гемов


COPY --from=builder /usr/local/bundle /usr/local/bundle
COPY --from=builder --chown=rails:rails /app /app

# Переменные среды
ENV RAILS_ENV=production \
RAILS_SERVE_STATIC_FILES=true \
RAILS_LOG_TO_STDOUT=true

# Открытие порта
EXPOSE 3000

4/5
# Запуск приложения
CMD ["bundle", "exec", "rails", "server", "-b", "[Link]"]

Преимущества этого подхода


После выполнения задания обратите внимание на следующие преимущества
использования multi-stage сборки для Rails-приложений:

Уменьшение размера образа на 200-300 МБ


Сокращение поверхности атаки за счёт отсутствия инструментов разработки
Разделение зависимостей сборки и зависимостей времени выполнения
Упрощение процесса CI/CD за счёт единого Dockerfile для всех окружений
Предварительная компиляция ассетов в процессе сборки образа (а не во
время запуска)

Дополнительные задачи
1. Добавить многоступенчатый кэш для зависимостей:
Отдельное копирование Gemfile и [Link]
Разделение установки гемов и Node-пакетов
2. Добавить аргументы сборки для настройки:
Версии Ruby
Окружения Rails (development/production)
3. Добавить механизм ожидания запуска базы данных перед запуском
приложения:
Создать entrypoint-скрипт, который проверяет доступность базы данных
Запускать приложение только после готовности базы данных

5/5
Создаем Multi-stage Dockerfile для веб-приложения
В этом задании вы создадите Multi-stage Dockerfile с тремя стадиями для полного
цикла разработки, тестирования и развертывания веб-приложения.

Техническое задание
1. Стадия 1 (deps):
Базовый образ — node:16-alpine
Рабочая директория — /app
Скопировать только файлы зависимостей ([Link] и package-
[Link])
Установить все зависимости через npm ci
Назвать эту стадию deps
2. Стадия 2 (test):
Базироваться на стадии deps
Скопировать исходный код приложения
Запустить линтинг и тесты (команды npm run lint и npm run test)
Назвать эту стадию test
3. Стадия 3 (production):
Снова начать с базового образа node:16-alpine
Создать непривилегированного пользователя nodeuser
Скопировать установленные зависимости из стадии deps (только
production зависимости)
Скопировать исходный код приложения
Переключиться на непривилегированного пользователя
Открыть порт 3000
Запустить приложение через npm start

Структура проекта
Dockerfile — файл, который вы создадите

1/5
[Link] — файл конфигурации [Link] приложения:

{
"name": "three-stage-demo",
"version": "1.0.0",
"description": "Demo for multi-stage build",
"main": "[Link]",
"scripts": {
"start": "node [Link]",
"lint": "eslint .",
"test": "jest"
},
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"eslint": "^8.28.0",
"jest": "^29.3.1"
}
}

[Link] — простой Express сервер:

const express = require('express');


const app = express();
const PORT = [Link] || 3000;

[Link]('/', (req, res) => {


[Link]('Hello from multi-stage Docker build!');
});

[Link]('/info', (req, res) => {


[Link]({
app: 'three-stage-demo',
stages: ['deps', 'test', 'production'],
environment: [Link].NODE_ENV || 'development'
});
});

[Link](PORT, () => {
[Link](`Server running on port ${PORT}`);
});

[Link] = app; // для тестов

2/5
[Link] — простой тест для приложения:

const app = require('./server');


const request = require('supertest');

test('GET / returns correct greeting', async () => {


const response = await request(app).get('/');
expect([Link]).toBe(200);
expect([Link]).toContain('Hello from multi-stage Docker build!');
});

test('GET /info returns correct info', async () => {


const response = await request(app).get('/info');
expect([Link]).toBe(200);
expect([Link]).toHaveProperty('app', 'three-stage-demo');
expect([Link]).toHaveProperty('stages');
expect([Link]).toHaveLength(3);
});

.[Link] — простая конфигурация ESLint:

{
"env": {
"node": true,
"jest": true
},
"extends": "eslint:recommended",
"rules": {
"no-console": "off"
}
}

Что нужно сдать?

1. Dockerfile с тремя стадиями (deps, test, production).


2. Проверить сборку с тестированием:

docker build --target test -t web-app:test .

3. Собрать и запустить финальный образ:

docker build -t web-app:prod .


docker run -d -p 3000:3000 --name webapp web-app:prod

Проверьте в браузере [Link] и [Link]

Преимущества трехэтапного подхода

Обратите внимание на следующие преимущества использования трех стадий:

3/5
Стадия deps: Оптимизирует кэширование зависимостей. При изменении
только кода приложения (без изменения зависимостей) Docker переиспользует
кэш этой стадии.
Стадия test: Гарантирует, что код проходит тесты и соответствует стандартам,
прежде чем попадет в production-образ.
Стадия production: Создает минимальный образ без dev-зависимостей и
инструментов тестирования/линтинга.

Подсказка (структура Dockerfile)

# Стадия зависимостей
FROM node:16-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# Стадия тестирования
FROM deps AS test
COPY . .
RUN npm run lint && npm run test

# Стадия production
FROM node:16-alpine
# ...инструкции для production...

Подсказка по оптимизации production стадии

# В production стадии
WORKDIR /app
COPY package*.json ./
COPY --from=deps /app/node_modules /app/node_modules
# Удаляем dev-зависимости, оставляя только production
RUN npm prune --production

Полное решение (открывать только при затруднениях)

4/5
# Стадия зависимостей
FROM node:16-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# Стадия тестирования
FROM deps AS test
COPY . .
RUN npm run lint && npm run test

# Стадия production
FROM node:16-alpine
# Создание непривилегированного пользователя
RUN addgroup -g 1001 -S nodeuser && \
adduser -S -u 1001 -G nodeuser nodeuser

WORKDIR /app
# Установка только production зависимостей
COPY package*.json ./
COPY --from=deps /app/node_modules /app/node_modules
RUN npm prune --production

# Копирование кода приложения


COPY . .

# Назначение пользователя
USER nodeuser

# Переменные среды
ENV NODE_ENV=production \
PORT=3000

# Открытие порта
EXPOSE 3000

# Запуск приложения
CMD ["npm", "start"]

5/5
Создаем Dockerfile для машинного обучения
В этом задании вы разработаете Multi-stage Dockerfile с тремя стадиями для
полного цикла создания и развертывания модели машинного обучения.

Техническое задание
1. Стадия 1 (data-prep):
Базовый образ — python:3.9-slim
Установка необходимых библиотек для обработки данных (pandas, scikit-
learn)
Копирование скрипта предобработки данных
Предварительная обработка набора данных и сохранение в формате для
обучения
Назвать эту стадию data-prep
2. Стадия 2 (model-training):
Базовый образ — python:3.9 (полная версия Python с инструментами
сборки)
Установка библиотек машинного обучения (scikit-learn, joblib)
Копирование скрипта обучения модели
Копирование подготовленных данных из первой стадии
Обучение модели и сохранение в файл
Оценка метрик качества обучения
Назвать эту стадию model-training
3. Стадия 3 (model-serving):
Базовый образ — python:3.9-slim
Установка минимальных библиотек для API (Flask, joblib, scikit-learn)
Копирование обученной модели из второй стадии
Копирование скрипта веб-сервера для API
Открытие порта 5000
Запуск Flask API сервера для получения предсказаний модели

Структура проекта

Dockerfile — создаваемый вами файл


data/ — директория с исходными данными:
[Link] — классический набор данных Iris
scripts/ — директория со скриптами:
[Link] — скрипт подготовки данных
[Link] — скрипт обучения модели
[Link] — веб-сервер с API для модели

Содержимое data/[Link] (стандартный датасет Iris):

1/9
Скачать датасет можно по ссылке - [Link]

Что внутри?

sepal_length,sepal_width,petal_length,petal_width,species
5.1,3.5,1.4,0.2,setosa
4.9,3.0,1.4,0.2,setosa
...
7.0,3.2,4.7,1.4,versicolor
...
6.3,3.3,6.0,2.5,virginica
...

Содержимое скрипта scripts/[Link]:

2/9
import pandas as pd
from sklearn.model_selection import train_test_split
from [Link] import StandardScaler, LabelEncoder
import os
import joblib

print("Starting data preprocessing...")

# Создаем директории для результатов


[Link]('/data/processed', exist_ok=True)

# Загружаем данные
df = pd.read_csv('/data/raw/[Link]')
print(f"Loaded dataset with {[Link][0]} rows and {[Link][1]} columns")

# Разделяем признаки и целевую переменную


X = [Link]('species', axis=1)
y = df['species']

# Кодируем целевую переменную


le = LabelEncoder()
y_encoded = le.fit_transform(y)

# Сохраняем энкодер
[Link](le, '/data/processed/label_encoder.joblib')
print("Saved label encoder")

# Масштабируем признаки
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# Сохраняем скейлер
[Link](scaler, '/data/processed/[Link]')
print("Saved scaler")

# Разделяем на обучающую и тестовую выборки


X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y_encoded, test_size=0.2, random_state=42
)

# Сохраняем подготовленные данные


[Link]((X_train, y_train), '/data/processed/train_data.joblib')
[Link]((X_test, y_test), '/data/processed/test_data.joblib')

print("Preprocessing completed. Data saved to /data/processed/")

Содержимое скрипта scripts/[Link]:

3/9
from [Link] import RandomForestClassifier
from [Link] import accuracy_score, classification_report
import joblib
import os

print("Starting model training...")

# Создаем директорию для модели


[Link]('/model', exist_ok=True)

# Загружаем подготовленные данные


X_train, y_train = [Link]('/data/processed/train_data.joblib')
X_test, y_test = [Link]('/data/processed/test_data.joblib')

print(f"Loaded training data with {X_train.shape[0]} samples")


print(f"Loaded test data with {X_test.shape[0]} samples")

# Создаем и обучаем модель


model = RandomForestClassifier(n_estimators=100, random_state=42)
[Link](X_train, y_train)
print("Model training completed")

# Оцениваем модель
y_pred = [Link](X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Model accuracy: {accuracy:.4f}")
print("\nClassification Report:")
print(classification_report(y_test, y_pred))

# Сохраняем модель
[Link](model, '/model/iris_model.joblib')
print("Model saved to /model/iris_model.joblib")

Содержимое скрипта scripts/[Link]:

4/9
from flask import Flask, request, jsonify
import joblib
import numpy as np
import os

app = Flask(__name__)

# Загружаем модель и препроцессоры


model = [Link]('/model/iris_model.joblib')
scaler = [Link]('/data/processed/[Link]')
encoder = [Link]('/data/processed/label_encoder.joblib')

@[Link]('/predict', methods=['POST'])
def predict():
# Получаем данные из запроса
data = [Link]

# Проверяем наличие всех необходимых полей


required_fields = ['sepal_length', 'sepal_width', 'petal_length',
'petal_width']
if not all(field in data for field in required_fields):
return jsonify({"error": "Missing required fields"}), 400

# Подготавливаем входные данные


features = [Link]([[
float(data['sepal_length']),
float(data['sepal_width']),
float(data['petal_length']),
float(data['petal_width'])
]])

# Масштабируем данные
features_scaled = [Link](features)

# Получаем предсказание
prediction = [Link](features_scaled)

# Декодируем результат
species = encoder.inverse_transform(prediction)[0]

# Возвращаем результат
return jsonify({
"prediction": species,
"prediction_code": int(prediction[0]),
"probability": float([Link](model.predict_proba(features_scaled)))
})

@[Link]('/health', methods=['GET'])
def health():
return jsonify({"status": "ok"})

if __name__ == '__main__':
[Link](host='[Link]', port=5000)

Что нужно сдать?

5/9
1. Dockerfile с тремя стадиями (data-prep, model-training, model-serving).
2. Проверить сборку и запуск:

docker build -t ml-iris-model .


docker run -d -p 5000:5000 --name ml-api ml-iris-model

3. Протестировать API с помощью curl:

curl -X POST [Link] \


-H "Content-Type: application/json" \
-d '{"sepal_length": 5.1, "sepal_width": 3.5, "petal_length": 1.4,
"petal_width": 0.2}'

Преимущества трехэтапного подхода для ML-проектов

Стадия data-prep: Изолирует предобработку данных, позволяя


переиспользовать кэш даже при изменении модели.
Стадия model-training: Выполняет ресурсоемкие вычисления в отдельном
контейнере, который может содержать дополнительные инструменты.
Стадия model-serving: Создает легковесный образ только с необходимыми
компонентами для серверной части.

Дополнительные преимущества:

Возможность запуска только стадии обучения для экспериментов: docker


build --target model-training -t ml-training .
Раздельное кэширование для каждой стадии, что ускоряет итеративную
разработку
Различные стадии могут иметь разные зависимости или версии Python

Подсказка (структура Dockerfile)

# Стадия предобработки данных


FROM python:3.9-slim AS data-prep
# ... установка зависимостей и подготовка данных

# Стадия обучения модели


FROM python:3.9 AS model-training
# ... установка библиотек ML и обучение модели

# Стадия сервинга модели


FROM python:3.9-slim
# ... настройка API сервера

Подсказка по работе с путями и директориями

6/9
# Создайте отдельные директории для каждого этапа
WORKDIR /app

# Для передачи данных между стадиями


# В стадии 1:
VOLUME /data/processed

# В стадии 2:
COPY --from=data-prep /data/processed /data/processed
VOLUME /model

# В стадии 3:
COPY --from=model-training /model /model
COPY --from=data-prep /data/processed /data/processed

Полное решение (открывать только при затруднениях)

7/9
# Стадия 1: Предобработка данных
FROM python:3.9-slim AS data-prep

WORKDIR /app

# Установка необходимых библиотек


RUN pip install --no-cache-dir pandas scikit-learn joblib

# Создаем структуру директорий


RUN mkdir -p /data/raw /data/processed

# Копируем исходные данные и скрипт предобработки


COPY data/[Link] /data/raw/
COPY scripts/[Link] .

# Запускаем предобработку
RUN python [Link]

# Стадия 2: Обучение модели


FROM python:3.9 AS model-training

WORKDIR /app

# Установка библиотек машинного обучения


RUN pip install --no-cache-dir scikit-learn joblib

# Копируем подготовленные данные из первой стадии


COPY --from=data-prep /data/processed /data/processed

# Копируем скрипт обучения


COPY scripts/[Link] .

# Обучаем модель
RUN python [Link]

# Стадия 3: Развертывание модели в API сервисе


FROM python:3.9-slim

WORKDIR /app

# Установка минимальных зависимостей для сервинга


RUN pip install --no-cache-dir flask scikit-learn joblib gunicorn

# Копируем модель и препроцессоры


COPY --from=model-training /model /model
COPY --from=data-prep /data/processed /data/processed

# Копируем API скрипт


COPY scripts/[Link] .

# Открываем порт
EXPOSE 5000

# Запускаем приложение через gunicorn для production


CMD ["gunicorn", "--bind", "[Link]:5000", "app:app"]

8/9
Реальное применение в индустрии
Такой подход особенно полезен для ML-проектов, потому что:

Разделяет ресурсоемкий процесс обучения и легковесный процесс


предсказания
Позволяет использовать разные среды для разных этапов ML-пайплайна
Упрощает CI/CD для моделей машинного обучения
Обеспечивает воспроизводимость результатов от подготовки данных до
финального развертывания

9/9
3.11 Типовые ошибки

Типичные ошибки в Dockerfile


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

1. Оставлять пользователя root по умолчанию


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

Решение: Создать непривилегированного пользователя и переключиться на него


через USER. К примеру (Alpine):

RUN adduser -D myuser


USER myuser

Убедитесь, что рабочая директория и файлы имеют нужные права (chown),


чтобы приложение могло их использовать.
Итог: приложение работает от «myuser», а не root.

2. Копирование лишних файлов в образ (увеличение размера)

Проблема: При COPY . . вы не исключили node_modules, .git, __pycache__ или


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

Решение: Использовать .dockerignore.

# Пример .dockerignore:
.git
node_modules
__pycache__
*.env

Так Docker не будет копировать указанные файлы/папки в образ.

3. Ставить всё в отдельные RUN, плодя слои


Проблема:

RUN apt-get update


RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*

1/4
Каждая команда = новый слой. Избыточно, плюс в результате остаются временные
файлы.

Решение: Объединить команды — одна команда RUN:

RUN apt-get update && \


apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*

Таким образом создаётся только один слой, и кэш Docker проверяет его разом.

4. Неиспользование кэша при установке зависимостей


Проблема:

COPY . /app
RUN npm install

Если вы меняете любой файл (даже не [Link]), Docker пересобирает npm


install — дорого и долго.

Решение:

# Сначала копируем package*.json, устанавливаем deps


COPY package*.json /app
RUN npm install

# Потом только остальной код


COPY . /app

Теперь если вы меняете только код, а не зависимости, npm install берётся из


кэша.

5. Не удалять временные файлы, кэш, лишние пакеты


Проблема: После установки всякого (apt-get, apk, npm install) остаются временные
файлы, кэш, которые тоже попадают в конечный образ. Увеличивается размер,
забивая пространство.

Решение: Удалять их в той же RUN-команде, чтобы они не попали в предыдущий


слой. Примеры:

RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

RUN pip install --no-cache-dir -r [Link]

--no-cache-dir не будет хранить wheel-кеши.


Если нужно, делайте npm ci (который не оставляет кеш) вместо npm install.

6. Хранить секреты (.env) в образе

2/4
Проблема: Копируете .env или [Link] с паролями / API ключами прямо в
образ — могут утечь важные данные.

Решение:

Не копировать такие файлы, использовать -e переменные среды при запуске


(или Docker Secrets / Vault).
В .dockerignore указать .env и др.

7. Оставлять всё в root / не задавать USER

Проблема: Приложение (Nginx, Node, Python) может работать от root. Это


небезопасно.

Решение: Создать пользователя myuser (или node / www-data) и сделать USER


myuser.

RUN adduser -D myuser


USER myuser

Убедитесь, что рабочая директория и файлы приложения имеют нужные


права.

8. Неверный порядок инструкций (неиспользование кэша)

Проблема: Сначала COPY . ., а потом RUN npm install. Любое изменение кода
ломает кэш для npm install.

Решение:

COPY package*.json ./
RUN npm install
COPY . .

Так npm install берётся из кэша, пока [Link] не меняется.

9. Неиспользование multi-stage build при сборке больших приложений

Проблема: Для компиляции Go/Java/C++ вы устанавливаете кучу dev-


зависимостей, и они остаются в финальном образе.

Решение: multi-stage build:

FROM golang:1.18 AS builder


WORKDIR /app
COPY . .
RUN go build -o myapp

FROM alpine:3.17
COPY --from=builder /app/myapp /usr/local/bin/myapp
CMD ["myapp"]

3/4
Второй этап получает лишь бинарник, всё dev мусор остаётся в первом.

Вывод

USER — уйти от root.


.dockerignore — исключить лишние файлы.
Объединять RUN — меньше слоёв.
Сначала копировать deps, потом код — кэш эффективнее.
Multi-stage builds — собрать / тестировать в одном этапе, финальный образ
«чистый».
Не хранить секреты и не делать COPY .env, etc.

Устранив эти типовые ошибки, вы сделаете образы легче, сборку быстрее и


повысите безопасность контейнера.

4/4
2 апреля 2025 г.

4.1 Архитектура сетей

Одно из ключевых преимуществ Docker — это гибкая модель работы с сетями.


Контейнеры можно подключать к различным видам сетей (bridge, host, none,
overlay), в зависимости от того, как именно вы хотите организовать их
взаимодействие между собой и с внешним миром

1. Bridge Network
Bridge — это наиболее часто используемый и дефолтный тип сети при запуске
контейнера без особых флагов. Ваш контейнер получает виртуальный Ethernet-
интерфейс и IP-адрес внутри этой сети. Docker настраивает NAT, чтобы вы могли
публиковать порты наружу.

docker0 (Linux): на хосте обычно существует виртуальный интерфейс docker0


(или другое имя), к которому мостятся контейнеры.
User-defined bridge: вы можете создавать свои собственные сети (docker
network create my-bridge) и подключать контейнеры к ним. В этих сетях
контейнеры автоматически могут «видеть» друг друга по имени (через
встроенный DNS Docker).
Для доступа извне к контейнеру используют -p HOSTPORT:CONTAINERPORT.
Docker настраивает iptables, чтобы пакеты перенаправлялись к контейнеру.

Плюсы:

Изоляция: контейнеры в одной bridge-сети не видят контейнеры в другой (если


вы создали несколько user-defined сетей).
Удобство: можно опустить сложную настройку, просто bridge и пробрасывать
порты.

Минусы:

Есть небольшой overhead из-за NAT (iptables). Иногда это не проблема, но в


сетях с высокой нагрузкой может заметно.

2. Host Network

1/4
Host network означает, что контейнер разделяет сетевой стек хостовой машины, без
отдельного виртуального интерфейса. Проще говоря, никаких iptables/NAT для
контейнера — всё напрямую на хосте.

docker run --network=host ... (на Linux) — тогда контейнер слушает тот же
интерфейс, что и хост. Если внутри контейнера приложение слушает 80 порт,
то это будет 80 порт самого хоста (без -p).
В Windows/macOS режим host network практически не работает в том же
смысле (ограничения виртуализации), так что эта опция по-настоящему
полезна на Linux.

Плюсы:

Нет overhead на NAT (iptables). Производительность выше при сетевой


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

Минусы:

Порт-коллизии: если контейнер слушает 80, а хост уже использует 80 —


конфликт.
Меньше изоляции. Все порты контейнера = порты хоста.

2/4
3. None Network
None network означает вообще никакой сети (кроме lo внутри контейнера).
Контейнер не подключается ни к какой внешней сети, не получает IP.

docker run --network=none — контейнер отрезан от внешнего мира.


Иногда используют для особых сценариев (полная изоляция) или когда сами
вручную будут привязывать интерфейсы.

Плюсы:

Абсолютная изоляция от сети, контейнер недоступен ни снаружи, ни к другим


контейнерам.

Минусы:

Подобный контейнер не может ни скачать пакеты, ни пинговать что-либо, если


вы не настроите ручную сеть.

4. Overlay Network
Overlay используется для контейнеров, раскиданных по разным хостам, например в
Docker Swarm или Kubernetes (там свои аналоги overlay). Docker Swarm, например,
создает VXLAN-туннели между хостами, чтобы контейнеры в одной overlay-сети
видели друг друга по IP, даже если физически они на разных узлах.

docker network create --driver=overlay my-overlay (в Swarm-режиме) —


создаёт распределённую сеть.
Контейнеры могут взаимодействовать по сервисным именам (DNS),
Docker/Swarm решает маршрутизацию на физическом уровне.

Плюсы:

Очень удобно для микросервисов в кластере, когда сервис A (на хосте1) и


сервис B (на хосте2) видят друг друга, будто в одной локальной сети.

Минусы:

Сложнее настройка и overhead на VXLAN-туннель (все пакеты encapsulated).

Docker DNS и alias


В user-defined bridge или overlay сетях, Docker автоматически поднимает DNS. Вы
можете сделать ping mydb (где mydb — имя контейнера/сервиса), и Docker DNS
разрешит это имя в IP внутри сети. Это упрощает настройку микросервисов:
достаточно DB_HOST=mydb.

Как Docker реализует NAT (в случае bridge)?

3/4
На хосте создаётся виртуальный интерфейс (docker0).
Каждый контейнер получает внутренний IP из пула (172.17.x.x или 192.168.x.x
— в зависимости от сети).
При -p 80:80, Docker прописывает iptables-правило, перенаправляющее
трафик с хоста:80 на IP/порт контейнера.

Когда использовать какой тип

Bridge (по умолчанию): достаточно для большинства сценариев на одном


хосте. Пробрасывать порты наружу через -p.
Host: когда нужна максимальная производительность сети или хотите
избежать NAT overhead, и знаете, что не будет конфликтов портов. Но только
на Linux.
None: когда контейнер вообще не должен иметь сетевого доступа. Или вы
вручную привяжете что-то.
Overlay: когда у вас несколько Docker-хостов в Swarm (или используете
Kubernetes), и вы хотите, чтобы контейнеры на разных узлах были в одной
логической сети.

Вывод

У Docker гибкая модель сетей, позволяющая организовывать общение контейнеров


так, как это нужно для различных сценариев — от полного none до распределённых
overlay в Swarm. На локальном окружении чаще всего пользуются bridge (дефолт) с
-p для внешнего доступа. Понимание того, как Docker натягивает iptables,
организует DNS и создает виртуальные интерфейсы, помогает точнее
диагностировать проблемы и проектировать сетевую топологию.

4/4
4.2 Практика

Создаем пользовательскую (user-defined) Bridge-сеть и


проверяем связь контейнеров
Цель: Научиться создавать собственную (user-defined) bridge-сеть и подключать к
ней несколько контейнеров. Убедиться, что контейнеры могут взаимодействовать
друг с другом по DNS-именам внутри этой сети.

Сценарий задания
1. Создайте пользовательскую сеть (bridge). Назовите ее, например, mynet.
2. Запустите два контейнера в этой сети (любой образ, который позволяет что-то
запустить, например alpine или nginx на ваше усмотрение).
3. Проверьте, что один контейнер видит другой по DNS-имени (то есть сможет
пингануть второй по его имени). Например, если один контейнер называется
app1, а другой app2, то app1 должен обращаться к app2.

Что сдать в качестве ответа?

Список команд, которые вы использовали для:


Создания сети
Запуска двух контейнеров в этой сети
Проверки DNS-доступа между ними

Подсказка (раскрывать при необходимости)


Для создания сети есть отдельная команда docker network create с
драйвером bridge.
Чтобы запустить контейнер в нужной сети, используйте флаг --network.
Если образы маленькие (например, alpine), можно использовать команду ping
внутри контейнера, проверяя DNS-имя другого.
Имя контейнера это --name при docker run. Именно его можно пинговать (или
curl), чтобы проверить DNS.

Итог: Если все сделано правильно, один контейнер сможет обращаться к другому
по имени, благодаря встроенному DNS Docker в user-defined bridge-сети.

1/1
4.2 Практика

Запускаем контейнер в host network (только Linux)


Цель: Разобраться, как работает сеть host в Docker, где контейнер пользуется
сетевым стеком хоста напрямую, без NAT и проброса портов.

Сценарий задания
1. Важно: Данный вариант сети полноценно работает только на Linux (на
Windows/Mac он ведет себя иначе).
2. Запустите любой контейнер (например, nginx или httpd), используя режим --
network=host. То есть:
Без -p флагов, так как порты контейнера сразу «совпадают» с портами
хоста.
3. Убедитесь, что если контейнер слушает внутри на 80 порту, то на хосте также
занят порт 80. Можете проверить, нет ли конфликта с другими сервисами
(например, Apache, Nginx), которые слушают 80 порт на хосте.
4. По желанию, попробуйте зайти в браузере на [Link] и увидеть
страницу, отдаваемую контейнером (если ничего не мешает). Либо вы можете
проверить с помощью curl [Link] или curl [Link].

Что сдать в качестве ответа?

Список команд, которые вы использовали, чтобы:


Запустить контейнер в сети host
Проверить доступ к приложению (через curl или браузер)

Подсказка (раскрывать при необходимости)


Используйте флаг --network=host в команде docker run.
Порт -p при этом не нужен. Контейнер слушает тот же интерфейс, что и хост.
На Linux можно посмотреть, какой процесс занял 80 порт, через sudo lsof -i
:80 или netstat -tulpn.

Итог: Вы оцените, как «host network» позволяет контейнеру работать без NAT, а
также увидите риск конфликтов портов и сниженную изоляцию.

1/1
4.2 Практика

Запускаем контейнер в сети none


Цель: Разобраться, как работает сеть none в Docker, когда контейнер не получает
никакого сетевого интерфейса, кроме lo, и не может взаимодействовать с внешним
миром.

Сценарий задания

1. Запустите любой контейнер (например, alpine) с опцией --network=none.


2. Убедитесь, что у контейнера нет внешнего IP, и он не может подключаться к
интернету (попробуйте ping или curl чего-нибудь и проверьте, что оно не
работает).
3. Посмотрите вывод docker inspect, найдите там, что у контейнера нет данных
о сети (кроме lo).

Что сдать в качестве ответа?


Список команд, которые вы использовали, чтобы:
Запустить контейнер в сети none
Убедиться, что контейнер не имеет доступа к сети (например, ping или
curl)
Проверить информацию о сети через docker inspect

Подсказка (раскрывать при необходимости)


Используйте флаг --network=none при запуске контейнера: docker run -it --
network=none ....
Внутри контейнера попробуйте ping [Link] или curl любой адрес, чтобы
увидеть отсутствие сети.
docker inspect <container> покажет, что у контейнера нет обычного IP в
NetworkSettings.

Итог: Вы убедитесь, что режим none полностью изолирует контейнер от внешней


сети, оставляя лишь интерфейс lo внутри контейнера.

1/1
4.2 Практика

Создаем две пользовательские сети и проверяем изоляцию и


взаимодействие
Цель: Научиться создавать несколько пользовательских (user-defined) сетей (bridge)
в Docker и понимать, как изоляция работает, когда разные контейнеры подключены к
разным сетям.

Сценарий задания
1. Создайте две пользовательские сети с драйвером bridge. Назовем их
front_net и back_net.
2. Запустите три контейнера:
frontend: подключен только к front_net
db: подключен только к back_net
api: подключен к обеим сетям (front_net и back_net)
3. Убедитесь, что:
frontend не может напрямую видеть db (так как они в разных сетях).
frontend может «достучаться» (ping или curl) до api, так как оба в
front_net.
db может достучаться до api, так как оба в back_net.

Что сдать в качестве ответа?

Список команд, которыми вы:


Создали сети front_net и back_net
Запустили три контейнера с нужными сетями (frontend, db, api)
Проверили (ping, curl, etc.) соединение или его отсутствие между ними

Подсказка (раскрывать при необходимости)


Сети создаются командами вроде docker network create --driver bridge
front_net.
При запуске контейнеров:
frontend — укажите --network front_net
db — укажите --network back_net
api — можно либо запустить с --network front_net, потом docker
network connect back_net api, или использовать «подключение» сразу
после запуска (если у вас подходящий вариант).
Протестировать доступ можно командой docker exec, заходя в контейнер и
пытаясь «ping backend» или «curl» что-то.

Итог: Вы узнаете, как использовать несколько сетей для разделения доступа, и как
один «пограничный» контейнер (api) может участвовать в обеих сетях, общаясь и со
frontend, и с db.

1/1
4.2 Практика

Сравниваем доступ к контейнеру при дефолтном bridge с


портом и при user-defined сети без публикации
Цель: Понять, почему один контейнер может быть доступен с хоста через -p
(дефолтная bridge-сеть), а другой остаётся изолированным в пользовательской сети
без публичного порта.

Сценарий задания
1. Запустите контейнер publicweb, используя дефолтный bridge и параметр -p
8080:80.
Убедитесь, что curl [Link] (или браузер) возвращает
страницу Nginx.
2. Создайте пользовательскую сеть, назовите её isolated_net. Затем запустите
контейнер hiddenweb в этой сети, но без публикации порта
3. Попробуйте зайти [Link] или :8080 — вы не увидите ответ
hiddenweb, так как он в отдельной сети без публикации порта.
4. Дополнительно (по желанию): убедитесь, что publicweb и hiddenweb не
видят друг друга по DNS-имени (ведь они в разных сетях). Можете проверить
через docker exec, попытавшись «ping hiddenweb» из publicweb или наоборот
(вероятнее всего, не получится).

Что сдать в качестве ответа?

Список команд, которыми вы:


Запустили publicweb (с дефолтной bridge-сетью и -p 8080:80).
Создали isolated_net (user-defined bridge).
Запустили hiddenweb (без публикации порта).
(Опционально) Проверили доступ и отсутствие доступа к контейнерам.

Подсказка (раскрывать при необходимости)


docker run -d --name publicweb -p 8080:80 nginx — дефолтная сеть, порт
8080 проброшен наружу.
docker network create --driver bridge isolated_net — создаем
пользовательскую сеть.
docker run -d --name hiddenweb --network isolated_net nginx — не
публикуем порт, контейнер не виден извне.
Проверить «curl localhost:8080» для первого контейнера и отсутствие доступа
для второго.

Итог: Вы увидите, как -p при дефолтном bridge делает контейнер доступным с


хоста, а user-defined сеть без публикации порта не даёт наружного доступа, пока
вы не пробросите порт или не смените сеть.

1/1
4.2 Практика

Подключаем уже запущенный контейнер ко второй сети


Цель: Увидеть, как контейнер может динамически присоединяться к
дополнительной сети после старта. Это полезно, когда вы хотите добавить
контейнер к новой сети без перезапуска.

Сценарий задания

1. Запустите контейнер myapp с указанием одной сети (допустим, app_net).


Можете либо создать app_net заранее, либо воспользоваться дефолтной
сетью — но пусть это будет одна сеть.
2. Убедитесь, что контейнер работает (например, nginx или alpine с «sleep»).
3. Создайте еще одну user-defined сеть, назовите ее, скажем, extra_net.
4. С помощью docker network connect «присоедините» контейнер myapp к
extra_net. Проверьте, что теперь в выводе docker inspect myapp видно
«Networks: app_net, extra_net» (или аналогично).
5. По желанию, можете запустить еще один контейнер в extra_net и проверить,
что теперь оба контейнера видят друг друга по DNS-именам (только после
того, как myapp подключили).

Что сдать в качестве ответа?

Список команд, которыми вы:


Запустили контейнер myapp (с одной сетью)
Создали вторую сеть extra_net
Подключили myapp к extra_net (через docker network connect)
Опционально, проверили связь между контейнерами (если запустили
второй контейнер)

Подсказка (раскрывать при необходимости)


Сеть можно создать командой docker network create --driver bridge
extra_net.
Команда docker network connect extra_net myapp — ключ к добавлению
контейнера к сети.
Посмотрите docker inspect myapp до и после «подключения».

Итог: Вы научитесь подключать уже запущенный контейнер к новой сети, не


перезапуская его.

1/1
4.2 Практика

Исследуем разные типы сетей (host, none, user-defined bridge)


Цель: В одном задании попробовать несколько типов сетей Docker: host, none, а
также user-defined bridge. Посмотреть, как это влияет на доступ к контейнерам,
общение между ними и изоляцию.

Сценарий задания
1. Host network:
Запустите контейнер myhost с использованием --network=host (только на
Linux работает полноценно). Например, возьмите nginx или httpd,
который слушает 80 порт.
Убедитесь, что сервис внутри контейнера совпадает с портом хоста (если
внутри контейнера 80, то снаружи тоже 80).
Проверьте, что curl [Link] (или браузер) открывает страницу,
отдаваемую контейнером (если порт 80 не занят чем-то другим).
2. None network:
Запустите контейнер mynone с --network=none. Например, alpine с sleep.
Убедитесь, что нет доступа к сети внутри контейнера (попробуйте ping
или curl — не работает), и что на docker inspect видите нет IP-адреса
в NetworkSettings, кроме lo.
3. User-defined bridge:
Создайте сеть (например, mybridge): docker network create --driver
bridge mybridge.
Запустите контейнер mybridge1 (любой образ, например nginx или
alpine) с флагом --network mybridge.
При желании, запустите второй контейнер mybridge2 в той же сети и
убедитесь, что mybridge1 может пинговать или подключиться к mybridge2
(DNS-имя mybridge2), если у него есть нужный инструмент (e.g. ping).
4. Наблюдения:
С контейнером myhost (host network) — не нужно -p, порты совпадают с
хостом.
С контейнером mynone — полная изоляция, он не имеет IP и не виден
снаружи.
С контейнерами в mybridge — они видят друг друга по DNS (если в одной
сети), но извне доступ не откроется, пока не используете -p (если нужно).

Что сдать в качестве ответа?

1/2
Список команд, которые вы использовали, чтобы:
Запустить myhost (--network=host) и протестировать его доступ с хоста
Запустить mynone (--network=none) и проверить отсутствие сети
Создать сеть mybridge и запустить контейнер(ы) mybridgeX в этой сети (и
при желании проверить взаимодействие между ними)

Подсказка (раскрывать при необходимости)


docker run --network=host + никакого -p. Если порт 80 внутри контейнера не
занят и свободен на хосте, curl [Link] должен работать.
docker run --network=none — внутри контейнера нет IP, не работает ping
[Link] или ping [Link].
docker network create --driver bridge mybridge — новая сеть, контейнеры
в ней видят друг друга по DNS. Можете проверить через docker exec «ping
mybridge2».

Итог: Вы одновременно сравните, как работает host, none и user-defined bridge


сети в Docker, и лучше поймете их различия.

2/2
4.3 DNS

Один из удобных моментов Docker — контейнеры могут обращаться друг к другу по


имени (например, db или redis) вместо IP. Для этого Docker поднимает собственный
DNS-механизм. В этом уроке разберём, как Docker DNS работает, когда оно
включается и почему оно зависит от типа сети (особенно user-defined network).

1. Базовая идея: внутренний Docker DNS

При создании user-defined bridge или overlay сети Docker автоматически


поднимает «внутренний DNS-сервер».
Контейнеры, запущенные в этой сети, получают особую /etc/[Link], где
в списке DNS указывается адрес Docker DNS.
Когда внутри контейнера вы делаете ping db или ваше приложение стучится к
[Link] запрос идёт к этому Docker DNS, который знает IP
контейнеров по именам.

Итог: контейнер db может быть достигнут по имени db или [Link]-net, без ручного
прописывания IP.

2. Default «bridge» vs. user-defined «bridge»

Важная разница:

Default bridge (docker0): По умолчанию, если вы не укажете --network при


docker run, контейнеры не могут обращаться друг к другу по именам (только
по IP). Docker не поднимает DNS в default bridge.
User-defined bridge: Если вы сделаете docker network create mynet и
запустите контейнеры с --network mynet, тогда Docker DNS будет работать.
Контейнеры могут обращаться друг к другу по имени --name.

Вывод: Для простого сетевого взаимодействия (по имени) лучше всегда создавать
user-defined bridge (или overlay). Default «bridge» весьма ограничен.

3. Что происходит внутри контейнера

При запуске Docker прописывает /etc/[Link] в контейнере, указывая


адрес Docker DNS-сервера (например, [Link]) или другой, в зависимости
от настроек.

1/3
Любые DNS-запросы (ping, curl по hostname) идут к этому DNS, который
решает: db → [Link], web → [Link] и т. д.

Важно: если вы вручную монтируете /etc/[Link], можете сломать этот


механизм. Оставляйте Docker управлять им.

4. Как Docker DNS узнаёт IP контейнеров

Docker демон хранит информацию о контейнерах и их сетевых интерфейсах.


Когда вы даёте контейнеру имя mydb, он регистрирует mydb → 172.20.0.X в
DNS на этой user-defined сети.
Если вы меняете имя контейнера (или останавливаете его), Docker обновляет
внутреннюю «DNS-базу».

5. Aliases, service discovery

Кроме --name, можно задавать --network-alias при docker run, чтобы контейнер
имел несколько DNS-имен. В [Link] (в service’ах) есть aliases и т. д.

Если вы делаете docker run --network mynet --network-alias db2 ...,


контейнер ответит как по имени «mycontainer», так и «db2».

6. Overlay network (Swarm) DNS

В Docker Swarm (или Kubernetes) похожий принцип: сервисы регистрируются


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

Суть та же: Docker (или оркестратор) хранит таблицу сервис → IP, и встроенный
DNS отвечает на запросы контейнеров.

7. Основные советы

Старайтесь использовать user-defined сети, если нужно динамическое


взаимодействие контейнеров: docker network create mynet, docker run --
network mynet --name db, docker run --network mynet --name web.
В Dockerfile, вы не прописываете DNS, это происходит при docker run.
Не перенастраивайте /etc/[Link] внутри контейнера вручную. Дайте
Docker управлять.

8. Как проверить DNS внутри контейнера

1. Запустить контейнер с user-defined network, например: docker run -it --


network mynet --name test alpine sh.
2. ping другое имя: ping db (если есть контейнер db).
3. cat /etc/[Link] — увидите строчку nameserver [Link], что значит
Docker DNS.

2/3
Итог
Система DNS в Docker встроена в daemon. Контейнеры в user-defined bridge или
overlay сети получают автоматический DNS, разрешая имена контейнеров/сервисов.
Это упрощает service discovery и избавляет от ручного прописывания IP. В итоге,
db, redis, web и другие контейнеры обращаются по имени, а Docker сам под капотом
переназначает IP при перезапусках. Понимание этой механики поможет вам легче
связывать контейнеры в микросервисах и избежать проблем с DNS.

3/3
4.4 Практика

Проверяем работу DNS в user-defined сети vs. default bridge


Цель: Убедиться, что в user-defined bridge сети Docker поднимает собственный
DNS, и контейнеры видят друг друга по имени, тогда как в default bridge — нет.

Сценарий задания
1. Контейнеры в default bridge (сеть по умолчанию, когда не указываем --
network):
Запустите два контейнера, дайте им имена (например, def1 и def2), но не
используйте флаг --network.
Попробуйте обратиться из одного контейнера к другому по имени
(например, def2), посмотрите результат.
2. Создаём user-defined bridge:
Создайте новую сеть (например, mydnsnet) с драйвером bridge.
Запустите два контейнера (например, dns1 и dns2), указав, что они
входят в эту сеть (через --network).
Попробуйте обратиться из одного контейнера к другому по имени
(например, dns2), проверьте, что теперь DNS работает.
Посмотрите, что внутри контейнера может быть /etc/[Link] с
nameserver [Link].

Что сдать в качестве ответа?

Список команд, которыми вы:


Запустили два контейнера без явной сети (default bridge)
Создали user-defined сеть (например, mydnsnet)
Запустили контейнеры (dns1, dns2) в этой сети
(Опционально) Проверили разницу (ping/проверка /etc/[Link]) между
default bridge и user-defined

Подсказка (раскрывать при необходимости)


При запуске контейнера без --network — он входит в default bridge. По именам
чаще всего не ответит, только по IP.
При создании новой сети (например, mydnsnet) и запуске там контейнеров
Docker DNS начинает работать, разрешая имена в IP.
Проверить можно внутри контейнера (например, через ping dns2), а также
взглянуть на /etc/[Link].

Итог: Вы убедитесь, что default bridge не предоставляет внутренний DNS, а user-


defined сеть даёт автоматическое разрешение имён через встроенный Docker DNS,
упрощая связь контейнеров.

1/1
9 апреля 2025 г.

4.4 Практика

Используем network-alias, чтобы контейнер отвечал на


несколько имён
Цель: Понять, как с помощью --network-alias один контейнер может иметь
несколько DNS-имён в user-defined сети, и проверить, что эти имена действительно
работают.

Сценарий задания
1. Создайте user-defined сеть (например, aliasnet).
2. Запустите контейнер (назовите его, например, app) с двумя разными alias:
Основное имя (через --name app).
Альтернативный alias (через --network-alias alt1).
Можно добавить ещё один (через --network-alias alt2), если хотите
3. Запустите второй контейнер (в той же сети aliasnet), назовите его tester.
Внутри него попробуйте «постучаться» к первому контейнеру по разным
именам (app, alt1, alt2), чтобы убедиться, что Docker DNS резолвит все
варианты.

Что сдать в качестве ответа?

Список команд, которыми вы:


Создали сеть (aliasnet)
Запустили контейнер (app) с несколькими alias
Запустили контейнер (tester) и проверили связь (ping, curl или что-то
подобное) по разным именам

Подсказка (раскрывать при необходимости)


docker network create --driver bridge aliasnet — создаёт сеть.
docker run --network aliasnet --name app --network-alias alt1 --
network-alias alt2 ... — позволяет указать несколько alias.
Запустите tester в той же сети, затем docker exec tester ping alt1 и ping
app — оба должны резолвиться в IP первого контейнера.

Итог: Вы увидите, как --network-alias даёт несколько DNS-имен для одного


контейнера, упрощая гибкость при обращении к сервису.

1/1
4.4 Практика

Проверяем Docker DNS при обращении к внешним именам и к


локальному контейнеру
Цель: Увидеть, что Docker DNS может разрешать внутренние имена (контейнеры в
одной user-defined сети), а внешние домены (например, [Link]) передаются на
системные DNS сервера хоста. Таким образом, запросы к db (внутреннее имя)
работают через Docker DNS, а запросы к [Link] (внешний мир) — через
обычный DNS хоста.

Сценарий задания
1. Создайте user-defined сеть, например fallbacknet.
2. Запустите контейнер с именем db (или backend) в этой сети (используйте
alpine с sleep, nginx или любой другой образ). Назовите контейнер, чтобы --
name db.
3. Запустите второй контейнер tester в сети fallbacknet, чтобы из него можно
было проверить обращение как к db, так и к внешнему домену (например,
[Link]).
4. В контейнере tester проверьте:
ping db или ping backend — запрос разрешается внутренним Docker
DNS.
ping [Link] (или curl [Link]), видите, что это уходит во
внешний мир. Docker DNS «фолбэчит» на системный DNS хоста.

Что сдать в качестве ответа?

Список команд, которыми вы:


Создали сеть fallbacknet
Запустили контейнер db (или «backend») в ней
Запустили контейнер tester в ней
(Опционально) Проверили ping db и curl [Link] (или ping), увидев,
что оба разрешаются, но разными способами

Подсказка (раскрывать при необходимости)


docker network create --driver bridge fallbacknet
docker run -d --name db --network fallbacknet [образ]
docker run -it --name tester --network fallbacknet [образ] sh
Внутри tester: ping db, ping [Link] или curl [Link]. DNS резолвит
внутреннее имя db (через [Link]), а внешние домены передаются
системному DNS.

1/2
Итог: Вы увидите, что Docker DNS знает внутренние имена контейнеров в сети, а
для обычных интернет-доменов прокидывает запрос к DNS хоста, делая работу
контейнеров прозрачной как для локальных, так и для внешних адресов.

2/2
10 апреля 2025 г.

4.4 Практика

Проверяем обновление DNS при переименовании и


пересоздании контейнеров
Цель: Убедиться, что когда контейнеры в user-defined сети удаляются и создаются
снова (но с теми же именами или alias), Docker DNS обновляет записи — и другие
контейнеры начинают видеть новый IP.

Сценарий задания
1. Создайте user-defined сеть, назовите её testdns.
2. Запустите контейнер (назовите его db) в сети testdns. Можно взять alpine
или любой другой образ (чтобы он жил, используйте sleep или tail -f
/dev/null).
3. Запустите контейнер tester в той же сети testdns. Изнутри tester —
проверьте, что ping db действительно резолвится и отвечает.
4. Остановите и удалите контейнер db, а затем запустите другой контейнер
(другой образ или тот же), но с тем же именем db в сети testdns.
Теперь IP-адрес у нового db может отличаться.
5. Всё ещё находясь в tester, опять попробуйте ping db. Убедитесь, что DNS
«подхватил» новый контейнер. Вы должны увидеть, что запросы идут уже на
новый IP — а не «зависают» на старом.

Что сдать в качестве ответа?

Список команд, которыми вы:


Создали сеть testdns
Запустили контейнер db (первый), запустили контейнер tester
Удалили контейнер db и запустили новый контейнер db (замену)
Проверили в tester, что ping db теперь резолвится на новый IP

Подсказка (раскрывать при необходимости)


docker network create --driver bridge testdns
docker run -d --name db --network testdns alpine sleep 9999
docker run -it --name tester --network testdns alpine sh
Внутри tester: ping db — видим IP (например, [Link]).
Снаружи: docker rm -f db, потом docker run -d --name db --network
testdns ubuntu tail -f /dev/null, IP может стать другим (например,
[Link]).
Внутри tester: ping db снова, видим обновленный IP.

Итог: Вы на практике проверите, что Docker DNS динамически обновляет запись,


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

1/2
2/2
4.4 Практика

Множественные сети, алиасы и динамическое


переподключение
Цель: В одном задании отработать навыки:

Создания нескольких user-defined сетей (frontend_net и backend_net)


Подключения контейнера к нескольким сетям (одновременно или
динамически)
Использования network-alias для одного контейнера, чтобы к нему можно было
обращаться по разным именам
Проверки того, что при удалении/перезапуске контейнера DNS обновляется —
в итоге новый контейнер с тем же alias или именем отвечал по тому же DNS-
имени

Сценарий задания
1. Создайте две user-defined сети: frontend_net и backend_net.
2. Запустите три контейнера (в любом порядке):
frontend — подключите к frontend_net и дайте ему alias web (через --
network-alias).
backend — подключите к backend_net, alias api (или другое название).
Возможно, вы захотите подключить этот контейнер и к frontend_net
(если ему надо общаться с frontend), но можно сделать это
динамически (см. пункт о docker network connect).
db — подключите (сначала) к backend_net и дайте alias database.
Если образам нужно жить в фоне, используйте tail -f /dev/null или sleep
в Alpine.

3. Проверьте DNS-обращения:
В frontend (или web alias) попробуйте пинг/обращение к api, если
backend оказался в той же сети frontend_net.
Если нет — используйте docker network connect для backend и
frontend_net.
В backend (или api) попробуйте пинг/обращение к database (alias для db)
— так как они оба в backend_net.
4. Удалите и пересоздайте один из контейнеров (скажем, db), сохранив прежний
alias (database). Убедитесь, что другие контейнеры теперь видят
новый контейнер под тем же DNS-именем database — Docker DNS
обновляется.
5. (Дополнительно, если хотите) добавьте ещё один alias к одному из
контейнеров (например, backend пусть ответит и на oldapi, и на api), и
проверьте, что обращение по обоим именам срабатывает.

1/2
Что сдать в качестве ответа?

Список команд:
Создание сетей frontend_net и backend_net
Запуск frontend, backend, db (каждого с нужными --network / --network-
alias)
Любые docker network connect (если вы динамически подключаете
контейнер ко второй сети)
Проверка (ping или curl) между контейнерами по алиасам
Удаление и пересоздание одного контейнера (с тем же alias),
подтверждая, что DNS обновился
Короткое описание, как всё в итоге работает: frontend пингует api,
backend пингует database и т. д., а после пересоздания контейнера всё ещё
работает по тому же DNS-имени.

Подсказка (раскрывать при необходимости)


docker network create --driver bridge frontend_net, docker network
create --driver bridge backend_net
docker run -d --name frontend --network frontend_net --network-alias
web [образ]
docker run -d --name backend --network backend_net --network-alias api
[образ]
docker run -d --name db --network backend_net --network-alias database
[образ]
docker network connect frontend_net backend (если нужно, чтобы «frontend»
и «backend» могли общаться в одной сети)
Зайдите в «frontend» (docker exec) и «ping api», или зайдите в «backend» и
«ping database»
Удалите container db, создайте новый с тем же alias, проверьте, что «backend»
всё ещё видит его по «database»

Итог: Если всё сделано, вы убедитесь, что Docker DNS позволяет гибко связывать
контейнеры разными именами (alias), подключать контейнеры к нескольким сетям, и
автоматически обновляет IP, если контейнер удаляется и создаётся заново под тем
же именем (или alias).

2/2
4.5 Траблшутинг сетей

Когда контейнеры не могут увидеть друг друга, или сеть ведёт себя странно, нужно
уметь быстро проверить, где сбой. В этом уроке разберём три полезных
инструмента: ping, nslookup, и docker network inspect. С их помощью вы сможете
разобраться, есть ли у контейнеров сеть вообще, как имена резолвятся в IP, и какие
порты доступны.

1. ping
Что проверяет? Достучится ли контейнер A до контейнера B по IP или hostname, а
также проверяет задержку (latency). Это самое базовое сетевое средство.

docker exec -it mycontainer sh — заходим в контейнер, затем:

ping db
ping [Link]

Если «db» отвечает, значит DNS внутри сети работает, и трафик идёт.
На некоторых минималистичных образах (Alpine, BusyBox) ping может
отсутствовать по умолчанию. При необходимости можно «apk add iputils» или
apt-get install iputils-ping в контейнере.

Ограничения: ping не всегда работает в host сети или firewall может блокировать
ICMP. Но для user-defined bridge обычно полезен.

2. nslookup (или dig)

Что проверяет? Как Docker DNS (или системный DNS) резолвит имя в IP. Если db
не резолвится, приложение не увидит db.

Можно docker exec внутрь контейнера и сделать:

nslookup db

или

dig db

(в зависимости от образа).
Результат покажет IP-адрес, если Docker DNS знает контейнер db.

1/3
Если ошибка server can’t find db — значит DNS не в курсе этого имени. Может,
вы не используете user-defined network? Или контейнер db вышел из строя?

3. docker network inspect

Что проверяет? Детальные сведения о сети Docker: какие контейнеры


подключены, IP-адреса, настройки subnet/gateway и т. д.

docker network ls — посмотреть все сети.


docker network inspect mynetwork — покажет JSON с информацией:

{
"Name": "mynetwork",
"Id": "...",
"IPAM": { "Config": [ { "Subnet": "[Link]/16" } ] },
"Containers": {
"container_id": {
"Name": "mycontainer",
"IPv4Address": "[Link]/16",
...
}
}
}

Вы увидите, какие контейнеры в сети, их IP, gateway и т. д.

Полезно, если unsure, на какой IP Docker поместил контейнер, и действительно ли


ваш контейнер подключён к нужной сети.

Пример сценария диагностики

1. docker network inspect mynet — убедиться, что db и app подключены к mynet,


и что у них есть IP-адреса.
2. docker exec -it app sh — заходим в контейнер app, делаем ping db или
nslookup db.
Если ping db выдает ошибку Name not resolved или Destination Host
Unreachable, значит либо DNS не видит db, либо firewall.
Если ping есть ответ, значит сетевой маршрут есть, IP/коннект ok.
3. (Если нужно) apt-get install iputils-ping dnsutils или apk add iputils bind-
tools в контейнере, если ping/nslookup отсутствуют.

Дополнительные инструменты

telnet или curl: проверить порт (e.g. curl db:5432 — получится ошибка, но
если соединение установилось, значит порт открыт).
traceroute / tracepath: для более глубокого пути пакетов, обычно реже нужно в
локальном Docker-контейнере.

Почему это важно?

2/3
При микросервисной архитектуре, где много контейнеров общаются через user-
defined bridge (или overlay), когда что-то «не пингуется» или «не резолвится», эти
команды быстро помогают локализовать проблему:

docker network inspect → контейнер правда ли в сети?


ping / nslookup → DNS-резолв и reachability, внутри контейнера.
После этого, вы понимаете: проблема в DNS, в firewall, в отсутствии сети, или
контейнер вовсе не живой.

Итог
ping проверяет маршрут и отклик, nslookup (или dig) проверяет DNS-резолв, а
docker network inspect даёт полную картину сети (IP-адреса, subnet, какие
контейнеры подключены). Сочетая их, вы сможете быстро диагностировать сетевые
проблемы в Docker.

3/3
4.6 Практика

Диагностика сетевых проблем с помощью ping, nslookup и


docker network inspect
Цель: Отработать базовые действия по диагностике сети внутри Docker-
контейнеров: проверку пинга, DNS-разрешения, а также осмотр информации о сети
через docker network inspect.

Сценарий задания
1. Создайте user-defined сеть, назовём её, например, diag_net.
2. Запустите два контейнера в этой сети (например, mydb и tester). Позаботьтесь
о том, чтобы в tester были доступные инструменты для диагностики (либо
установите их).
3. Используя доступные инструменты, проверьте:
docker network inspect для вашей сети, чтобы увидеть, какие
контейнеры подключены и под какими IP-адресами.
ping внутри контейнера tester в адрес mydb (по имени или IP) —
убедитесь, что пакеты доходят.
nslookup (или dig) для mydb внутри tester, чтобы удостовериться, что
Docker DNS корректно резолвит имя.

Что сдать в качестве ответа?

Список команд, которыми вы:


Создали сеть diag_net
Запустили контейнеры mydb и tester (оба в diag_net)
Проверили docker network inspect, ping, nslookup (или dig)

Подсказка (раскрывать при необходимости)


Создайте сеть с драйвером bridge, чтобы контейнеры могли обращаться друг
к другу по имени.
Запустите два контейнера в этой сети, например, один — mydb (любой образ с
длительным процессом), второй — tester (где есть ping, nslookup, либо
установите их).
docker network inspect <your_network> покажет JSON-вывод с IP-адресами
и списком контейнеров.
Зайдя в tester (через docker exec), можете протестировать ping mydb и
nslookup mydb (или dig mydb), убедившись, что имя резолвится и пингуется.

Итог: Вы научитесь использовать ping и nslookup (или dig) внутри контейнера для
проверки DNS и доступности, а также docker network inspect для просмотра
конфигурации сети и IP-адресов контейнеров.

1/1
4.6 Практика

Диагностика открытых портов через netcat (nc) и проверка


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

Сценарий задания
1. Создайте user-defined сеть (например, portcheck_net).
2. Запустите контейнер с простым приложением, которое слушает на каком-то
порту. Например:
[Link] или Python, включающее в себя HTTP-сервер,
или nc -lkp 8080 (netcat в режиме сервер),
или любой другой образ, где сервис висит на порту и пишет логи при
входящих соединениях.
Назовём контейнер server и подключим его к portcheck_net.
3. Запустите второй контейнер client в той же сети (portcheck_net), в котором
есть nc (netcat) или telnet. С его помощью попробуйте подключиться к порту
серверного контейнера по имени server (например, nc server 8080),
отправьте какие-то символы.
4. Убедитесь, что:
соединение устанавливается;
server контейнер что-то пишет в логи (например, входящее соединение,
полученные данные);
client может отправлять и получать ответ (зависит от того, как настроен
server).
5. (Опционально) Откройте ещё один порт server или используйте -p
HOSTPORT:CONTAINERPORT, чтобы проверить доступ с хоста. Но основная идея
— тест взаимодействия контейнер-контейнер по имени в user-defined сети.

Что сдать в качестве ответа?


Список команд, которыми вы:
Создали сеть portcheck_net
Запустили server контейнер (где-то слушает порт), подключили к
portcheck_net
Запустили client контейнер, оттуда подключились к server (через netcat
или telnet) по имени server
Посмотрели docker logs server, увидели сообщение или
зафиксированный трафик

Подсказка (раскрывать при необходимости)

1/2
Создайте сеть (драйвер bridge), запустите контейнер «server» с каким-нибудь
сервисом, который слушает на 8080 или 1234.
Например, nc -lkp 8080 (в Alpine нужно apk add busybox-extras или apk
add netcat-openbsd).
Запустите «client» с nc (или telnet) установленным, подключитесь командой
nc server 8080.
Посмотрите логи «server» (docker logs server): если сервис что-то выводит
при подключении, вы увидите это.

Итог: Вы убедитесь, что в user-defined сети контейнеры могут подключаться к


сервисам по имени + порту, и в логах server будет видно, что пришло соединение.

2/2
14 апреля 2025 г.

4.6 Практика

Настройка простого Nginx reverse proxy для другого


контейнера в user-defined сети
Цель: Освоить связку reverse proxy (nginx) → backend в одной пользовательской
сети. При этом проверить, что Nginx действительно проксирует запросы к backend-
контейнеру по внутреннему имени (через Docker DNS), а снаружи доступ идёт
только к самому nginx.

Сценарий задания

1. Создайте user-defined сеть (например, proxy_net).


2. Запустите контейнер backend (в любом образе, где можно запустить простой
HTTP-сервис). К примеру:
python:3 с командой python -m [Link] 5000,
или node:alpine с вашим скриптом на 5000 порту,
или alpine с apk add busybox-extras и запустить httpd.
Подключите backend к сети proxy_net. Убедитесь, что он слушает, скажем,
5000.
3. Подготовьте Nginx-контейнер (назовите его proxy), тоже в сети proxy_net.
Настройте (через конфигурацию Nginx) проксирование:
Nginx слушает на 80 (внутри контейнера),
Все запросы проксируются на [Link] (используя DNS-имя
backend, разрешённое Docker DNS).
Для этого можно либо скопировать кастомный [Link] внутрь контейнера,
либо монтировать его. Главное, чтобы в конфиге было что-то вроде:

location / {
proxy_pass [Link]
}

4. Опубликуйте порт 80 Nginx-контейнера наружу (например, -p 8080:80),


чтобы вы могли заходить на [Link] и получить ответ от
backend.
5. Проверьте:
Если зайти в браузере (или curl) по [Link] запрос
попадёт в proxy, который в свою очередь обращается к backend:5000 по
Docker DNS.
Можно зайти внутрь proxy (через docker exec) и сделать ping backend
или curl backend:5000, убедившись, что внутренний DNS имя backend
резолвит.

Что сдать в качестве ответа?

1/2
Список команд, которыми вы:
Создали сеть proxy_net
Запустили backend, настроив его на порт (скажем, 5000)
Подготовили proxy (Nginx), подключили к proxy_net, пробросили порт -p
8080:80 наружу
Убедились, что [Link] отдаёт контент от backend

Подсказка (раскрывать при необходимости)


Сеть создаётся (драйвер bridge) — backend и proxy должны быть в ней.
backend слушает 5000, в [Link] надо прописать:

location / {
proxy_pass [Link]
}

При запуске Nginx-контейнера пробросьте порт -p 8080:80, чтобы снаружи


зайти.
По адресу [Link] вы должны увидеть тот же отклик, что и
если обратиться к backend напрямую.

Итог: Вы настроите reverse proxy внутри Docker-сети, убедитесь, что Nginx ходит по
имени backend (через DNS), и всё работает прозрачно для внешнего пользователя
на localhost:8080.

2/2
14 апреля 2025 г.

4.6 Практика

Динамическое подключение и отключение контейнеров в двух


сетях с проверкой DNS
Цель: Попробовать свои силы в создании сразу двух пользовательских сетей,
подключении контейнеров к обеим (или одной) из них, динамическом добавлении и
удалении временных (ephemeral) контейнеров, и проверке, как Docker DNS
реагирует на эти изменения.

Сценарий задания
1. Создайте две сети (например, front_net и back_net), обе user-defined (driver
= bridge).
2. Запустите три контейнера:
frontend в front_net, дайте ему alias web (через --network-alias),
backend в back_net, alias api,
joiner, который должен быть мостом: подключен и к front_net, и к
back_net. Чтобы это сделать:
Запустите joiner сначала в одной сети,
потом добавьте к другой сети через docker network connect.
Этот контейнер (joiner) может быть простым alpine с sleep — главное,
чтобы он числился в обеих сетях.
3. Проверьте через joiner (зайдя docker exec внутрь):
ping web (из front_net, alias web) — должно работать, ведь joiner и
frontend в одной сети.
ping api — должно работать в back_net, так как joiner и backend в
одной сети.
4. Добавьте временный контейнер (например, ephemeral) в back_net с alias
temp. Из joiner сделайте ping temp, убедитесь, что DNS резолвит новое имя.
Теперь удалите ephemeral (docker rm -f ephemeral).
Снова запустите ephemeral (другой образ или тот же) с alias temp в
back_net. Проверьте, что joiner видит новый IP.
5. По желанию: если хотите, подключите frontend тоже к back_net, чтобы
проверить, как несколько сетей у одного контейнера сочетаются, и проверяйте
DNS-имена с разных сторон. Но это опционально.

Что сдать в качестве ответа?

1/2
Список команд, которыми вы:
Создали front_net и back_net
Запустили frontend (alias web), backend (alias api), joiner (добавив
вторую сеть через docker network connect)
Добавили ephemeral (alias temp) в back_net, протестировали доступ,
удалили, пересоздали — увидели новый IP
Проверяли DNS через ping или nslookup внутри joiner (и, при желании,
в frontend/backend)

Подсказка (раскрывать при необходимости)


Создайте сети (driver=bridge) — docker network create ….
frontend — --network front_net --network-alias web.
backend — --network back_net --network-alias api.
joiner — запустите в одной сети, затем docker network connect ко второй.
В joiner (через docker exec), проверьте ping web, ping api.
ephemeral — --network back_net --network-alias temp. Удалите,
пересоздайте, проверьте «ping temp» из joiner.и.

2/2
4.7 Частые ошибки

На практике при запуске контейнеров часто возникают разнообразные ошибки,


связанные с доступом по портам, конфликтами между приложениями, настройками
firewall и пр. Ниже разберём наиболее распространённые «грабли» и способы их
обхода.

1. Конфликты портов

Симптом: Could not bind to port 80, или Address already in use при
запуске docker run -p 80:80.
Причина: На хост-машине уже кто-то слушает этот порт (Nginx, Apache, другой
контейнер).
Решение:
1. Освободить порт: остановить процесс, который его занимает.
2. Или выбрать другой порт хоста, например -p 8080:80, если 80 уже занят.
3. Проверить, нет ли уже запущенных контейнеров, которые занимают этот
порт.

2. Непробиваемый firewall

Симптом: Запустили контейнер с -p 8080:80, но с другой машины/сети


недоступно [Link]
Причина: Firewall (UFW, iptables, corporate firewall) может блокировать
входящие подключения на 8080.
Решение:
1. Настроить правила firewall, чтобы разрешить входящий трафик на
нужный порт (sudo ufw allow 8080 или iptables-аналог).
2. Убедиться, что докерная цепочка iptables не конфликтует с другими
правилами.
3. Если в «облаке» (AWS/GCP/Azure), проверьте настройки Security Group,
Inbound Rules и т. п.

3. Забыл указать -p

Симптом: Сервис внутри контейнера слушает 80, но вы не указали -p


8080:80. С хоста или другой машины нет доступа.
Причина: Без -p или --publish Docker по умолчанию не открывает порт
наружу.

1/3
Решение:
1. Запустить контейнер заново с -p (или -P для автопубликации).
2. Проверить, что сервис внутри контейнера слушает именно тот порт,
который вы мапите снаружи.

4. Сетевая изоляция - контейнер не может достучаться до интернета

Симптом: Контейнер не обновляет пакеты, curl [Link] не работает,


Temporary failure in name resolution.
Причина: Проблемы с DNS или заблокирован выход в интернет (firewall,
корпоративный прокси). Или внутренняя docker-сеть неправильно настроена.
Решение:
1. Проверить конфигурацию DNS Docker (/etc/docker/[Link] и т. д.).
2. Убедиться, что [Link] (Google DNS) или локальный DNS доступен.
3. Проверить iptables на хосте, прокси-настройки (HTTP_PROXY, etc.).

5. Мультихост-связь: контейнеры на разных хостах не видят друг


друга

Симптом: Мы подняли два контейнера на разных серверах, но они не


пингуются.
Причина: По умолчанию Docker предоставляет виртуальную сеть внутри
одного хоста. Между разными хостами нет сети (нужен либо VPN, либо Docker
Swarm overlay, либо Kubernetes).
Решение:
1. Либо использовать внешнюю сеть (VPN, ssh tunnel),
2. Либо настроить Docker Swarm и overlay-сеть,
3. Либо задеплоить оба контейнера на одном хосте или в Kubernetes-
кластере с общей сетью.

6. Сервис не слушает [Link] внутри контейнера

Симптом: Вы пробросили -p 8080:80, но приложение всё равно недоступно.


Причина: Приложение внутри контейнера слушает localhost ([Link]), а не
[Link]. Docker NAT не видит этот порт.
Решение:
1. Настроить приложение на слушать [Link] (или all interfaces) вместо
[Link].
2. Проверить docker exec <container> netstat -tulpn (или ss -lntp),
увидеть, какой IP слушает.

7. Операционные проблемы: /etc/hosts, SELinux, AppArmor

Симптом: Контейнер не может модифицировать что-то на хосте, Permission


denied, или проблемы с монтированием папок.

2/3
Причина: SELinux или AppArmor могут блокировать доступ к файлам, если не
настроены соответствующие профили. Также некоторые контейнеры пытаются
менять /etc/hosts, что Docker не разрешает по умолчанию.
Решение:
1. Проверить SELinux-режим (Permissive, Enforcing). Выполнить chcon -Rt
svirt_sandbox_file_t /path при bind mount.
2. AppArmor-профили настроить или отключить для конкретного
контейнера, если нужно.
3. Не пытаться напрямую менять /etc/hosts; используйте Docker DNS или
--add-host для дополнительных хостов.

Итог
Конфликты портов — следите, чтобы -p X:Y не пересекался с уже занятой
службой.
Firewall — откройте нужные порты, убедитесь, что внешние запросы не
блокируются.
Bind to [Link] — если приложение слушает только localhost, наружу его не
видно.
Сети на разных хостах — для межхостовых связей нужна либо Swarm
overlay, либо VPN, либо Kubernetes.
SELinux/AppArmor — могут мешать монтированию папок, проверяйте
контексты и профили.

3/3
4.8 Macvlan и Ipvlan

Помимо классической bridge-схемы (когда контейнеры получают IP из внутренней


сети Docker и идут наружу через NAT), Docker поддерживает более продвинутые
драйверы: macvlan и ipvlan. Они позволяют контейнеру получить прямой IP в той
же физической сети, что и хост, а также выглядеть для локальной сети как
отдельное устройство (MAC-адрес / IP-адрес).

1. Что такое Macvlan?

Macvlan — это тип сетевой виртуализации на уровне канала (L2). Контейнер


получает собственный виртуальный MAC-адрес и выглядит для внешнего
мира, будто это совсем другой физический узел.
Хост (и контейнер) при этом разделяют общий «физический» интерфейс
(например, eth0), но Docker создаёт дополнительный виртуальный интерфейс,
у которого своя MAC.
Со стороны роутера/свитча контейнер виден как отдельное устройство с
собственным MAC и IP-адресом, то есть его можно пинговать напрямую, если
он в той же подсети.

Плюсы:

Не нужен NAT, контейнер настоящим образом в вашей LAN.


Можно задавать IP-адреса из того же диапазона, что у хоста (например,
192.168.1.x).
Высокая производительность (меньше overhead, чем при NAT).

Минусы:

Хост, как правило, не может напрямую общаться с контейнером по этому


интерфейсу (есть нюанс, описан далее: bridge для macvlan). Для
маршрутизации хост → контейнер придётся делать workaround или
дополнительный проброс.
Нужны права на создание macvlan-интерфейсов (root), а также совместимые
настройки в сети (некоторые физические свитчи могут блокировать пакеты с
неожиданными MAC).

2. Как создать macvlan-сеть в Docker

1/3
1. Определите физический интерфейс хоста, с которым хотите
связать контейнеры, например eth0.
2. Узнайте подсеть, шлюз и т. д. (например, у вас LAN: [Link]/24, шлюз
[Link]).
3. Создайте сеть командой docker network create с драйвером macvlan:

docker network create -d macvlan \


--subnet=[Link]/24 \
--gateway=[Link] \
-o parent=eth0 \
my-macvlan

-o parent=eth0 указывает, к какому физическому интерфейсу цеплять


macvlan.
--subnet и --gateway — ваша реальная LAN.
4. Запустите контейнер:

docker run -d --network my-macvlan --name test1 \


--ip [Link] nginx

Контейнер test1 получит IP [Link]. Если вы пингуете его из другой


машины в сети, он ответит.
Но если вы пингуете его с того же хоста, где запущен Docker, может не
сработать напрямую (см. ниже).

3. Проблема «Хост не видит контейнер» и решение через macvlan-


bridge

По умолчанию macvlan изолирует трафик между хостом и контейнером.


Контейнер не может общаться с хостом по этому интерфейсу (и наоборот),
если нет bridge-связки.
Чтобы исправить, некоторые настраивают доп. macvlan-интерфейс на хосте
(macvlan bridge mode). Это позволяет маршрутизировать пакеты хост ↔
контейнер.
Пример (упрощённый):

# Создаём интерфейс macvlan0 (на хосте) в той же подсети:


ip link add macvlan0 link eth0 type macvlan mode bridge
ip addr add [Link]/24 dev macvlan0
ip link set macvlan0 up

# Теперь хост может общаться с контейнерами на macvlan, если route совпадают.

Важно: это уже не стандартный Docker, а ручная настройка. Именной macvlan


bridge mode — отдельная тема для продвинутых сетевых случаев.

4. Ipvlan: похожий механизм на L3

Ipvlan похож на macvlan, но работает на уровне L3 (IP), не создавая


виртуальных MAC-адресов.

2/3
Вместо того чтобы у контейнеров были собственные MAC, они делят MAC с
хостом, а различаются IP-адресом. Это упрощает ситуацию, когда свитчи не
любят дополнительные MAC-адреса.
Команда создания сети похожа, только -d ipvlan вместо -d macvlan. Опции те
же (parent, --subnet, --gateway).
Так же, как и с macvlan, есть нюанс: хост по умолчанию не сможет влезть в эту
же L3-сеть без ручного маршрута.

5. Когда использовать macvlan/ipvlan?

Нужно реальное присутствие в LAN: каждому контейнеру требуется


видимый IP, без NAT, чтобы другие устройства в сети видели их как
полноценные узлы.
Избегаем коллизий портов: при macvlan/ipvlan не нужно -p 80:80; контейнер
использует свой IP:80. Это может упростить настройки, если много сервисов
слушают 80-порт.
Высокая производительность: нет NAT overhead. Подходит для некоторых
сетевых сервисов, говорящих напрямую в LAN.

Не используйте, если:

У вас нет прав (root) или доступа на хосте, чтобы менять сетевые интерфейсы
/ iptables. Macvlan требует соответствующих прав.
Не хотите заморачиваться с маршрутизацией хост ↔ контейнер. Bridge-сеть
или host network проще.
Свитч или сетевые администраторы не любят появление новых MAC-адресов
(тогда ipvlan может помочь, но всё равно нужна продвинутая настройка).

6. Пример: когда macvlan удобен

У вас есть домашний сервер [Link] (host), вы хотите запускать внутри


него несколько контейнеров (Nextcloud, Home Assistant, etc.), каждому дать IP
типа [Link], [Link], чтобы заходить из локальной сети
напрямую: [Link] и т. д.
Не нужно думать об -p и портах. Каждый сервис слушает свой 80 (или другой),
без конфликтов.

3/3
5.1 Volumes и Bind Mounts

Когда контейнер работает, любые данные внутри него живут только до момента
удаления или пересоздания контейнера (Copy-on-Write слой). Если вы хотите
сохранить данные дольше, нужно использовать либо тома (volumes), которые
управляет Docker, либо bind mounts, которые напрямую привязаны к директории на
хосте. Рассмотрим эти подходы и их различия.

1. Проблема: данные пропадают при пересоздании контейнера


Предположим, вы запускаете базу данных в контейнере, и она хранит файлы в
/var/lib/mysql. Если просто запустить без дополнительных опций, и потом удалить
контейнер, все данные исчезнут вместе с ним. Чтобы это предотвратить, Docker
даёт механизм вынести данные наружу.

2. Volume vs Bind Mount

Volume (том) — это специальное место, управляемое Docker. Вы указываете


docker run -v myvolume:/path/in/container или используете docker volume
create, и Docker хранит данные где-то внутри /var/lib/docker/volumes (или
аналогично), но вы обычно не лезете туда напрямую.
Bind Mount — когда вы монтируете конкретную директорию/файл хостовой
машины в контейнер. Пример: docker run -v /home/user/data:/app/data.
Тогда, всё, что контейнер пишет в /app/data, фактически появляется (и
сохраняется) в /home/user/data на хосте.

3. Принципиальные отличия

Критерий Volume Bind Mount

Создание и Docker сам создаёт и хранит Вы вручную выбираете


управление (под капотом) в папку/файл на хосте (e.g.
/var/lib/docker/volumes /home/user/data),
(обычно), вы можете монтируете её в контейнер.
использовать docker volume Docker не управляет
create, docker volume ls, содержимым, просто
docker volume inspect и т. д. пробрасывает.

1/3
Критерий Volume Bind Mount

Прозрачность Чёрный ящик (если вы не Прямая привязка к


для копаетесь внутри конкретному пути на хост-
пользователя /var/lib/docker/volumes), машине. Полная
Docker сам решает, где прозрачность, но нужно
хранить. Удобно, если не аккуратно следить за
хотите жёстко привязываться к правами доступа и путями.
путям на хосте.

Переносимость Volumes проще переносить/ Bind mount жёстко завязан на


резервировать, так как Docker структуру файлов на хосте.
знает, что это том. Мигрировать При переносе контейнеров
на другой хост можно с на другую машину придётся
помощью docker volume воссоздавать ту же структуру
export и т. д. директорий.

Использование Можно указать VOLUME /path в В Dockerfile нельзя


в Dockerfile Dockerfile (хотя не всегда жёстко прописать bind mount.
рекомендуется), и тогда Docker Это на совести команды
автоматически создаёт том. docker run (или docker-
compose), где вы указываете
-v
/host/path:/container/path.

Безопасность Доступ контролируется через Прямой доступ к хост-


и права Docker. Можно использовать файлам. Нужно следить за
SELinux/AppArmor настройки. SELinux-контекстами,
Менее гибко, если хотите exact правами chmod, чтобы
контроль хостовых прав. контейнер мог писать/читать.

4. Примеры запуска

Volume

# 1) Создаём именованный том


docker volume create mydata

# 2) Запускаем контейнер, монтируя этот том:


docker run -d -v mydata:/var/lib/mysql mysql

Теперь MySQL будет хранить файлы в /var/lib/mysql внутри контейнера, а физически


Docker хранит в /var/lib/docker/volumes/mydata/_data (не лезьте туда руками, если
нет особой надобности).

Bind Mount

docker run -d -v /home/user/data:/app/data myimage

В контейнере всё, что записывается в /app/data, сразу появляется на хосте в


/home/user/data.

5. Плюсы/минусы

2/3
Volume:
+ Не завязаны на конкретный путь хоста, удобно переносить.
+ Docker командой docker volume ls показывает все тома, легко
управлять.
– Менее прозрачно, где именно лежат файлы, если вдруг нужно вручную
внести правки.
Bind Mount:
+ Полный контроль: прописали точный путь хоста, видим файлы,
редактируем в реальном времени.
– Менее переносимо, нужно помнить о путях, правах. На другой машине
папки могут отличаться.

6. Как выбрать?

Используйте volumes (именованные) если вам нужна абстракция от путей на


хосте. Например, база данных, которая должна просто сохраняться.
Используйте bind mounts при разработке (горячая замена кода), или когда
вам нужно точное соответствие папок (например, вы хотите, чтобы изменения
на хосте моментально отражались в контейнере).

Итог
Volume — управляемая Docker'ом область хранения. Отличная
переносимость, простое управление командами docker volume.
Bind Mount — монтируете конкретную папку/файл хоста в контейнер, полная
прозрачность.

3/3
5.2 Практика

Используем и именованный том, и bind mount для хранения


данных
Цель: Научиться различать, как работают volume и bind mount. Вы запустите два
контейнера, один — с именованным томом, другой — с папкой на хосте, и сравните,
где действительно хранятся файлы и как Docker управляет ими.

Сценарий задания
1. Создайте именованный том (например, myvol).
2. Запустите контейнер (назовём его container_vol), который будет
использовать этот том для хранения данных (например, пишите логи или
файлы в какую-то директорию внутри контейнера). Убедитесь, что данные
сохраняются в myvol.
3. Запустите второй контейнер (назовём его container_bind), который вместо
тома использует bind mount (например, монтируете локальную папку
/tmp/testbind в /app/data внутри контейнера). Убедитесь, что при записи в
/app/data внутри контейнера файлы оказываются на хосте в /tmp/testbind.
4. Сравните:
Как Docker отображает контейнер c volume (посмотрите docker volume
ls, docker volume inspect).
Где физически лежат данные при bind mount (вы сразу видите их на хосте
в /tmp/testbind), а не через docker volume inspect.
5. Попробуйте удалить контейнеры и посмотреть, что произойдёт с данными:
Данные из именованного тома (myvol) не исчезнут.
Файлы на хосте (в /tmp/testbind) тоже останутся, так как bind mount
напрямую привязан к папке.

Что сдать в качестве ответа?

Список команд, которыми вы:


Создали именованный том (myvol).
Запустили container_vol (используя myvol) и записали в него какие-то
тестовые данные.
Создали локальную папку (например, /tmp/testbind) и запустили
container_bind с -v /tmp/testbind:/app/data (или аналогичным путём).
Проверили, что при записи внутри контейнера файлы появляются на
хосте и наоборот.

Подсказка (раскрывать при необходимости)


Сначала создайте именованный том myvol. Запустите контейнер (например,
alpine), который пишет файлы в какую-нибудь директорию, связанную с myvol.

1/2
Затем создайте локальную папку (например, /tmp/testbind), запустите второй
контейнер с -v /tmp/testbind:/app/data. При записи в /app/data внутри
контейнера файлы появятся в /tmp/testbind на хосте.
Посмотрите docker volume ls — увидите myvol, а для bind mount Docker
volume не создаётся, потому что это просто монтирование папки хоста.

Итог: Вы получите практическое сравнение volume (управляемого Docker) и bind


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

2/2
15 апреля 2025 г.

5.2 Практика

Запускаем локальный сайт в контейнере через Bind Mount и


сравниваем с Volume

Сценарий задания

1. Подготовьте локальную папку, например /tmp/mywebsite, и разместите там


простой [Link] с какими-нибудь отличительными строчками (чтобы было
видно, что это ваш контент).
2. Запустите контейнер Nginx (назовём его site_bind) и смонтируйте
/tmp/mywebsite внутрь контейнера в /usr/share/nginx/html.
Укажите флаг -p 8080:80, чтобы с хоста посмотреть сайт на
[Link]
Убедитесь, что страница, которую отдаёт Nginx, действительно [Link]
из вашей локальной папки.
3. Измените [Link] в /tmp/mywebsite (добавьте или исправьте текст).
Обновите страницу в браузере — проверьте, что изменения видны сразу.
4. Попробуйте вместо Bind Mount использовать Volume (например, mywebvol) и
скопируйте туда файлы (через контейнер или docker cp) — сравните, что
изменение на лету не так просто, но Docker сам хранит эти данные в
/var/lib/docker/volumes, не завися от путей на хосте.

Что сдать в качестве ответа?

Список команд, которыми вы:


Создали локальную папку (например, /tmp/mywebsite), положили туда
[Link].
Запустили контейнер site_bind с флагами -v
/tmp/mywebsite:/usr/share/nginx/html и -p 8080:80.
Проверили, что сайт открывается на [Link] и
отображает ваш контент.
(Опционально) Создали Volume (mywebvol) и запустили Nginx с ним,
сравнили поведение.

Подсказка (раскрывать при необходимости)


Убедитесь, что у вас есть [Link] в /tmp/mywebsite.
Запустите Nginx с -v /tmp/mywebsite:/usr/share/nginx/html и -p 8080:80.
Изменения в /tmp/mywebsite будут мгновенно видны в контейнере.
Если попытаетесь сделать то же самое с Volume, увидите, что нужно
копировать файлы внутрь тома, так как Volume абстрагируется от конкретного
пути на хосте.

1/1
5.2 Практика

Разделяем логи и данные в одном контейнере, используя


Volume и Bind Mount

Сценарий задания
1. Создайте именованный том (например, mydatavol), чтобы хранить
данные приложения.
2. Подготовьте локальную папку на хосте (например, /tmp/myapplogs), в которую
приложение будет писать логи (через bind mount).
3. Запустите контейнер (назовём его myapp), который:
Использует mydatavol как том для данных (например, в /var/lib/myapp).
Одновременно монтирует /tmp/myapplogs (локальная папка на хосте) в
/var/log/myapp (для логов).
При необходимости, используйте любой образ (например, alpine или
ubuntu) и простую команду, которая периодически пишет что-то в лог и
создаёт данные (можно скрипт на bash или Python).
4. Проверьте:
Что в /var/lib/myapp (внутри контейнера) находятся данные (и они
сохраняются в mydatavol).
Что в /var/log/myapp контейнер действительно пишет логи, которые
видны прямо в /tmp/myapplogs на хосте.
5. Удалите контейнер (или пересоздайте его). Убедитесь, что:
«Данные» в mydatavol не пропали (при запуске нового контейнера с тем
же -v том снова «подхватит»).
«Логи» в /tmp/myapplogs остались (так как это bind mount).

Что сдать в качестве ответа?

Список команд, которыми вы:


Создали именованный том (mydatavol).
Подготовили локальную папку (например, /tmp/myapplogs).
Запустили контейнер myapp, привязав и mydatavol (в /var/lib/myapp) и
/tmp/myapplogs (в /var/log/myapp).
Проверили, что данные и логи видны где нужно (mydatavol и
/tmp/myapplogs соответственно).

Подсказка (раскрывать при необходимости)


Сначала docker volume create mydatavol для «данных».
Локальную папку /tmp/myapplogs создайте сами (например mkdir
/tmp/myapplogs), чтобы «логи» внутри контейнера (e.g. /var/log/myapp)
отображались.

1/2
Запуск контейнера: docker run с -v mydatavol:/var/lib/myapp и -v
/tmp/myapplogs:/var/log/myapp. Используйте любой образ, где можно
записывать в /var/log/myapp и /var/lib/myapp.
После удаления контейнера проверьте, что docker volume ls всё ещё
показывает mydatavol, а /tmp/myapplogs не пустая.

2/2
5.2 Практика

MySQL с именованным томом для данных, bind mount для


конфигурации и одноразовые контейнеры для бэкапа

Сценарий задания
1. Создайте именованный том (например, mysqldata) для хранения файлов
MySQL.
2. Подготовьте локальную папку (например, /tmp/mysqlconf), где будет лежать
[Link]. В этом файле можно указать любые нужные настройки (например,
bind-address=[Link], sql_mode=NO_ENGINE_SUBSTITUTION, и т. д.).
3. Запустите контейнер MySQL (назовём его mysqlserver):
Используйте mysqldata как том: подключив его к /var/lib/mysql или
подходящей директории внутри контейнера.
Подключите [Link] с хоста (через bind mount) к директории, куда MySQL
читает кастомные конфиги (например, /etc/mysql/conf.d).
При первом запуске, если используете mysql:latest, задайте -e
MYSQL_ROOT_PASSWORD=secret (или другой), чтобы MySQL
инициализировался.
По желанию, опубликуйте порт -p 3306:3306, чтобы проверять доступ к
базе извне (или используйте другой подход, например, Docker network +
клиент). Но это не обязательно.
4. Сделайте какую-то тестовую базу или таблицу, чтобы было что бэкапить
(через docker exec + mysql -u root -psecret).
5. Одноразовый контейнер (назовём его backup):
Создайте (при необходимости) локальную папку (например, /tmp/backups)
на хосте, куда бэкап будет сохраняться.
Запустите контейнер «backup» без сохранения (флаг --rm), где
используете mysqldump (или копирование файлов) и складываете
результат в /tmp/backups на хосте (через bind mount).
Если SELinux блокирует запись в /tmp/backups, нужно будет chcon -Rt
svirt_sandbox_file_t /tmp/backups или иное решение. В случае
«Permission denied» — это вероятно SELinux/доступность папки.
В итоге появится (например) /tmp/backups/[Link] или
/tmp/backups/[Link], смотря какую стратегию бэкапа вы выбрали.
6. Восстановление (или проверка сохранённого архива):
Можно аналогичным способом запустить restore контейнер:
монтировать /tmp/backups и выполнить mysql -u root SOURCE
/backups/[Link] или распаковать файлы.
7. Удалите и пересоздайте mysqlserver, проверив, что данные остались в
mysqldata, так как это именованный том.

Что сдать в качестве ответа?

1/2
Краткий список действий, как вы:
Создали том (mysqldata), подготовили папку /tmp/mysqlconf (с [Link]) и
(при желании) /tmp/backups.
Запустили mysqlserver (используя mysqldata и bind mount для [Link]).
Создали тестовую таблицу, вставили данные.
Запустили одноразовый контейнер (backup) с mysqldump (или
архивированием), проверили SELinux (если нужно).
Опционально, сделали восстановление или пересоздание контейнера,
убедились, что данные не пропали.

Подсказка (раскрывать при необходимости)


Том создаётся docker volume create mysqldata.
Для [Link]: /tmp/mysqlconf/[Link] → /etc/mysql/conf.d/[Link] (bind
mount).
Для бэкапа: одноразовый контейнер (например mysql:latest), используйте
mysqldump -h mysqlserver -u root -psecret > /backup/[Link], если /backup →
/tmp/backups на хосте.
Если «Permission denied» — chcon -Rt svirt_sandbox_file_t /tmp/backups.
Удаляя mysqlserver, том mysqldata не исчезнет, при следующем запуске новый
контейнер может «подхватить» те же файлы.

2/2
5.3 docker volume

Мы уже обсудили, что такое Volume (том) в Docker: это специальная область для
хранения данных, которую Docker управляет самостоятельно. Теперь давайте
посмотрим на конкретные команды — docker volume create и docker volume
inspect, с помощью которых вы можете создавать, настраивать и изучать тома.

1. docker volume create

Назначение: создать именованный (named) том, который можно использовать при


запуске контейнера.

docker volume create [OPTIONS] <имя_тома>

Если не указать имя, Docker сгенерирует случайное (пример:


ab12cd_xyvolume).
Можно указать драйвер (--driver) или дополнительные параметры (--opt)
для более сложных случаев (например, сетевые тома NFS, CIFS). Но в
базовом сценарии:

docker volume create mydata

Теперь у вас есть том mydata.

Использование в docker run:

docker run -d \
--name mycontainer \
-v mydata:/path/in/container \
some-image

Здесь mydata — это тот самый именованный том.


Docker автоматически подцепит том к /path/in/container. Любые записи
внутри этого пути будут сохраняться в томе, не пропадут при удалении
контейнера.

2. docker volume inspect

Назначение: посмотреть детальную информацию об уже созданном томе.


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

1/3
docker volume inspect <имя_тома>

Пример:

docker volume create mydata


docker volume inspect mydata

Результатом будет JSON-структура, что-то вроде:

[
{
"CreatedAt": "2023-05-01T10:15:24Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/mydata/_data",
"Name": "mydata",
"Options": {},
"Scope": "local"
}
]

Mountpoint показывает, где физически лежат данные на хосте.


Driver обычно local, если не указано иное.
Scope указывает, что этот том локальный для данного Docker-хоста.

3. Дополнительные флаги и сценарии

docker volume ls: покажет список всех томов.


docker volume rm <том>: удаляет том (если не используется контейнером).
Будьте осторожны, все данные в нём исчезнут.
Драйверы: с помощью --driver <driver_name> можно создавать тома,
лежащие не на локальном диске, а в других хранилищах (NFS, AWS EFS, и т.
д.).
Опции: --opt key=value — дополнительные настройки для драйвера.
Например, указываете пути, параметры NFS.

4. Когда использовать именованные тома?

Длительное хранение: базы данных, папки с загрузками, любой stateful-


контейнер.
Простота: вам не нужно следить, где именно на хосте лежат файлы. Docker
абстрагирует детали.
Лёгкая передача: в [Link] прописать volumes: и всё начинает
работать, не привязываясь к хостовым путям.

5. Пример полного цикла

1. Создаём том: docker volume create mydata.


2. inspect: docker volume inspect mydata — видим Mountpoint, Driver=local.

2/3
3. Запускаем контейнер с этим томом:

docker run -d \
--name some-mysql \
-e MYSQL_ROOT_PASSWORD=secret \
-v mydata:/var/lib/mysql \
mysql:5.7

4. Контейнер пишет файлы базы данных в /var/lib/mysql, что фактически


сохраняется в /var/lib/docker/volumes/mydata/_data (но мы туда руками не
лезем).
5. Удаляем контейнер:

docker stop some-mysql


docker rm some-mysql

В томе данные остались


6. Снова запускаем:

docker run -d \
--name some-other-mysql \
-e MYSQL_ROOT_PASSWORD=secret \
-v mydata:/var/lib/mysql \
mysql:5.7

Тот же том mydata, база подхватит старые данные.

Итог
docker volume create позволяет явно создать том (именованный), который
потом используете в -v volumeName:/path.
docker volume inspect даёт детальную инфу (где хранится, драйвер, дата
создания и т. д.).
Вместе они помогают упорядочить хранение данных, не теряя их при
пересоздании контейнеров.

3/3
5.4 Практика

Создаем именованный том, запускаем контейнер и проверяем


сохранение данных

Сценарий задания
1. Создайте именованный том (например mydata).
2. Проверьте, что том создан.
3. Запустите контейнер (назовем его mycontainer), используя том mydata. Пусть
внутри контейнера будет директория, в которую вы запишите файлы.
4. Остановите и удалите контейнер.
5. Запустите новый контейнер, снова используя том mydata, и убедитесь, что
данные остались.
6. Посмотрите подробную информацию о томе mydata.

В качестве ответа сдайте все команды которые вы использовали

Подсказка
Сначала можно выполнить docker volume create mydata
Посмотреть список томов через docker volume ls
При запуске контейнера используйте -v mydata:/someDir, создайте там файл
После удаления контейнера и повторного запуска с тем же -v
mydata:/someDir данные сохранятся
docker volume inspect mydata покажет информацию о томе

1/1
5.4 Практика

Создаем том для базы данных и проверяем сохранение


таблицы после удаления контейнера

Сценарий задания
1. Создайте именованный том (например mydbdata).
2. Создайте сеть Docker (например mynet), чтобы связать контейнеры.
3. Запустите контейнер (назовем его mydb) с образом MySQL, монтируя том
mydbdata в директорию для данных, и установите переменные среды
(например пароль root). Подключите контейнер к сети mynet.
4. Проверьте, что mydb работает.
5. Запустите второй контейнер (например myclient) в сети mynet, чтобы
подключиться к mydb и создать (или изменить) таблицу в базе.
6. Остановите и удалите mydb.
7. Запустите mydb заново с тем же томом mydbdata и убедитесь, что таблица
сохранилась.
8. Посмотрите информацию о томе mydbdata.

В качестве ответа сдайте все команды которые вы использовали

Подсказка
Можно создать том через docker volume create mydbdata.
Сеть можно создать через docker network create mynet.
Для MySQL используйте -e MYSQL_ROOT_PASSWORD и монтируйте -v
mydbdata:/var/lib/mysql, а также подключайте к сети --network mynet.
Чтобы проверить таблицу, можно подключиться к базе через docker exec или
запустить второй контейнер myclient с тем же --network.
docker volume inspect mydbdata покажет подробную информацию.

1/1
5.4 Практика

Настраиваем веб-сервер с логированием в том и проверяем


сохранение логов

Сценарий задания
1. Подготовьте образ (можно с помощью Dockerfile или используя готовый,
например nginx), чтобы веб-сервер записывал логи в папку (например
/app/logs).
2. Создайте именованный том (например mylogs) для хранения логов.
3. Запустите контейнер с веб-сервером, смонтировав том mylogs в директорию
логов внутри контейнера.
4. Сделайте несколько http-запросов (или отправьте какие-то сообщения), чтобы
веб-сервер сгенерировал логи.
5. Остановите и удалите контейнер.
6. Запустите новый контейнер с тем же томом mylogs и проверьте, что логи
сохранились.
7. Посмотрите информацию о томе mylogs.

В качестве ответа сдайте все команды которые вы использовали

Подсказка
Для создания тома можно использовать docker volume create mylogs.
Для запуска веб-сервера монтируйте папку логов: -v mylogs:/app/logs (если
в образе веб-сервера логи идут в /app/logs).
Посмотреть информацию о томе: docker volume inspect mylogs.
После удаления контейнера данные в томе сохраняются.

1/1
5.4 Практика

Запускаем базу MySQL и Redis с несколькими томами и


проверяем сохранение данных

Сценарий задания
1. Создайте два именованных тома (например mysql_data и redis_data).
2. Создайте сеть (например myapp_network) для соединения сервисов.
3. Запустите контейнер MySQL (назовем его mysqldb), используя том mysql_data
для директории данных и указав переменные среды (например пароль).
Подключите его к сети myapp_network.
4. Запустите контейнер Redis (назовем его myredis), используя том redis_data
для директории данных. Подключите его к сети myapp_network.
5. Запустите еще один контейнер (например myclient) в myapp_network,
проверьте подключение к mysqldb и myredis, создайте (или измените) записи,
чтобы убедиться, что данные сохраняются.
6. Остановите и удалите контейнеры mysqldb и myredis.
7. Снова запустите mysqldb и myredis с теми же томами mysql_data и redis_data
и проверьте, что данные остались.
8. Посмотрите информацию о томах (например docker volume inspect).

В качестве ответа сдайте все команды которые вы использовали

Подсказка
Тома можно создать через docker volume create mysql_data и docker volume
create redis_data.
Сеть — docker network create myapp_network.
Для MySQL можно использовать -e MYSQL_ROOT_PASSWORD=... -v
mysql_data:/var/lib/mysql --network myapp_network.
Для Redis — -v redis_data:/data --network myapp_network.
С помощью контейнера myclient (или docker exec) можно проверить записи в
MySQL и Redis, а затем убедиться, что после удаления контейнеров данные не
пропадают.
docker volume inspect покажет путь и драйвер тома.

1/1
5.5 Продвинутые вещи

До сих пор мы говорили о стандартных томах и bind mounts. Но Docker


предоставляет и более продвинутые возможности, позволяющие оптимизировать
производительность, повышать безопасность и организовывать бэкапы. Рассмотрим
три интересных вещи: tmpfs-тома, шифрованные тома и резервное копирование
(бэкапы) томов.

1. tmpfs-тома
tmpfs — это том, который хранится в оперативной памяти (RAM) и не записывается
на диск. Он полезен для временных файлов или кэша, которые не нужно сохранять
после завершения контейнера. Это обеспечивает высокую скорость доступа и
гарантированное стирание данных при остановке.

Как задать tmpfs при запуске?

docker run -d \
--name mycontainer \
--tmpfs /app/tmp:rw,size=64m \
alpine

--tmpfs /app/tmp говорит Docker: создай tmpfs на пути /app/tmp.


size=64m (опционально) ограничивает размер в 64 МБ.
Все файлы, которые контейнер пишет в /app/tmp, живут в памяти и не
сохраняются на диск.

Плюсы/Минусы tmpfs

Плюсы:
Высокая скорость чтения/записи (RAM).
Безопасность: данные исчезают при остановке, не остаются на диске.
Минусы:
Ограничено объёмом оперативной памяти.
Если приложение или база активно использует много памяти, можно
столкнуться с OOM (Out Of Memory).

2. Шифрованные тома

1/3
Для дополнительной безопасности, иногда нужно хранить данные в
зашифрованном виде (например, в случае конфиденциальных данных). Docker
сам по себе не умеет из коробки шифровать тома, но есть несколько подходов:

Внешний драйвер для томов (например, rexray, blockbridge) — они могут


предоставлять шифрование на уровне блочных устройств или файловой
системы.
LUKS/dm-crypt на уровне хоста — шифруете раздел, где лежат все Docker-
вольюмы. Таким образом, даже если диск утекает, данные зашифрованы.
Docker Enterprise (устаревший) в некоторых системах была опция
шифрования томов, но в большинстве случаев используется или внешний
плагин, или вы настраиваете шифрование файловой системы на уровне хоста.

Общая схема

Допустим, вы хотите зашифровать том, используя внешний драйвер. Вы можете


сделать что-то вроде:

docker volume create \


--driver blockbridge \
--opt encryption=on \
--name securedata

Затем -v securedata:/app/data при запуске контейнера.


Драйвер сам занимается шифрованием/дешифрованием, храня ключи где-то
(часто интеграция с KMS).

В результате в случае похищения физического диска или доступа к


/var/lib/docker/volumes — данные будут зашифрованы.

3. Механизмы бэкапов (резервного копирования)


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

3.1 Использовать docker run с tar/zip

1. Запускаете временный контейнер, монтируя нужный том.


2. В контейнере выполняете tar -czf - /data — вывод направляете в stdout.
3. На хосте принимаете этот поток, записываете в файл .[Link].

docker run --rm \


-v mydata:/data:ro \
alpine \
tar -czf - /data > [Link]

Здесь mydata — том, /data — путь внутри контейнера.


:ro (read-only) — безопаснее, чтоб случайно не менять данные.

3.2 docker volume export/import

2/3
В новых версиях Docker есть возможность docker volume export VOLUME, но она
экспериментальная в некоторых сборках. Аналогично docker volume import. Это
упрощает извлечение/загрузку содержимого тома.

3.3 Внешние инструменты бэкапа

Если вы используете внешнее хранилище (NFS, GlusterFS, cloud storage), то можно


организовать бэкап средствами этой системы (снимки, snapshots). Либо запускать
внутри контейнера Cron, который делает бэкап БД, и складывает его в другое место.

Подведём итоги

tmpfs-тома: храним данные в памяти (RAM), без записи на диск. Полезно для
кэша/временных файлов, но не для долгосрочного хранения.
Шифрованные тома: обычно реализуются через внешние драйверы или
шифрование на уровне хоста. Позволяют защитить данные при утечке
физического диска.
Бэкапы:
Традиционный метод через docker run tar (архивируем содержимое
тома в .[Link]).
docker volume export/import (при необходимости).
Использование внешних инструментов, если том на NFS или облаке.

Эти механизмы позволяют тонко контролировать, как и где хранятся ваши


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

3/3
5.6 Практика

Используем tmpfs для временных файлов, создаем


шифрованный том и делаем бэкап

Сценарий задания
1. Запустите контейнер с помощью --tmpfs, чтобы папка (например /app/tmp)
была в оперативной памяти.
2. Создайте именованный том (например securedata) с опциями шифрования
(можно указать любой драйвер или флаг, симулирующий шифрование).
3. Запустите контейнер, монтируя том securedata в директорию (например
/data) внутри контейнера. Запишите туда любые файлы.
4. Сделайте бэкап содержимого тома, запаковав файлы (например с помощью
tar) и сохранив архив в локальную папку на хосте.
5. Остановите и удалите контейнер.
6. Запустите контейнер снова, используя тот же том securedata, убедившись, что
файлы на месте.
7. Посмотрите информацию о томе securedata.

В качестве ответа предоставьте все команды

Подсказка
Для tmpfs можно использовать --tmpfs /app/tmp:rw,size=64m.
Чтобы симулировать шифрованный том, можно указать драйвер и опцию
(например --driver blockbridge --opt encryption=on).
Для бэкапа воспользуйтесь tar, смонтировав том в контейнере и
перенаправив вывод архива на хост.
docker volume inspect securedata покажет путь к данным.

1/1
5.6 Практика

Используем tmpfs для логов базы данных, создаем


шифрованный том и делаем бэкап

Сценарий задания
1. Создайте именованный том (например secure_db) с опцией шифрования (или
любым фиктивным драйвером, который это поддерживает).
2. Запустите контейнер (например mydb) с базой данных (MySQL или любая
другая), монтируя том secure_db в директорию данных.
3. Настройте, чтобы логи базы писались во временную папку (например /logs) и
сделайте её tmpfs (в оперативной памяти).
4. Сделайте несколько операций в базе, чтобы сгенерировать логи и данные.
5. С помощью бэкапа (например через tar или другой способ) сохраните
содержимое тома secure_db на хосте.
6. Остановите и удалите контейнер.
7. Запустите контейнер снова, используя тот же том secure_db, и убедитесь, что
данные остались (а логи были временными и пропали).
8. Посмотрите информацию о томе (например docker volume inspect
secure_db).

В качестве ответа предоставьте все команды

Подсказка
Создать том можно через docker volume create --driver blockbridge --opt
encryption=on --name secure_db (или любую другую комбинацию).
Чтобы логи оказались в памяти, используйте --tmpfs /logs:size=64m при
запуске контейнера.
Для бэкапа можно собрать архив с помощью tar, запустив временный
контейнер и перенаправив вывод в файл на хосте.
После удаления контейнера том сохраняется, что позволяет поднять снова тот
же контейнер и проверить, что данные остались.
docker volume inspect secure_db даст детали о томе.

1/1
5.6 Практика

Разворачиваем несколько контейнеров: БД с шифрованным


томом, приложение с tmpfs и бэкап тома

Сценарий задания
1. Создайте том (например encrypted_db), указав драйвер или опцию
шифрования.
2. Запустите контейнер базы данных (например mydb), монтируя том
encrypted_db в папку для данных и установив переменную среды для пароля.
3. Настройте, чтобы контейнер приложения (например myapp) имел директорию
(например /app/cache) в tmpfs. Подключите его к той же сети, что и mydb, и
убедитесь, что он может взаимодействовать с БД.
4. Создайте еще один том (например db_backup), предназначенный для
сохранения резервных копий.
5. Запустите временный контейнер (например backup_runner), чтобы сделать
бэкап содержимого тома encrypted_db и положить архив в том db_backup.
Используйте любой инструмент (например tar).
6. Остановите и удалите контейнеры mydb и myapp.
7. Запустите их снова, используя тот же том encrypted_db, убедившись, что
данные БД остались.
8. Проверьте информацию о томе encrypted_db, а при желании и о томе
db_backup.

В качестве ответа предоставьте все команды

Подсказка
Том можно создать так: docker volume create --driver blockbridge --opt
encryption=on --name encrypted_db.
Для tmpfs: --tmpfs /app/cache:size=64m в параметрах docker run.
Сеть (если нужно) создается через docker network create mynet, потом
указывать --network mynet при запуске контейнеров.
Для бэкапа: docker run --rm -v encrypted_db:/source:ro -v
db_backup:/backup alpine tar -czf /backup/[Link] /source.
docker volume inspect encrypted_db покажет информацию о томе.

1/1
6.1 Основы

Зачем нужен Docker Compose


Когда ваш проект состоит из нескольких контейнеров (например, база данных,
бэкенд-приложение, фронтенд-приложение, кэш и т. д.), управлять ими командами
docker run (каждый со своими флагами -p, -e, -v) становится неудобно. Docker
Compose решает эту проблему тем, что дает вам единый файл (обычно docker-
[Link]) и набор команд, чтобы поднимать (и останавливать) все контейнеры
разом.

1. Основная идея Docker Compose

Вы описываете сервисы (каждый сервис — один контейнер) в YAML-файле.


В этом файле задаёте:
образы (image)
порты (ports)
переменные окружения (environment)
тома (volumes)
зависимости (depends_on)
и многое другое
При команде docker-compose up (или docker compose up в новых версиях
Docker) Compose:
1. считывает YAML;
2. создаёт нужные сети и тома;
3. запускает все указанные сервисы (контейнеры);
4. связывает их между собой через DNS — каждый сервис доступен по
своему имени

1/6
2. Детали синтаксиса [Link]

Минимальная структура обычно такая:

2/6
version: "3.8" # версия синтаксиса Compose
services: # блок, где мы перечисляем все сервисы
servicename: # имя сервиса (например, web или db)
image: ...
container_name: ...
ports:
- "хост_порт:контейнер_порт"
environment:
- KEY=value
volumes:
- ...
depends_on:
- ...
volumes:
...
networks:
...

version: "3.8" — одна из последних стабильных версий формата (3.x). В


Docker Compose V2 (плагине)
services: — главная секция, где перечисляются контейнеры (сервисы),
которые вы хотите запустить.
volumes: и networks: внизу — объявления глобальных именованных томов и
кастомных сетей, если нужны.

Пример с двумя сервисами

3/6
version: "3.8"

services:
db:
image: postgres:14
container_name: my_db
environment:
- POSTGRES_PASSWORD=secret
- POSTGRES_USER=user
volumes:
- dbdata:/var/lib/postgresql/data
networks:
- mynetwork

web:
image: nginx:latest
container_name: my_web
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
depends_on:
- db
networks:
- mynetwork

volumes:
dbdata:

networks:
mynetwork:

В этом примере:

db: запускает Postgres, хранит данные в /var/lib/postgresql/data, монтирует


том dbdata. Переменные окружения задают пользователя, пароль. networks:
[mynetwork] — подключается к сети mynetwork.
web: запускает Nginx, публикует порт 8080 (перенаправление на 80), берёт
статику из ./html (bind mount), зависит от db. Подключен к той же сети
mynetwork, так что внутри Compose web может обращаться к db по хосту db.
volumes: (dbdata) — именованный том, который сохраняет данные Postgres
между перезапусками.
networks: (mynetwork) — пользовательская сеть, в которой оба контейнера
находятся и видят друг друга.

3. Что такое depends_on и как оно работает

depends_on позволяет указать, что один сервис должен запускаться после


другого.
Например, depends_on: - db по сути говорит: «Запустить web после db».
Это гарантирует порядок запуска контейнеров: при docker compose up сначала
поднимется db, затем web.

4/6
Однако depends_on не гарантирует, что db успеет полностью
инициализироваться и быть готовым к подключению (для этого есть
специальные механизмы healthcheck, wait-for-it, и т.д.). Но для большинства
базовых случаев, когда нужно просто сначала поднять базу, потом
приложение, depends_on достаточно.
Если у вас более сложные требования (дождаться готовности сервиса), нужно
дополнительно использовать healthcheck или иные инструменты (например,
wait-for-it скрипты), чтобы приложение не попыталось подключаться к базе
слишком рано.

4. Как поднять, остановить и смотреть логи?

docker-compose up (или docker compose up) — запускает всё и выводит логи в


консоль. Если нажать CTRL+C — остановит контейнеры.
docker-compose up -d — запускает в фоне.
docker-compose down — остановит и удалит контейнеры, сети (и, при желании,
тома — если указать --volumes).
docker-compose logs — посмотреть логи всех сервисов (можно -f или указать
конкретный сервис docker-compose logs -f db).

5. Почему это удобнее, чем docker run?

Вместо длинных ручных команд (куча флагов -p, -v, -e, --network) вы всё
описываете в YAML.
Легко расти и поддерживать: сегодня у вас 2 контейнера, завтра 5 — просто
добавляете новые секции services: в Compose.

5/6
Управление версионностью: [Link] можно хранить в
репозитории, чтобы вся команда видела общий способ запуска проекта.

Итог
Docker Compose — инструмент и формат (YAML) для описания нескольких
контейнеров как сервисов.
[Link] позволяет задать образы, порты, тома, переменные
окружения, зависимости (depends_on) и многое другое.
docker-compose up/down (или docker compose up/down) запускают и
останавливают все сервисы за один шаг.

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

6/6
6.2 Практика

Учимся писать [Link] на примере реальной


задачи
Ниже разберем пошагово, как создать небольшой статический сайт и запустить его
через Nginx с помощью [Link].

1. Создаем папку и файл [Link]

Создайте папку mywebsite.


Внутри неё создайте файл [Link] со следующим содержимым:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello from Docker Compose</title>
</head>
<body>
<h1>Hello from Docker Compose!</h1>
<p>Это наш тестовый сайт, который будет раздаваться Nginx через Docker Compose.
</p>
</body>
</html>

2. Создаем файл [Link] с подробными комментариями

В той же папке, где лежит mywebsite, создайте файл [Link]. Ниже


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

1/3
version: "3.8" # Версия синтаксиса Docker Compose
# (3.8 - актуальная и часто используемая)

services: # Раздел, где описываются все контейнеры (сервисы)


web: # Имя нашего сервиса (может быть любым)
image: nginx:latest # Используем официальный образ nginx
container_name: my_web # Назначаем понятное имя контейнеру

ports:
- "8080:80" # Пробрасываем порт 80 из контейнера
# на 8080 на хостовой машине

volumes:
- ./mywebsite:/usr/share/nginx/html
# Монтируем локальную папку mywebsite
# в директорию /usr/share/nginx/html,
# откуда nginx раздает статические файлы

command: nginx -g "daemon off;"


# Параметр 'command' позволяет переопределить
команду,
# с которой запускается контейнер, вместо той, что
задана
# в Dockerfile образа.
# В случае с Nginx по умолчанию уже задана команда
# "nginx -g daemon off;", так что переопределение
# не требуется. Если бы вы хотели добавить или
изменить
# запуск, можно было бы это сделать здесь.

Что такое command в [Link]

Когда вы используете command, вы переопределяете стандартную команду,


идущую из Dockerfile или из entrypoint образа.
Это полезно в случаях, когда нужно запустить конкретный скрипт или процесс
вместо стандартного запуска приложения.
Если в Dockerfile указано CMD ["nginx", "-g", "daemon off;"], то в docker-
[Link] можно задать command: nginx -g "daemon on;" (или что-то иное),
чтобы изменить поведение.

3. Запускаем и проверяем

Откройте терминал и перейдите в папку, где у вас лежит [Link].


Выполните docker compose up -d — Docker Compose подтянет образ nginx
(если его нет локально) и запустит контейнер my_web.
Проверьте, что контейнер работает: docker compose ps.
Откройте в браузере [Link] Вы должны увидеть свою
страницу с заголовком «Hello from Docker Compose».

4. Остановка и удаление

docker compose down — остановит и удалит запущенный контейнер my_web.

2/3
Если нужно оставить контейнер работающим — не выполняйте down, тогда
веб-сервис продолжит раздавать сайт.

Итого:

Мы создали папку mywebsite и файл [Link].


Написали [Link] с подробными комментариями, объясняющими
каждую строку, включая возможное использование command.
Запустили Nginx одной командой docker compose up -d и сделали проброс
порта на localhost:8080.

3/3
6.2 Практика

Создаем первый Docker Compose файл


В этом задании вы создадите самый простой Docker Compose файл для запуска
веб-сервера.

Техническое задание
1. Сервис (веб-сервер):
Базовый образ — nginx:alpine
Проброс порта 8080 на хосте на порт 80 в контейнере

Структура проекта

Создайте файл [Link] в пустой директории.

Что нужно сдать?

1. [Link] — файл, который запускает nginx контейнер.


2. Проверить работу композа:

docker-compose up -d
# Или для более новых версий Docker
docker compose up -d

3. Проверка работы: откройте в браузере [Link] — вы должны


увидеть стандартную страницу приветствия Nginx.

Подсказка (структура [Link])

version: "3"

services:
web:
image: nginx:alpine
ports:
- "8080:80"

Полное решение

version: "3"

services:
web:
image: nginx:alpine
container_name: simple_web
ports:
- "8080:80"

1/1
13 апреля 2025 г.

6.2 Практика

Веб-сервер с статическим контентом


В этом задании вы создадите Docker Compose файл для запуска веб-сервера с
пользовательским HTML-контентом.

Техническое задание
1. Сервис (веб-сервер):
Базовый образ — nginx:alpine
Проброс порта 8080 на хосте на порт 80 в контейнере
Подключение локальной директории ./html к /usr/share/nginx/html в
контейнере

Структура проекта

Подготовьте следующую структуру файлов:

[Link] — файл, который вы должны создать


html/[Link] — HTML-файл, который вы также должны создать

Создайте файл html/[Link] со следующим содержимым:

1/3
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Моя первая страница через Docker Compose</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f0f0f0;
}
.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
}
</style>
</head>
<body>
<div class="container">
<h1>Привет из Docker Compose!</h1>
<p>Если вы видите эту страницу, значит:</p>
<ul>
<li>Вы успешно создали [Link] файл</li>
<li>Правильно настроили volumes для HTML-контента</li>
<li>Корректно пробросили порты</li>
</ul>
<p>Поздравляем с успешным выполнением задания!</p>
</div>
</body>
</html>

Что нужно сдать?


1. [Link] — файл, который запускает nginx с пользовательским
контентом.
2. Проверить работу композа:

docker-compose up -d
# Или для более новых версий Docker
docker compose up -d

3. Проверка работы: откройте в браузере [Link] — вы должны


увидеть страницу, созданную вами в [Link] (а не стандартную страницу
Nginx).

Требования к решению

Необходимо использовать bindmount для подключения локальной директории


с HTML-файлами

2/3
Версия docker-compose файла должна быть не ниже 3.0
Контейнер должен иметь имя web-server

Подсказка (структура [Link])

version: "3"

services:
web:
image: nginx:alpine
container_name: web-server
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html

3/3
11 апреля 2025 г.

6.2 Практика

Веб-приложение с базой данных


В этом задании вы создадите Docker Compose файл для запуска веб-приложения,
которое взаимодействует с базой данных.

Техническое задание
1. Сервис 1 (веб-приложение):
Базовый образ — php:7.4-apache
Проброс порта 8080 на хосте на порт 80 в контейнере
Подключение локальной директории ./app к /var/www/html в контейнере
Установка расширения PHP для работы с MySQL
Зависимость от сервиса базы данных
2. Сервис 2 (база данных):
Базовый образ — mysql:5.7
Настройка переменных окружения для инициализации БД
Использование именованного тома для хранения данных
Проброс порта 3306 (опционально)

Структура проекта

Подготовьте следующую структуру файлов:

[Link] — файл, который вы должны создать


app/[Link] — PHP-файл для тестирования подключения к БД

Создайте файл app/[Link] со следующим содержимым:

1/4
<?php
$host = 'db'; // Имя сервиса в docker-compose
$user = 'devuser'; // Имя пользователя из переменных окружения
$password = 'devpass'; // Пароль из переменных окружения
$db = 'test_db'; // Имя базы данных из переменных окружения

// Создаем соединение
$conn = new mysqli($host, $user, $password, $db);

// Проверяем соединение
if ($conn->connect_error) {
die("Ошибка подключения к базе данных: " . $conn->connect_error);
}

echo "<h1>Веб-приложение с базой данных</h1>";


echo "<p>Успешное подключение к MySQL!</p>";

// Создание таблицы, если она не существует


$sql = "CREATE TABLE IF NOT EXISTS visitors (
id INT AUTO_INCREMENT PRIMARY KEY,
visit_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)";

if ($conn->query($sql) === TRUE) {


echo "<p>Таблица visitors создана или уже существует.</p>";
} else {
echo "<p>Ошибка создания таблицы: " . $conn->error . "</p>";
}

// Записываем информацию о новом посещении


$sql = "INSERT INTO visitors (visit_time) VALUES (NOW())";
$conn->query($sql);

// Получаем все записи о посещениях


$sql = "SELECT * FROM visitors ORDER BY visit_time DESC";
$result = $conn->query($sql);

echo "<h2>История посещений:</h2>";


echo "<table border='1'>";
echo "<tr><th>ID</th><th>Время посещения</th></tr>";

if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
echo "<tr><td>" . $row["id"]. "</td><td>" . $row["visit_time"]. "</td>
</tr>";
}
} else {
echo "<tr><td colspan='2'>Нет записей о посещениях</td></tr>";
}

echo "</table>";

$conn->close();
?>

Что нужно сдать?

2/4
1. [Link] — файл, который запускает оба сервиса.
2. Проверить работу композа:

docker-compose up -d
# Или для более новых версий Docker
docker compose up -d

3. Проверка работы: откройте в браузере [Link] — вы должны


увидеть веб-страницу с информацией об успешном подключении к базе
данных и таблицу посещений.

Требования к решению

Веб-приложение должно иметь доступ к базе данных.


Данные в MySQL должны сохраняться при перезапуске контейнеров
(используйте именованный том).
В переменных окружения для MySQL должны быть заданы:
MYSQL_ROOT_PASSWORD
MYSQL_DATABASE (должно быть test_db)
MYSQL_USER (должно быть devuser)
MYSQL_PASSWORD (должно быть devpass)
Используйте depends_on для указания зависимости веб-приложения от базы
данных.
Версия docker-compose файла должна быть не ниже 3.0.

Дополнительные технические детали

Для веб-сервиса вам нужно установить расширение mysqli для PHP. Это можно
сделать с помощью следующего блока команд в Docker Compose:

web:
image: php:7.4-apache
# другие настройки...
command: >
bash -c "docker-php-ext-install mysqli &&
apache2-foreground"

Или можно создать собственный Dockerfile для PHP с установленными


расширениями, но в данном задании это не требуется.

Подсказка (настройка веб-сервиса)

3/4
web:
image: php:7.4-apache
container_name: web_app
ports:
- "8080:80"
volumes:
- ./app:/var/www/html
depends_on:
- db
command: >
bash -c "docker-php-ext-install mysqli &&
apache2-foreground"

Подсказка (настройка сервиса базы данных)

db:
image: mysql:5.7
container_name: mysql_db
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: test_db
MYSQL_USER: devuser
MYSQL_PASSWORD: devpass
volumes:
- mysql_data:/var/lib/mysql

volumes:
mysql_data:

4/4
21 марта 2025 г.

6.2 Практика

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

Техническое задание
1. Сервис 1 (веб-сервер):
Базовый образ — nginx:alpine
Проброс порта 8080 на хосте на порт 80 в контейнере
Подключение локальной директории ./html к /usr/share/nginx/html в
контейнере
Зависимость от сервиса времени
Должен быть в общей сети app-network
2. Сервис 2 (сервис времени):
Базовый образ — busybox:latest
Должен выполнять скрипт, который каждые 10 секунд обновляет время в
файле
Подключение к общему тому shared-data
Должен быть в общей сети app-network

Структура проекта

Подготовьте следующую структуру файлов:

[Link] — файл, который вы должны создать


html/[Link] — HTML-файл для отображения на веб-сервере

Создайте файл html/[Link] со следующим содержимым:

1/5
<!DOCTYPE html>
<html>
<head>
<title>Docker Compose Практика</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f5f5f5;
}
.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
max-width: 600px;
margin: 0 auto;
}
h1 {
color: #333;
text-align: center;
}
.time-container {
margin-top: 20px;
padding: 15px;
background-color: #e9f7fe;
border-radius: 5px;
text-align: center;
}
#current-time {
font-size: 24px;
font-weight: bold;
color: #0066cc;
}
button {
margin-top: 10px;
padding: 8px 16px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<div class="container">
<h1>Docker Compose Демонстрация</h1>
<p>Это пример работы Docker Compose с двумя связанными сервисами:</p>
<ul>
<li><strong>Веб-сервер (Nginx)</strong> - отображает эту страницу</li>
<li><strong>Сервис времени (Busybox)</strong> - обновляет текущее
время</li>

2/5
</ul>

<div class="time-container">
<p>Текущее время (обновляется каждые 10 секунд):</p>
<div id="current-time">Загрузка...</div>
<button onclick="checkTime()">Обновить сейчас</button>
</div>

<p>Если вы видите обновляющееся время, значит:</p>


<ul>
<li>Вы успешно создали [Link] файл</li>
<li>Правильно настроили оба сервиса</li>
<li>Корректно настроили общую сеть</li>
<li>Правильно пробросили тома</li>
</ul>
</div>

<script>
function checkTime() {
fetch('/[Link]')
.then(response => [Link]())
.then(time => {
[Link]('current-time').innerText = time;
})
.catch(error => {
[Link]('current-time').innerText = 'Ошибка:
время не доступно';
[Link]('Ошибка:', error);
});
}

// Проверяем время при загрузке страницы


checkTime();

// Проверяем время каждые 5 секунд


setInterval(checkTime, 5000);
</script>
</body>
</html>

Что нужно сдать?

1. [Link] — файл, который запускает оба сервиса с правильными


настройками.
2. Проверить работу композа:

docker-compose up -d
# Или для более новых версий Docker
docker compose up -d

3. Проверка работы:
Откройте в браузере [Link] — вы должны увидеть
страницу с текущим временем, которое обновляется каждые 10 секунд.

3/5
Требования к решению

Оба сервиса должны быть в одной пользовательской сети app-network.


Веб-сервер должен зависеть от сервиса времени (использовать depends_on).
Сервис времени должен использовать том shared-data для сохранения файла
с временем.
Версия docker-compose файла должна быть 3.8.
Каждый контейнер должен иметь уникальное имя (задайте через
container_name).

Сервис времени должен выполнять следующую команду для обновления времени


каждые 10 секунд:

sh -c "while true; do date > /shared-data/[Link]; sleep 10; done"

Подсказка (структура [Link])

version: "3.8"

services:
web:
image: nginx:alpine
container_name: web_server
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
- shared-data:/usr/share/nginx/html
depends_on:
- time-service
networks:
- app-network

time-service:
image: busybox:latest
container_name: time_service
command: sh -c "while true; do date > /shared-data/[Link]; sleep 10; done"
volumes:
- shared-data:/shared-data
networks:
- app-network

volumes:
shared-data:

networks:
app-network:

Полное решение

4/5
version: "3.8"

services:
web:
image: nginx:alpine
container_name: web_server
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
- shared-data:/usr/share/nginx/html
depends_on:
- time-service
networks:
- app-network

time-service:
image: busybox:latest
container_name: time_service
command: sh -c "while true; do date > /shared-data/[Link]; sleep 10; done"
volumes:
- shared-data:/shared-data
networks:
- app-network

volumes:
shared-data:

networks:
app-network:

5/5
21 марта 2025 г.

6.2 Практика

Разработка окружения для веб-приложения


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

Техническое задание
Компания разрабатывает веб-приложение и вам необходимо настроить локальное
окружение для разработки, которое должно включать следующие компоненты:

1. Веб-сервер (фронтенд + бэкенд):


Базовый образ — nginx:latest
Проброс порта 8000 на хосте на порт 80 в контейнере
Подключение локальной директории ./src к /usr/share/nginx/html в
контейнере
Зависимость от сервиса базы данных
Имя контейнера: dev-web
2. База данных:
Базовый образ — postgres:13
Переменные окружения для настройки базы данных:
POSTGRES_DB: development
POSTGRES_USER: developer
POSTGRES_PASSWORD: devpassword
Использование именованного тома для хранения данных
Имя контейнера: dev-db
3. Админ-панель для БД:
Базовый образ — adminer:latest
Проброс порта 8080 на хосте на порт 8080 в контейнере
Зависимость от сервиса базы данных
Имя контейнера: dev-adminer

Структура проекта
Подготовьте следующую структуру файлов:

[Link] — файл, который вы должны создать


src/[Link] — HTML-файл с тестовой страницей
src/[Link] — HTML-файл для тестирования доступности базы данных

Создайте файл src/[Link] со следующим содержимым:

1/7
<!DOCTYPE html>
<html>
<head>
<title>Проект компании</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f5f5f5;
}
.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
max-width: 800px;
margin: 0 auto;
}
h1 {
color: #333;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.panel {
margin-top: 20px;
padding: 15px;
background-color: #f9f9f9;
border-left: 4px solid #007bff;
}
.links a {
display: inline-block;
margin: 10px;
padding: 10px 15px;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 4px;
}
.links a:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="container">
<h1>Тестовое окружение для разработки</h1>

<div class="panel">
<h2>Доступные сервисы:</h2>
<ul>
<li><strong>Веб-сервер:</strong> [Link] (текущая
страница)</li>
<li><strong>База данных PostgreSQL:</strong> dev-db:5432</li>
<li><strong>Adminer (управление БД):</strong>
[Link]
</ul>

2/7
</div>

<div class="links">
<a href="[Link]">Проверка подключения к БД</a>
<a href="[Link] target="_blank">Открыть Adminer</a>
</div>

<div class="panel">
<h3>Данные для подключения к БД:</h3>
<ul>
<li><strong>Сервер:</strong> dev-db</li>
<li><strong>База данных:</strong> development</li>
<li><strong>Пользователь:</strong> developer</li>
<li><strong>Пароль:</strong> devpassword</li>
</ul>
</div>
</div>
</body>
</html>

Создайте файл src/[Link] со следующим содержимым:

3/7
<!DOCTYPE html>
<html>
<head>
<title>Проверка подключения к БД</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f5f5f5;
}
.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
max-width: 800px;
margin: 0 auto;
}
h1 {
color: #333;
}
.note {
background-color: #fff3cd;
border-left: 4px solid #ffc107;
padding: 15px;
margin: 20px 0;
}
.back {
display: inline-block;
margin-top: 20px;
padding: 10px 15px;
background-color: #6c757d;
color: white;
text-decoration: none;
border-radius: 4px;
}
.back:hover {
background-color: #5a6268;
}
</style>
</head>
<body>
<div class="container">
<h1>Проверка подключения к БД</h1>

<div class="note">
<p><strong>Примечание:</strong> На текущем этапе мы не можем выполнить
реальную проверку подключения к базе данных из браузера напрямую из-за ограничений
безопасности. В рабочем проекте такая проверка обычно выполняется через серверный
код (PHP, [Link], Python и т.д.).</p>

<p>Для проверки подключения рекомендуется использовать Adminer,


доступный по адресу <a href="[Link]
target="_blank">[Link] с параметрами подключения, указанными
на главной странице.</p>
</div>

4/7
<a href="[Link]" class="back">← Вернуться на главную</a>
</div>
</body>
</html>

Что нужно сдать?


1. [Link] — файл, который запускает все три сервиса.
2. Проверить работу композа:

docker-compose up -d
# Или для более новых версий Docker
docker compose up -d

3. Проверка работы:
Откройте в браузере [Link] — должна открыться
тестовая веб-страница
Откройте в браузере [Link] — должен открыться
интерфейс Adminer
В Adminer подключитесь к базе данных, используя следующие
параметры:
Система: PostgreSQL
Сервер: dev-db
Пользователь: developer
Пароль: devpassword
База данных: development

Требования к решению

Все сервисы должны быть в одной сети для обеспечения связи между ними.
База данных должна сохранять данные между перезапусками контейнеров
(использовать именованный том).
Используйте depends_on для указания зависимостей между сервисами.
Версия docker-compose файла должна быть не ниже 3.0.

Подсказка (структура [Link])

5/7
version: "3.8"

services:
web:
image: nginx:latest
container_name: dev-web
ports:
- "8000:80"
volumes:
- ./src:/usr/share/nginx/html
depends_on:
- db
networks:
- dev-network

db:
image: postgres:13
container_name: dev-db
environment:
- POSTGRES_DB=development
- POSTGRES_USER=developer
- POSTGRES_PASSWORD=devpassword
volumes:
- db-data:/var/lib/postgresql/data
networks:
- dev-network

adminer:
image: adminer:latest
container_name: dev-adminer
ports:
- "8080:8080"
depends_on:
- db
networks:
- dev-network

volumes:
db-data:

networks:
dev-network:

Полное решение

6/7
version: "3.8"

services:
web:
image: nginx:latest
container_name: dev-web
ports:
- "8000:80"
volumes:
- ./src:/usr/share/nginx/html
depends_on:
- db
networks:
- dev-network

db:
image: postgres:13
container_name: dev-db
environment:
- POSTGRES_DB=development
- POSTGRES_USER=developer
- POSTGRES_PASSWORD=devpassword
volumes:
- db-data:/var/lib/postgresql/data
networks:
- dev-network

adminer:
image: adminer:latest
container_name: dev-adminer
ports:
- "8080:8080"
depends_on:
- db
networks:
- dev-network

volumes:
db-data:

networks:
dev-network:

7/7
6.3 Внешние .env файлы

Подключение внешних файлов .env и использование


переменных окружения
При работе с Docker Compose мы часто не хотим хардкодить чувствительные
данные (пароли, токены) или иметь какие-то строки вроде версии образа или пути.
Для этого можно использовать файлы .env и подстановку переменных окружения
прямо в [Link]. Рассмотрим, как это работает и зачем это нужно.

1. Как Docker Compose читает .env

По умолчанию, если рядом с [Link] лежит файл .env, Compose


автоматически подгрузит переменные оттуда перед тем, как интерпретировать
ваш YAML.
.env должен иметь формат KEY=value, без кавычек или других YAML-
особенностей. Пример:

DB_PASS=supersecret
IMAGE_VERSION=1.2

Далее в вашем [Link], вы можете отсылаться к ним, используя


${KEY} синтаксис. Например, image: "myapp:${IMAGE_VERSION}".

2. Пример [Link] с подстановкой переменных

version: "3.8"

services:
db:
image: mysql:5.7
environment:
- MYSQL_ROOT_PASSWORD=${DB_PASS}
- MYSQL_DATABASE=appdb

web:
image: "myorg/myweb:${IMAGE_VERSION}"
ports:
- "${HOST_PORT}:80"
environment:
- DB_HOST=db
- DB_PASS=${DB_PASS}
depends_on:
- db

environment указывает MYSQL_ROOT_PASSWORD=${DB_PASS}. Compose заменит


${DB_PASS} на значение из .env (напр. supersecret).
ports мапит ${HOST_PORT}:80, так что вы можете задать HOST_PORT=8080 или
другой в .env.

1/3
image "myorg/myweb:${IMAGE_VERSION}" — позволяет менять версию образа
(например «1.2», «latest», «test-rc») без правки самого YAML.

3. Использование env_file

Иногда вы хотите не только подменять в YAML, но и автоматически передавать


переменные внутрь контейнера. Тогда вы можете использовать ключ env_file для
конкретного сервиса:

services:
web:
image: myorg/myweb
env_file:
- ./[Link]
ports:
- "8080:80"

[Link] — содержит строки KEY=value. Compose будет пробрасывать их


внутрь контейнера как переменные окружения.
Это несколько отличается от .env, которое Compose читает для подстановки.
env_file назначает эти переменные именно в контейнер.

4. Будьте осторожны с секретами!

2/3
.env по умолчанию можно коммитить в git, если вы не настроили исключения.
Если там пароли/токены — утечка.
Лучше хранить секреты отдельно (gitignore .env), использовать Vault, Docker
Secrets, или хотя бы private repo.
Любой, кто запускает Compose, увидит эти переменные. Также их можно
подсмотреть через docker inspect (в среде runtime) или docker-compose
config.

5. Проверка конфигурации (docker-compose config)

Вы можете выполнить docker-compose config (или docker compose config) —


Compose рассчитает финальную YAML-конфигурацию, подставив переменные
окружения.
Это полезно, чтобы проверить, верно ли подтянулись значения из .env, или
проверить, правильно ли прописались порты
Например вы увидите что-то такое:

services:
db:
environment:
MYSQL_ROOT_PASSWORD: supersecret
MYSQL_DATABASE: appdb
image: mysql:5.7
...
web:
image: "myorg/myweb:1.2"
ports:
- "8080:80"
...

Итог
.env файл — хранит пары KEY=value, которые Docker Compose считывает и
подставляет в [Link] (прямо в места с ${KEY}).
env_file: — ещё один способ указать, какие переменные окружения
передавать непосредственно внутрь контейнера.
docker-compose config — проверка, как итоговый YAML после подстановок
выглядит.

3/3
6.4 Практика

Базовое использование .env файлов


В этом задании вы научитесь использовать файл .env для хранения и подстановки
переменных окружения в Docker Compose.

Техническое задание
Необходимо создать простую конфигурацию Docker Compose, которая запускает
веб-сервер, используя переменные из файла .env для настройки порта и версии
образа.

Шаги для выполнения


1. Создайте файл [Link], который запускает сервис на базе образа
nginx. Версия образа должна задаваться через переменную окружения.
2. Настройте проброс портов для сервиса так, чтобы порт хоста задавался через
переменную окружения.
3. Создайте файл .env с определением нужных переменных.

Структура проекта

Подготовьте следующую структуру файлов:

[Link] — файл конфигурации Docker Compose


.env — файл с переменными окружения

Требования к решению

1. В .env файле должны быть определены две переменные:


NGINX_VERSION со значением 1.21-alpine
HOST_PORT со значением 8000
2. В [Link] необходимо:
Использовать образ nginx
Настроить проброс портов 80
Задать имя контейнера web-server
3. Версия docker-compose должна быть не ниже 3.0

Что нужно сдать?


1. Файл [Link]
2. Файл .env

Проверка работы

1/2
1. Проверьте конфигурацию командой:

docker-compose config

Вы должны увидеть в выводе подставленные значения переменных.


2. Запустите сервис:

docker-compose up -d

3. Убедитесь, что сервис работает, открыв в браузере [Link]

Подсказка (файл .env)

NGINX_VERSION=1.21-alpine
HOST_PORT=8000

Подсказка (структура [Link])

version: "3"

services:
web:
image: nginx:${NGINX_VERSION}
container_name: web-server
ports:
- "${HOST_PORT}:80"

Полное решение
Файл .env:

NGINX_VERSION=1.21-alpine
HOST_PORT=8000

Файл [Link]:

version: "3"

services:
web:
image: nginx:${NGINX_VERSION}
container_name: web-server
ports:
- "${HOST_PORT}:80"

2/2
24 марта 2025 г.

6.4 Практика

Использование .env файла с Docker Compose


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

Техническое задание
1. Создать веб-приложение, которое будет отображать информацию из
переменных окружения:
Базовый образ — nginx:alpine
Порт для проброса — должен настраиваться через переменную
окружения
Версия образа — должна настраиваться через переменную окружения
2. Настроить файл .env для хранения конфигурационных параметров

Структура проекта

Подготовьте следующую структуру файлов:

[Link] — файл Docker Compose, использующий переменные из


.env
.env — файл с переменными окружения
html/[Link] — простой HTML-файл для тестирования

Создайте файл html/[Link] со следующим содержимым:

1/4
<!DOCTYPE html>
<html>
<head>
<title>Тестирование .env в Docker Compose</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f0f0f0;
}
.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
}
.info {
margin-top: 20px;
padding: 10px;
background-color: #e7f3fe;
border-left: 3px solid #2196F3;
}
</style>
</head>
<body>
<div class="container">
<h1>Добро пожаловать!</h1>
<p>Эта страница обслуживается контейнером с Nginx.</p>

<div class="info">
<p><strong>Информация о контейнере:</strong></p>
<p>Вы успешно запустили Nginx через Docker Compose с использованием
.env файла!</p>
<p>Этот веб-сервер доступен через порт, указанный в .env.</p>
</div>
</div>
</body>
</html>

Что нужно сделать?

1. Создайте файл .env со следующими переменными:


NGINX_VERSION — версия образа Nginx (например, alpine)
WEB_PORT — порт, на котором будет доступен веб-сервер (например, 8080)
CONTAINER_NAME — имя контейнера (например, env_web_server)
2. Создайте файл [Link], который:
Использует переменные из .env файла для настройки сервиса
Монтирует локальную директорию ./html в контейнер
3. Запустите Docker Compose и проверьте, что сервис работает корректно
4. Измените значение переменной WEB_PORT в .env файле и перезапустите сервис

2/4
Проверка результата
1. Запустите сервис:

docker-compose up -d

2. Откройте браузер и перейдите по адресу [Link] (где XXXX —


значение переменной WEB_PORT)
3. Проверьте, что страница загружается корректно
4. Выполните команду для просмотра конфигурации с подставленными
переменными:

docker-compose config

5. Измените значение WEB_PORT в .env файле, перезапустите сервис и убедитесь,


что он доступен по новому порту

Подсказка 1 (содержимое .env файла)

# Версия Nginx
NGINX_VERSION=alpine

# Порт для веб-сервера


WEB_PORT=8080

# Имя контейнера
CONTAINER_NAME=env_web_server

Подсказка 2 (структура [Link])

version: "3"

services:
web:
image: nginx:${NGINX_VERSION}
container_name: ${CONTAINER_NAME}
ports:
- "${WEB_PORT}:80"
volumes:
- ./html:/usr/share/nginx/html

Полное решение
Файл .env:

# Версия Nginx
NGINX_VERSION=alpine

# Порт для веб-сервера


WEB_PORT=8080

# Имя контейнера
CONTAINER_NAME=env_web_server

Файл [Link]:

3/4
version: "3"

services:
web:
image: nginx:${NGINX_VERSION}
container_name: ${CONTAINER_NAME}
ports:
- "${WEB_PORT}:80"
volumes:
- ./html:/usr/share/nginx/html

Проверка конфигурации:

docker-compose config

Запуск сервиса:

docker-compose up -d

Затем изменяем значение переменной WEB_PORT в файле .env, например, на 9090, и


перезапускаем сервис:

docker-compose down
docker-compose up -d

После этого веб-сервер должен быть доступен по адресу [Link]

4/4
6.4 Практика

Настройка веб-приложения с БД через переменные окружения


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

Техническое задание

Необходимо создать конфигурацию Docker Compose, которая запускает веб-сервер


и базу данных MySQL. Все чувствительные и изменяемые параметры должны быть
вынесены в файл .env.

Шаги для выполнения


1. Создайте файл [Link], который запускает два сервиса: веб-
сервер на базе образа nginx и базу данных MySQL.
2. Настройте проброс портов для обоих сервисов через переменные окружения.
3. Настройте переменные окружения для MySQL (пароль root, имя базы данных,
пользователь, пароль пользователя).
4. Создайте файл .env с определением всех необходимых переменных.
5. Настройте именованный том для хранения данных MySQL.

Структура проекта

Подготовьте следующую структуру файлов:

[Link] — файл конфигурации Docker Compose


.env — файл с переменными окружения
html/[Link] — простой HTML-файл для тестирования веб-сервера

Содержимое файла html/[Link]:

1/5
<!DOCTYPE html>
<html>
<head>
<title>Docker Compose и переменные окружения</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f5f5f5;
}
.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
max-width: 800px;
margin: 0 auto;
}
h1 {
color: #333;
text-align: center;
}
.info {
border-left: 4px solid #2980b9;
padding: 10px;
background-color: #ebf5fb;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="container">
<h1>Успешно настроено!</h1>
<div class="info">
<p>Если вы видите эту страницу, значит:</p>
<ul>
<li>Веб-сервер успешно запущен</li>
<li>Переменные окружения корректно настроены</li>
<li>Порты проброшены правильно</li>
</ul>
</div>
<p>Теперь вы можете проверить подключение к базе данных MySQL.</p>
</div>
</body>
</html>

Требования к решению

2/5
1. В .env файле должны быть определены следующие переменные:
NGINX_VERSION=1.21-alpine — версия Nginx
WEB_PORT=8080 — порт для веб-сервера
MYSQL_VERSION=5.7 — версия MySQL
MYSQL_PORT=3306 — порт для MySQL
MYSQL_ROOT_PASSWORD=rootpassword — пароль root
MYSQL_DATABASE=appdb — имя базы данных
MYSQL_USER=appuser — имя пользователя
MYSQL_PASSWORD=apppassword — пароль пользователя
2. В [Link] необходимо:
Настроить сервис web на базе образа nginx:${NGINX_VERSION}
Пробросить порт ${WEB_PORT}:80 для сервиса web
Монтировать локальную директорию ./html в /usr/share/nginx/html
контейнера web
Настроить сервис db на базе образа mysql:${MYSQL_VERSION}
Пробросить порт ${MYSQL_PORT}:3306 для сервиса db (опционально)
Передать переменные окружения MySQL, используя подстановку из .env
Настроить именованный том mysql-data для сохранения данных
3. Версия docker-compose должна быть не ниже 3.0

Что нужно сдать?

1. Файл [Link]
2. Файл .env

Проверка работы

1. Проверьте конфигурацию командой:

docker-compose config

Вы должны увидеть в выводе подставленные значения переменных.


2. Запустите сервисы:

docker-compose up -d

3. Убедитесь, что веб-сервер работает, открыв в браузере


[Link]
4. Проверьте подключение к базе данных:

docker exec -it $(docker-compose ps -q db) mysql -u appuser -papppassword


appdb -e "SELECT 'Connection successful!' AS result;"

Подсказка (файл .env)

3/5
NGINX_VERSION=1.21-alpine
WEB_PORT=8080
MYSQL_VERSION=5.7
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_DATABASE=appdb
MYSQL_USER=appuser
MYSQL_PASSWORD=apppassword

Подсказка (структура [Link])

version: "3"

services:
web:
image: nginx:${NGINX_VERSION}
ports:
- "${WEB_PORT}:80"
volumes:
- ./html:/usr/share/nginx/html
depends_on:
- db

db:
image: mysql:${MYSQL_VERSION}
ports:
- "${MYSQL_PORT}:3306"
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql

volumes:
mysql-data:

Полное решение
Файл .env:

NGINX_VERSION=1.21-alpine
WEB_PORT=8080
MYSQL_VERSION=5.7
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_DATABASE=appdb
MYSQL_USER=appuser
MYSQL_PASSWORD=apppassword

Файл [Link]:

4/5
version: "3"

services:
web:
image: nginx:${NGINX_VERSION}
ports:
- "${WEB_PORT}:80"
volumes:
- ./html:/usr/share/nginx/html
depends_on:
- db

db:
image: mysql:${MYSQL_VERSION}
ports:
- "${MYSQL_PORT}:3306"
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql

volumes:
mysql-data:

5/5
6.5 Сети в Compose

Сети в Docker Compose


Когда вы описываете несколько сервисов в [Link], Docker
автоматически создаёт сеть по умолчанию и подключает к ней все контейнеры. Это
позволяет обращаться к сервисам по их именам (например, [Link] Но в
реальных проектах бывает нужно тонко управлять сетями — разделять сервисы на
разные подсети, делать внешние сети и т. д. В этом уроке разберём, как это
работает.

1. Сеть по умолчанию
Если вы ничего не указываете, Compose создаёт сеть в стиле <проект>_default и
все сервисы автоматически к ней подключаются. Внутри этой сети сервисы видят
друг друга по DNS-именам, совпадающим с названием сервиса.

version: "3.8"
services:
db:
image: postgres:14
web:
image: nginx:latest

Docker создаст сеть (например, myproject_default), при docker-compose up.


Сервис web может обращаться к db по адресу db:5432.

Минус: все сервисы находятся в одной сети, и некоторые приложения могут


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

2. Объявление собственных сетей


Чтобы разделить сервисы или задать конкретные параметры сети, в docker-
[Link] можно явно описать networks:.

1/5
version: "3.8"
services:
frontend:
image: nginx:latest
networks:
- frontend_net

backend:
image: myorg/backend:1.0
networks:
- backend_net
- frontend_net

db:
image: postgres:14
networks:
- backend_net

networks:
frontend_net:
driver: bridge
backend_net:
driver: bridge

Теперь у нас две сети: frontend_net и backend_net.


frontend (Nginx) в frontend_net, db в backend_net. Но backend (наш API)
находится в обеих сетях, значит он может общаться и с фронтендом, и с базой.
Таким образом, frontend не напрямую видит db (они в разных сетях), а
общение идёт через backend (если так задумано архитектурой).

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

3. Использование external сети

Иногда вам нужно, чтобы контейнеры подключались к уже существующей сети


Docker (вне Compose) или к сети, создаваемой в другом Compose-проекте. Тогда
прописываем external: true:

version: "3.8"
services:
app:
image: myorg/app
networks:
- existing_net

networks:
existing_net:
external: true

Здесь existing_net предполагает, что вы где-то уже создали сеть (например,


docker network create existing_net).

2/5
Compose не будет создавать или удалять её, а только подключит контейнер
app к сети.

Зачем это может быть нужно? Например, у вас есть внешний reverse proxy (Traefik
или Nginx) в другом Compose, и вы хотите, чтобы ваши контейнеры были в той же
сети, дабы автоматически обнаруживаться прокси.

4. Связь по DNS-названиям
Docker Compose поднимает встроенный DNS-сервер для сети. Если контейнеры
находятся в одной сети, они могут обращаться друг к другу по имени сервиса.
Пример:

services:
db:
image: mongo:4
api:
image: myorg/api
environment:
- DB_URL=mongodb://db:27017/mydb

Здесь переменная DB_URL указывает db как hostname. Работает, потому что db и api
в одной сети.

Важно: Если они подключены к разным сетям, то DNS-названия не будут видны.

5. Настройка IP-адресов

Если нужно определять статический IP-адрес или подсеть (например,


192.168.10.x), можно прописать ipam (IP Address Management). Это бывает нужно в
особо настроенных сетевых средах.

3/5
networks:
custom_net:
driver: bridge
ipam:
config:
- subnet: [Link]/24

Тогда контейнеры в этой сети будут получать IP из диапазона [Link]/24.


Можно даже указать конкретный IP сервису, но это делается через
container_name: и networks: → aliases: / ipv4_address:. Но это уже редкий
сценарий.

6. Небольшой пример c двумя сетями и разной доступностью

version: "3.8"

services:
frontend:
image: myorg/frontend
ports:
- "80:80"
networks:
- net_front

backend:
image: myorg/backend
networks:
- net_front
- net_back

db:
image: postgres:14
networks:
- net_back

networks:
net_front:
driver: bridge
net_back:
driver: bridge

frontend подключен только к net_front, открыт наружу на порт 80 (host:80 →


container:80).
db доступен только по net_back (не торчит наружу), его видит backend, потому
что backend и db делят net_back.
backend можно вызывать с frontend (одна сеть net_front), и backend может
достучаться до db (сеть net_back).

В результате frontend не может напрямую видеть db, а db не может случайно


торчать наружу. Сетевой трафик чётко распределён по двум сетям.

7. Вывод

4/5
По умолчанию, Compose создаёт одну default сеть. Это удобно для быстрых
стартов, но для более сложных случаев — объявляйте и настраивайте
networks.
Множественные сети позволяют изолировать сервисы и организовать
архитектуру: одни видны только в private сети (DB, backend), другие в
public (frontend).
external сети подключают контейнеры к уже существующей network, что
полезно для интеграции с внешними системами или общим reverse proxy.
DNS-резолвинг внутри сети идёт по имени сервиса, если они в одной сети.

5/5
6.6 Практика

Простая настройка сетей в Docker Compose


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

Техническое задание
Необходимо создать конфигурацию Docker Compose с тремя сервисами: веб-
сервер, API и база данных. Сервисы должны быть распределены по двум разным
сетям в соответствии со схемой безопасности.

Требования к настройке сетей


1. Веб-сервер (web) должен иметь доступ только к API, но не к базе данных.
2. API (api) должен иметь доступ как к веб-серверу, так и к базе данных.
3. База данных (db) должна быть видна только для API, но не для веб-сервера.

Структура проекта

Подготовьте следующую структуру файлов:

[Link] — файл конфигурации Docker Compose


[Link] — скрипт для проверки сетевых соединений
(предоставлен ниже)

Содержимое файла [Link]:

#!/bin/bash

echo "Проверка сетевых подключений..."


echo "1. Проверка подключения web → api..."
docker-compose exec web ping -c 2 api
echo "2. Проверка подключения web → db..."
docker-compose exec web ping -c 2 db
echo "3. Проверка подключения api → web..."
docker-compose exec api ping -c 2 web
echo "4. Проверка подключения api → db..."
docker-compose exec api ping -c 2 db
echo "Проверка завершена."

Что нужно сделать?

1. Создайте файл [Link] со следующими компонентами:


Сервис web на базе образа nginx:alpine
Сервис api на базе образа alpine
Сервис db на базе образа postgres:alpine
Две сети: frontend_network и backend_network

1/4
2. Настройте подключение сервисов к соответствующим сетям согласно
требованиям.
3. Добавьте к сервисам команду, чтобы контейнеры не завершались сразу после
запуска:
Для web и db это не требуется (они работают в фоне)
Для api добавьте команду tail -f /dev/null

Проверка результата

1. Запустите сервисы:

docker-compose up -d

2. Запустите скрипт проверки:

chmod +x [Link]
./[Link]

3. Проверьте результат. Успешная проверка должна показать:


web может подключиться к api
web НЕ может подключиться к db
api может подключиться к web
api может подключиться к db

Подсказка (структура [Link])

version: "3"

services:
web:
image: nginx:alpine
networks:
- frontend_network

api:
image: alpine
command: tail -f /dev/null
networks:
- ...
- ...

db:
image: postgres:alpine
environment:
POSTGRES_PASSWORD: example
networks:
- ...

networks:
frontend_network:
driver: bridge
backend_network:
driver: bridge

2/4
Полное решение
Файл [Link]:

version: "3"

services:
web:
image: nginx:alpine
networks:
- frontend_network

api:
image: alpine
command: tail -f /dev/null
networks:
- frontend_network
- backend_network

db:
image: postgres:alpine
environment:
POSTGRES_PASSWORD: example
networks:
- backend_network

networks:
frontend_network:
driver: bridge
backend_network:
driver: bridge

Объяснение:

web подключен только к frontend_network, поэтому видит только api, но не db.


api подключен к обеим сетям (frontend_network и backend_network), поэтому
может видеть и web, и db.
db подключен только к backend_network, поэтому его видит только api, но не
web.

Результат выполнения скрипта проверки:

3/4
Проверка сетевых подключений...
1. Проверка подключения web → api...
PING api ([Link]): 56 data bytes
64 bytes from [Link]: seq=0 ttl=64 time=0.116 ms
64 bytes from [Link]: seq=1 ttl=64 time=0.094 ms

2. Проверка подключения web → db...


ping: bad address 'db'

3. Проверка подключения api → web...


PING web ([Link]): 56 data bytes
64 bytes from [Link]: seq=0 ttl=64 time=0.070 ms
64 bytes from [Link]: seq=1 ttl=64 time=0.098 ms

4. Проверка подключения api → db...


PING db ([Link]): 56 data bytes
64 bytes from [Link]: seq=0 ttl=64 time=0.099 ms
64 bytes from [Link]: seq=1 ttl=64 time=0.099 ms

Проверка завершена.

4/4
Настройка внешней сети и межсервисного взаимодействия в
Docker Compose
В этом задании вы научитесь настраивать несколько сетей в Docker Compose,
включая внешнюю сеть, и организуете правильное взаимодействие между
сервисами.

Техническое задание
Необходимо создать конфигурацию Docker Compose для веб-приложения,
состоящего из четырех сервисов: веб-сервер, API, база данных и кэш. Сервисы
должны быть размещены в разных сетях с соблюдением принципов безопасности.

Требования к настройке сетей

1. Создать три сети:


public_network — сеть для внешнего доступа к приложению
app_network — внутренняя сеть для взаимодействия между
приложениями
db_network — закрытая сеть для доступа к хранилищам данных
2. Сеть public_network должна быть настроена как external
3. Распределить сервисы по сетям согласно следующим требованиям:
Веб-сервер (nginx) — должен быть в public_network и app_network
API (python-flask) — должен быть в app_network и db_network
База данных (postgres) — должна быть только в db_network
Кэш (redis) — должен быть только в db_network

Структура проекта

Подготовьте следующую структуру файлов:

[Link] — файл конфигурации Docker Compose


[Link] — скрипт для проверки сетевых соединений (предоставлен
ниже)

Содержимое файла [Link]:

1/6
#!/bin/bash

echo "=== Создание внешней сети ==="


docker network create public_network

echo "=== Запуск сервисов ==="


docker-compose up -d

echo "=== Проверка сетевой доступности ==="


echo "1. web -> api:"
docker-compose exec web ping -c 2 api
echo "2. web -> db:"
docker-compose exec web ping -c 2 db
echo "3. web -> cache:"
docker-compose exec web ping -c 2 cache
echo "4. api -> web:"
docker-compose exec api ping -c 2 web
echo "5. api -> db:"
docker-compose exec api ping -c 2 db
echo "6. api -> cache:"
docker-compose exec api ping -c 2 cache

echo "=== Проверка нахождения в нужных сетях ==="


echo "Сети для web:"
docker container inspect -f '{{range $net, $conf := .[Link]}}
{{$net}} {{end}}' $(docker-compose ps -q web)
echo "Сети для api:"
docker container inspect -f '{{range $net, $conf := .[Link]}}
{{$net}} {{end}}' $(docker-compose ps -q api)
echo "Сети для db:"
docker container inspect -f '{{range $net, $conf := .[Link]}}
{{$net}} {{end}}' $(docker-compose ps -q db)
echo "Сети для cache:"
docker container inspect -f '{{range $net, $conf := .[Link]}}
{{$net}} {{end}}' $(docker-compose ps -q cache)

Что нужно сделать?


1. Создайте файл [Link] со следующими компонентами:
Сервис web на базе образа nginx:alpine
Сервис api на базе образа python:3.9-alpine
Сервис db на базе образа postgres:13-alpine
Сервис cache на базе образа redis:alpine
Три сети с настройками согласно требованиям
2. Добавьте к сервисам web и api команду, которая предотвратит их завершение:
Для web это не требуется (nginx работает в фоне)
Для api используйте команду tail -f /dev/null
3. Настройте каждый сервис на подключение к нужным сетям
4. Настройте сеть public_network как внешнюю

Проверка результата

2/6
1. Запустите скрипт проверки:

chmod +x [Link]
./[Link]

2. Проверьте результаты. Ожидаемый результат:


web может подключиться к api
web НЕ может подключиться к db и cache
api может подключиться к web, db и cache
web находится в сетях public_network и app_network
api находится в сетях app_network и db_network
db и cache находятся только в сети db_network
3. Вы можете просмотреть все сети командой:

docker network ls

Подсказка (создание внешней сети)


Перед запуском docker-compose нужно создать внешнюю сеть:

docker network create public_network

В [Link] внешняя сеть объявляется так:

networks:
public_network:
external: true
app_network:
driver: bridge
db_network:
driver: bridge

Подсказка (структура [Link])

3/6
version: "3"

services:
web:
image: nginx:alpine
networks:
- public_network
- app_network

api:
image: python:3.9-alpine
command: tail -f /dev/null
networks:
- ...
- ...

db:
image: postgres:13-alpine
environment:
POSTGRES_PASSWORD: example
networks:
- ...

cache:
image: redis:alpine
networks:
- ...

networks:
public_network:
external: true
app_network:
driver: bridge
db_network:
driver: bridge

Полное решение
Файл [Link]:

4/6
version: "3"

services:
web:
image: nginx:alpine
networks:
- public_network
- app_network

api:
image: python:3.9-alpine
command: tail -f /dev/null
networks:
- app_network
- db_network

db:
image: postgres:13-alpine
environment:
POSTGRES_PASSWORD: example
networks:
- db_network

cache:
image: redis:alpine
networks:
- db_network

networks:
public_network:
external: true
app_network:
driver: bridge
db_network:
driver: bridge

Объяснение:

web подключен к public_network и app_network, что позволяет ему быть


доступным извне и взаимодействовать с API.
api подключен к app_network и db_network, что позволяет ему
взаимодействовать с веб-сервером и обращаться к базе данных и кэшу.
db и cache подключены только к db_network, что изолирует их от внешнего
мира и позволяет обращаться к ним только через API.
Сеть public_network помечена как external: true, что означает, что она
должна быть создана заранее и не будет автоматически удалена при
выполнении docker-compose down.

Результат выполнения скрипта проверки должен подтвердить, что:

web может подключиться к api (успешный ping)


web НЕ может подключиться к db и cache (ошибка "bad address")
api может подключиться к web, db и cache (успешный ping во всех случаях)

5/6
Каждый сервис находится в правильных сетях

6/6
Создание многоуровневого приложения с изолированными
сетями
В этом задании вы научитесь настраивать изолированные сети в Docker Compose
для многоуровневого приложения, реализуя принцип разделения доступа.

Техническое задание

Создайте многоуровневое приложение, состоящее из трех компонентов:

1. Сервис 1 (фронтенд):
Базовый образ — nginx:alpine
Должен быть доступен извне по порту 8080
Должен иметь доступ только к API-серверу, но не к базе данных
2. Сервис 2 (API-сервер):
Базовый образ — python:3.9-slim
Должен иметь доступ к базе данных
Должен быть виден фронтенду
Не должен быть доступен извне напрямую
3. Сервис 3 (база данных):
Базовый образ — postgres:13-alpine
Должна быть доступна только для API-сервера
Не должна быть доступна извне или для фронтенда

Структура проекта
Создайте следующую структуру файлов:

[Link] — основной файл конфигурации с настройкой сетей


frontend/[Link] — конфигурация Nginx для проксирования запросов к
API
frontend/html/[Link] — простой HTML-файл для тестирования
api/[Link] — простой Python API, который подключается к базе данных
api/[Link] — зависимости Python

Создайте файл frontend/[Link] со следующим содержимым:

1/9
events {
worker_connections 1024;
}

http {
server {
listen 80;

location / {
root /usr/share/nginx/html;
index [Link];
}

location /api/ {
proxy_pass [Link]
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}

Создайте файл frontend/html/[Link] со следующим содержимым:

2/9
<!DOCTYPE html>
<html>
<head>
<title>Тестирование сетей в Docker Compose</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f0f0f0;
}
.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
}
.success {
color: green;
}
.error {
color: red;
}
button {
padding: 10px 15px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<div class="container">
<h1>Тестирование сетей в Docker Compose</h1>

<div>
<button onclick="testApi()">Проверить API</button>
<button onclick="testDb()">Проверить прямой доступ к БД</button>
</div>

<div id="result" style="margin-top: 20px;">


<p>Нажмите кнопки выше, чтобы проверить соединения...</p>
</div>
</div>

<script>
function testApi() {
[Link]('result').innerHTML = '<p>Проверка соединения

3/9
с API...</p>';

fetch('/api/status')
.then(response => [Link]())
.then(data => {


[Link]('result').innerHTML =
`<p class="success"> Соединение с API успешно!</p>
<p>API ответил: ${[Link](data)}</p>`;
})
.catch(error => {


[Link]('result').innerHTML =
`<p class="error"> Ошибка соединения с API:
${[Link]}</p>`;
});
}

function testDb() {
[Link]('result').innerHTML = '<p>Проверка прямого
соединения с БД...</p>';

fetch('/db-test')
.then(response => [Link]())
.then(data => {


[Link]('result').innerHTML =
`<p class="error"> Прямое соединение с БД не должно быть
доступно, но получен ответ!</p>`;
})
.catch(error => {


[Link]('result').innerHTML =
`<p class="success"> Правильно! Прямое соединение с БД
недоступно, что и ожидалось.</p>`;
});
}
</script>
</body>
</html>

Создайте файл api/[Link] со следующим содержимым:

4/9
from flask import Flask, jsonify
import psycopg2
import socket
import os

app = Flask(__name__)

@[Link]('/')
def index():
return jsonify({"message": "API работает!"})

@[Link]('/status')
def status():
# Получаем имя хоста для идентификации контейнера
hostname = [Link]()

# Проверяем соединение с базой данных


db_status = "недоступна"
try:
# Попытка подключения к базе данных
conn = [Link](
host=[Link]("DB_HOST", "db"),
database=[Link]("DB_NAME", "postgres"),
user=[Link]("DB_USER", "postgres"),
password=[Link]("DB_PASSWORD", "postgres")
)
[Link]()
db_status = "доступна"
except Exception as e:
db_status = f"недоступна: {str(e)}"

return jsonify({
"status": "ok",
"container": hostname,
"db_status": db_status
})

if __name__ == '__main__':
[Link](host='[Link]', port=5000)

Создайте файл api/[Link] со следующим содержимым:

flask==2.0.1
psycopg2-binary==2.9.1

Что нужно сделать?

5/9
1. Создайте [Link] файл, в котором:
Определите три сервиса (frontend, api, db) согласно требованиям
Создайте две сети: frontend_network и backend_network
Подключите сервисы к сетям по схеме:
frontend → только к frontend_network
api → к обеим сетям
db → только к backend_network
Настройте проброс портов только для frontend (порт 8080)
2. Запустите Docker Compose и проверьте, что:
Фронтенд доступен по адресу [Link]
API доступен через фронтенд по адресу
[Link]
База данных не доступна напрямую из фронтенда

Проверка результата
1. Запустите сервисы:

docker-compose up -d

2. Откройте браузер и перейдите по адресу [Link]


3. Нажмите кнопку "Проверить API" — должен быть успешный ответ от API-
сервера
4. Нажмите кнопку "Проверить прямой доступ к БД" — запрос должен
завершиться ошибкой, поскольку прямой доступ к базе данных должен быть
невозможен
5. Выполните команду для просмотра созданных сетей:

docker network ls | grep network

6. Проверьте, к каким сетям подключен каждый контейнер:

docker inspect frontend | grep NetworkMode


docker inspect api | grep NetworkMode
docker inspect db | grep NetworkMode

Подсказка 1 (структура [Link])

6/9
version: "3"

services:
frontend:
# настройка фронтенда...
networks:
- frontend_network

api:
# настройка API...
networks:
- frontend_network
- backend_network

db:
# настройка базы данных...
networks:
- backend_network

networks:
frontend_network:
driver: bridge
backend_network:
driver: bridge

Подсказка 2 (настройка сервисов)

frontend:
image: nginx:alpine
volumes:
- ./frontend/[Link]:/etc/nginx/[Link]:ro
- ./frontend/html:/usr/share/nginx/html
ports:
- "8080:80"
networks:
- frontend_network

api:
build: ./api
# или можно использовать готовый образ python
# image: python:3.9-slim
# volumes, command и т.д.
networks:
- frontend_network
- backend_network

Полное решение
Файл [Link]:

7/9
version: "3"

services:
frontend:
image: nginx:alpine
container_name: network_frontend
volumes:
- ./frontend/[Link]:/etc/nginx/[Link]:ro
- ./frontend/html:/usr/share/nginx/html
ports:
- "8080:80"
networks:
- frontend_network

api:
image: python:3.9-slim
container_name: network_api
working_dir: /app
volumes:
- ./api:/app
command: sh -c "pip install -r [Link] && python [Link]"
environment:
- DB_HOST=db
- DB_NAME=postgres
- DB_USER=postgres
- DB_PASSWORD=postgres
networks:
- frontend_network
- backend_network

db:
image: postgres:13-alpine
container_name: network_db
environment:
- POSTGRES_PASSWORD=postgres
volumes:
- db_data:/var/lib/postgresql/data
networks:
- backend_network

networks:
frontend_network:
driver: bridge
backend_network:
driver: bridge

volumes:
db_data:

После запуска с этой конфигурацией:

1. Фронтенд будет доступен по адресу [Link]

2. API будет доступен через фронтенд по пути /api/.

8/9
3. База данных будет изолирована в backend_network и доступна только для API-
сервиса.

4. Прямой доступ от фронтенда к базе данных будет невозможен из-за сетевой


изоляции.

При нажатии на кнопку "Проверить API" в веб-интерфейсе вы увидите, что API


доступен, а попытка прямого доступа к БД завершится ошибкой, что подтверждает
правильность настройки сетей.

Дополнительные задания
1. Добавьте еще один сервис adminer или pgadmin для управления базой данных,
который будет находиться в backend_network, но также будет доступен извне
через порт 8081.
2. Создайте третью сеть monitoring_network и добавьте сервис мониторинга
(например, Prometheus), который сможет собирать метрики со всех сервисов.
3. Настройте использование network_mode: host для одного из сервисов и
проверьте, как это влияет на изоляцию.

9/9
6.7 Рестарт-политики

Рестарт-политики в Docker Compose


При работе с длительно живущими сервисами (например, базы данных, веб-
серверы) может возникнуть ситуация, когда контейнер по какой-то причине
завершается или падает (ошибка приложения, временная недоступность). Рестарт-
политики помогают автоматически перезапустить контейнер без вмешательства
человека, обеспечивая большую отказоустойчивость. В Docker Compose (формата
2.x/3.x) вы можете задать рестарт-политику для каждого сервиса.

1. Какие бывают рестарт-политики?

no — (по умолчанию) Docker не будет перезапускать контейнер, если он


упадёт или завершится.
on-failure — контейнер перезапускается только если процесс завершился с
неудачным кодом (ненулевым). Если же контейнер нормально завершился
(код 0), то рестарт не происходит.
always — контейнер будет перезапущен при любой остановке, даже если
процесс завершился успешно. Это удобно для сервисов, которые должны
всегда работать.
unless-stopped — тоже перезапускается при любой остановке, но не будет
запускаться заново, если вы явно остановили контейнер (docker stop) или при
перезапуске Docker. То есть перезапускается, пока вы сами не остановили
навсегда.

Таким образом, вы подбираете подходящую политику в зависимости от того, какой


характер у приложения и нужно ли насильно держать контейнер в живых.

2. Как указать рестарт-политику в [Link]?


Внутри секции сервиса просто добавьте ключ restart: с одним из значений:

version: "3.8"

services:
web:
image: nginx:latest
ports:
- "8080:80"
restart: always

Теперь, если контейнер web внезапно упадёт, Docker автоматически


перезапустит его. То же самое при перезагрузке демона Docker — контейнер
снова запустится.
Если вы используете compose v2 (как плагин), синтаксис тот же.

1/3
3. Краткие примеры

on-failure:

services:
db:
image: postgres:14
restart: on-failure

Если Postgres аварийно упадёт из-за ошибки, Compose/ Docker попробует его
перезапустить. Но если вы вручную остановили контейнер, он не будет заново
запускаться.

unless-stopped:

2/3
services:
app:
build: .
restart: unless-stopped

Поднимется, пока вы сами не остановите — если контейнер завершился с ошибкой,


перезапустится; при перезагрузке Docker-хоста тоже встанет. Но если вы сделали
docker-compose stop, то при следующем перезапуске Docker контейнер не оживёт
сам.

4. Когда это нужно?

Долговременные сервисы (веб-сервисы, базы данных, очереди). Если они


внезапно падают, вы хотите их «автоматически поднять» без ручного
вмешательства.
Разработка: можно настроить restart: on-failure, чтобы контейнер при
отладке перезапускался только если выловили ошибку. Или, наоборот, no,
если не хотите лишнего поведения.
Продакшн-среда: часто используют always или unless-stopped, чтобы
минимизировать простой сервиса.

5. Резюме

Рестарт-политики в Docker Compose — это способ управлять тем, как


контейнер ведёт себя при сбоях и остановках:
no — не перезапускать.
on-failure — перезапускать только при неудачном завершении.
always — поднимать заново при любой остановке (хоть с ошибкой, хоть
нормально).
unless-stopped — как always, но если контейнер остановлен вручную, не
перезапускается при рестарте Docker.
Указывать можно прямо в сервисе: restart: always.
Это даёт дополнительную стабильность сервисам, избавляя от необходимости
вручную поднимать контейнер при случайном сбое.

3/3
Настройка рестарт-политик для повышения
отказоустойчивости
В этом задании вы научитесь настраивать различные рестарт-политики в Docker
Compose для обеспечения стабильности работы сервисов.

Техническое задание

Необходимо создать Docker Compose файл с тремя различными сервисами, каждый


с определенной рестарт-политикой, и проверить их поведение при различных
сценариях остановки.

1. Веб-сервис (nginx) - должен автоматически перезапускаться всегда, даже


после явной остановки или перезагрузки Docker
2. Демо-приложение (простой скрипт) - должно перезапускаться только при
сбое (ненулевом коде выхода)
3. База данных (MySQL) - должна перезапускаться автоматически, но не после
явной остановки

Структура проекта

Подготовьте следующую структуру файлов:

[Link] - основной файл конфигурации


app/[Link] - скрипт, который мы будем использовать для тестирования
различных сценариев завершения

Создайте файл app/[Link] со следующим содержимым:

#!/bin/sh

echo "Демо-приложение запущено"


echo "Параметр EXIT_CODE = $EXIT_CODE"

# Проверяем, передана ли переменная окружения EXIT_CODE


if [ -z "$EXIT_CODE" ]; then
echo "Переменная EXIT_CODE не установлена. Приложение будет работать
бесконечно."
# Бесконечный цикл
while true; do
echo "Приложение работает... $(date)"
sleep 10
done
else
echo "Приложение завершится с кодом $EXIT_CODE через 5 секунд."
sleep 5
echo "Приложение завершается."
exit $EXIT_CODE
fi

1/5
Сделайте скрипт исполняемым:

chmod +x app/[Link]

Что нужно сделать?

1. Создайте [Link] файл с тремя сервисами и соответствующими


рестарт-политиками:
web с политикой always
app с политикой on-failure
db с политикой unless-stopped
2. Запустите сервисы и проверьте их поведение при разных сценариях
остановки.

Проверка результатов

1. Запустите все сервисы:

docker-compose up -d

2. Проверьте, что все контейнеры запущены:

docker-compose ps

3. Тест 1: Проверка политики on-failure


Сначала остановите приложение с успешным кодом:

docker-compose stop app


docker-compose run -e EXIT_CODE=0 app

Приложение должно завершиться и НЕ перезапуститься.


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

docker-compose run -e EXIT_CODE=1 app

Приложение должно завершиться с ошибкой и автоматически


перезапуститься.
4. Тест 2: Проверка политики always и unless-stopped
Остановите все сервисы:

docker-compose stop

Перезапустите Docker (или используйте команду):

systemctl restart docker

(или эквивалент для вашей системы)


Проверьте статус контейнеров:

docker-compose ps

Контейнер web должен быть запущен автоматически, а db - нет.

2/5
Дополнительное задание (опционально)
Модифицируйте скрипт [Link] таким образом, чтобы он непрерывно наращивал
потребление памяти до тех пор, пока контейнер не будет остановлен из-за нехватки
ресурсов. Проверьте, как работает рестарт-политика on-failure в этом случае.

Подсказка ([Link])

version: "3.8"

services:
web:
image: nginx:alpine
restart: always
ports:
- "8080:80"

app:
image: alpine:latest
restart: on-failure
volumes:
- ./app:/app
working_dir: /app
command: ./[Link]

db:
image: mysql:5.7
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: testdb

Полное решение
Файл [Link]:

3/5
version: "3.8"

services:
web:
image: nginx:alpine
restart: always
ports:
- "8080:80"
container_name: restart-web

app:
image: alpine:latest
restart: on-failure
volumes:
- ./app:/app
working_dir: /app
command: ./[Link]
container_name: restart-app

db:
image: mysql:5.7
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: testdb
container_name: restart-db

Выполнение тестов:

1. Запускаем все сервисы:

docker-compose up -d

2. Проверяем, что все контейнеры запущены:

docker-compose ps

3. Тест 1: Проверка политики on-failure


Останавливаем app и запускаем его с успешным кодом (0):

docker-compose stop app


docker-compose run -e EXIT_CODE=0 --name test-success app

Видим, что контейнер завершился и НЕ перезапустился.


Запускаем приложение с ошибкой (код 1):

docker-compose run -e EXIT_CODE=1 --name test-failure app

Видим, что контейнер завершился с ошибкой и был автоматически


перезапущен.

4/5
4. Тест 2: Проверка политики always и unless-stopped
Останавливаем все сервисы:

docker-compose stop

Перезапускаем Docker:

sudo systemctl restart docker

Проверяем статус контейнеров:

docker-compose ps

Видим, что контейнер web (с политикой always) был запущен


автоматически, а db (с политикой unless-stopped) - нет, так как он был
явно остановлен.

Результаты тестов подтверждают, что рестарт-политики работают согласно


описанию в документации Docker:

always - контейнер перезапускается всегда, даже после явной остановки


on-failure - контейнер перезапускается только при завершении с ненулевым
кодом
unless-stopped - контейнер перезапускается автоматически, но не после
явной остановки

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

Техническое задание
Вы создадите несколько сервисов с разными рестарт-политиками и проверите их
поведение в различных сценариях.

1. Сервис 1 (без перезапуска):


Политика: no
Базовый образ — просто контейнер, который быстро завершается
2. Сервис 2 (перезапуск при ошибке):
Политика: on-failure
Должен перезапускаться только при аварийном завершении
3. Сервис 3 (всегда перезапускать):
Политика: always
Должен перезапускаться при любом завершении
4. Сервис 4 (перезапуск, если не остановлен вручную):
Политика: unless-stopped
Должен перезапускаться при любом завершении, кроме ручной
остановки

Структура проекта

Создайте следующую структуру файлов:

[Link] — основной файл конфигурации с сервисами и рестарт-


политиками
test-success/[Link] — скрипт, который успешно завершается (с кодом 0)
test-failure/[Link] — скрипт, который завершается с ошибкой (с ненулевым
кодом)
test-server/[Link] — простой веб-сервер, который работает постоянно

Создайте файл test-success/[Link] со следующим содержимым:

1/8
#!/bin/sh
echo "Запуск сервиса с успешным завершением..."
echo "Сервис будет работать 10 секунд, затем завершится с кодом 0 (успех)"

# Выводим информацию о контейнере


echo "Имя контейнера: $HOSTNAME"
echo "Рестарт-политика: $RESTART_POLICY"

# Работаем 10 секунд
sleep 10

echo "Сервис успешно завершил работу с кодом 0"


exit 0

Создайте файл test-failure/[Link] со следующим содержимым:

#!/bin/sh
echo "Запуск сервиса с аварийным завершением..."
echo "Сервис будет работать 10 секунд, затем завершится с кодом 1 (ошибка)"

# Выводим информацию о контейнере


echo "Имя контейнера: $HOSTNAME"
echo "Рестарт-политика: $RESTART_POLICY"

# Работаем 10 секунд
sleep 10

echo "Сервис аварийно завершил работу с кодом 1"


exit 1

Создайте файл test-server/[Link] со следующим содержимым:

#!/usr/bin/env python3
import [Link]
import socketserver
import os
import time

PORT = 8000

class Handler([Link]):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()

message = f"""

Создайте файл test-server/Dockerfile со следующим содержимым:

2/8
FROM python:3.9-slim

WORKDIR /app
COPY [Link] .
RUN chmod +x [Link]

EXPOSE 8000

CMD ["./[Link]"]

Что нужно сделать?

1. Создайте [Link] файл с четырьмя сервисами, каждый с разной


рестарт-политикой:
no-restart — с политикой no
restart-on-failure — с политикой on-failure
restart-always — с политикой always
restart-unless-stopped — с политикой unless-stopped
2. Настройте каждый сервис так, чтобы он выводил информацию о себе в логи
3. Проведите тесты для проверки работы рестарт-политик в разных ситуациях

План тестирования

1. Тест 1: Проверка поведения при успешном завершении


Запустите контейнеры, которые успешно завершаются (код 0)
Проверьте, какие из них перезапустятся автоматически
2. Тест 2: Проверка поведения при аварийном завершении
Запустите контейнеры, которые завершаются с ошибкой (код не 0)
Проверьте, какие из них перезапустятся автоматически
3. Тест 3: Проверка поведения при ручной остановке
Запустите контейнеры с веб-сервером
Вручную остановите их с помощью docker stop
Перезапустите Docker и проверьте, какие контейнеры запустятся
автоматически

Рекомендуемое содержимое [Link]


Файл [Link] должен выглядеть примерно так:

3/8
version: "3"

services:
# Сервис с политикой no restart
no-restart-success:
image: alpine:latest
container_name: no-restart-success
volumes:
- ./test-success:/app
working_dir: /app
command: sh [Link]
environment:
- RESTART_POLICY=no
restart: "no"

no-restart-failure:
image: alpine:latest
container_name: no-restart-failure
volumes:
- ./test-failure:/app
working_dir: /app
command: sh [Link]
environment:
- RESTART_POLICY=no
restart: "no"

# Сервис с политикой on-failure


on-failure-success:
image: alpine:latest
container_name: on-failure-success
volumes:
- ./test-success:/app
working_dir: /app
command: sh [Link]
environment:
- RESTART_POLICY=on-failure
restart: on-failure

on-failure-failure:
image: alpine:latest
container_name: on-failure-failure
volumes:
- ./test-failure:/app
working_dir: /app
command: sh [Link]
environment:
- RESTART_POLICY=on-failure
restart: on-failure

# Сервис с политикой always


always-success:
image: alpine:latest
container_name: always-success
volumes:
- ./test-success:/app
working_dir: /app

4/8
command: sh [Link]
environment:
- RESTART_POLICY=always
restart: always

always-failure:
image: alpine:latest
container_name: always-failure
volumes:
- ./test-failure:/app
working_dir: /app
command: sh [Link]
environment:
- RESTART_POLICY=always
restart: always

# Веб-сервер с политикой unless-stopped


unless-stopped-server:
build: ./test-server
container_name: unless-stopped-server
ports:
- "8001:8000"
environment:
- RESTART_POLICY=unless-stopped
restart: unless-stopped

# Веб-сервер с политикой always


always-server:
build: ./test-server
container_name: always-server
ports:
- "8002:8000"
environment:
- RESTART_POLICY=always
restart: always

Проверка результатов
Выполните следующие действия для проверки работы рестарт-политик:

Тест 1: Проверка поведения при успешном завершении

1. Запустите контейнеры:

docker-compose up -d no-restart-success on-failure-success always-success

2. Посмотрите логи, чтобы увидеть, как контейнеры завершаются:

docker-compose logs -f

3. Проверьте статус контейнеров через некоторое время:

docker-compose ps

5/8
4. Ожидаемый результат:
no-restart-success — запустится, отработает и остановится
on-failure-success — запустится, отработает и остановится (НЕ
перезапустится, так как код возврата 0)
always-success — будет постоянно перезапускаться после успешного
завершения

Тест 2: Проверка поведения при аварийном завершении

1. Запустите контейнеры:

docker-compose up -d no-restart-failure on-failure-failure always-failure

2. Посмотрите логи, чтобы увидеть, как контейнеры завершаются:

docker-compose logs -f

3. Проверьте статус контейнеров через некоторое время:

docker-compose ps

4. Ожидаемый результат:
no-restart-failure — запустится, отработает и остановится с ошибкой
on-failure-failure — будет перезапускаться после аварийного
завершения
always-failure — будет перезапускаться после аварийного завершения

Тест 3: Проверка поведения с веб-серверами

1. Запустите веб-серверы:

docker-compose up -d unless-stopped-server always-server

2. Проверьте, что оба сервера доступны по адресам:


[Link] — unless-stopped-server
[Link] — always-server
3. Отправьте запрос на аварийное завершение первого сервера:

curl -X POST [Link]

4. Проверьте, что сервер перезапустился (должен быть доступен через


некоторое время)
5. Остановите оба сервера вручную:

docker stop unless-stopped-server always-server

6/8
6. Перезапустите Docker:

# На Linux
sudo systemctl restart docker

# На Windows/Mac
# Перезапустите Docker Desktop

7. После перезапуска Docker проверьте статус контейнеров:

docker-compose ps

8. Ожидаемый результат:
always-server — должен запуститься автоматически после перезапуска
Docker
unless-stopped-server — НЕ должен запуститься автоматически, так как
был остановлен вручную

Таблица результатов

Заполните таблицу результатов для разных рестарт-политик:

Перезапуск после Перезапуск после


успешного аварийного Перезапуск после
Политика завершения завершения ручной остановки

no ❌ Нет ❌ Нет ❌ Нет


on-
failure
❌ Нет ✅ Да ❌ Нет
always ✅ Да ✅ Да ✅ Да
unless-
stopped
✅ Да ✅ Да ❌ Нет
Выводы
После выполнения этой практики вы должны понимать:

1. Когда и какую рестарт-политику использовать для разных типов сервисов


2. Разницу между разными типами рестарт-политик в Docker
3. Как настроить отказоустойчивость для длительно работающих сервисов

Дополнительные задания

1. Добавьте параметр restart_policy.max_attempts для ограничения количества


попыток перезапуска
2. Создайте сценарий, когда один сервис зависит от другого, и проверьте, как
работают рестарт-политики при остановке зависимого сервиса

7/8
3. Настройте мониторинг перезапусков контейнеров с помощью команды docker
events

8/8
6.9 Профили в Compose

Профили в Docker Compose


В предыдущих уроках мы познакомились с базовым использованием Docker
Compose: как описывать сервисы в [Link], подключать тома, сети и
переменные окружения. Теперь посмотрим на более продвинутые приёмы, которые
упрощают работу со сложными стэками. Начнём с профилей (profiles) и вспомним
несколько важных команд (docker-compose up -d, docker-compose logs) с их
дополнительными опциями.

1. Профили (profiles)
С версии 3.9+ формата Docker Compose (и Docker Compose V2) можно
использовать профили (profiles), чтобы избирательно запускать некоторые
сервисы. Это удобно, когда у вас в [Link] прописано много сервисов,
но не все нужны в каждой среде.

Как это выглядит

version: "3.9"
services:
db:
image: postgres:14
profiles:
- default
cache:
image: redis:latest
profiles:
- dev
web:
image: nginx:latest
profiles:
- default
- dev

Сервис db имеет профиль default, значит запускается по умолчанию, если вы


просто делаете docker-compose up (или docker compose up) без указания
профилей.
Сервис cache и web имеют профиль dev, то есть поднимутся только если
включить профиль dev.

Как включить профиль при запуске

docker compose --profile dev up

Это значит, что Compose поднимет все сервисы у которых profiles: [dev]
ИЛИ profiles: [default] (профиль default включен всегда).

1/2
Таким образом, вы можете выключить или включить часть сервисов, не правя
постоянно YAML. Удобно для разных окружений (dev, test, staging), или для
вспомогательных сервисов (логирование, мониторинг), которые не нужны во
всех средах.

2/2
6.10 Практика

Использование профилей в Docker Compose


В этом задании вы научитесь использовать профили в Docker Compose для
избирательного запуска сервисов в зависимости от окружения.

Техническое задание
Необходимо создать Docker Compose конфигурацию для веб-приложения, которое
будет работать в трех разных режимах:

1. Режим разработки (dev) - включает все компоненты для удобства разработки


2. Режим тестирования (test) - включает основные компоненты и инструменты
для тестирования
3. Режим продакшн (prod) - включает только необходимые компоненты

Структура проекта

Подготовьте файл [Link] со следующими сервисами:

app - основное приложение (должно запускаться во всех режимах)


db - база данных (должна запускаться во всех режимах)
adminer - инструмент для управления базой данных (только для dev и test)
cache - Redis кэш (для dev и prod)
test-runner - сервис для запуска тестов (только для test)

Требования к решению
1. Используйте Docker Compose формата версии 3.9 или выше
2. Настройте профили для каждого сервиса в соответствии с требованиями
3. Сервис app и db должны запускаться во всех профилях
4. Сервис adminer должен запускаться только в профилях dev и test
5. Сервис cache должен запускаться в профилях dev и prod
6. Сервис test-runner должен запускаться только в профиле test

Что нужно сделать?


1. Создайте файл [Link] с необходимыми сервисами и профилями
2. Проверьте работу профилей, запустив docker-compose с разными профилями
3. Убедитесь, что в каждом режиме запускаются только нужные сервисы

Проверка результатов

Запустите следующие команды для проверки работы профилей:

1/5
1. Запуск всех сервисов (режим разработки):

docker-compose --profile dev up -d

2. Проверьте, какие сервисы запущены:

docker-compose ps

Должны быть запущены: app, db, adminer, cache


3. Остановите все сервисы:

docker-compose down

4. Запустите сервисы в режиме тестирования:

docker-compose --profile test up -d

5. Проверьте, какие сервисы запущены:

docker-compose ps

Должны быть запущены: app, db, adminer, test-runner


6. Остановите все сервисы:

docker-compose down

7. Запустите сервисы в режиме продакшн:

docker-compose --profile prod up -d

8. Проверьте, какие сервисы запущены:

docker-compose ps

Должны быть запущены: app, db, cache

Таблица ожидаемых результатов

Сервис Профиль dev Профиль test Профиль prod

app ✅ ✅ ✅
db ✅ ✅ ✅
adminer ✅ ✅ ❌
cache ✅ ❌ ✅
test-runner ❌ ✅ ❌
Подсказка (структура [Link])

2/5
version: "3.9"

services:
app:
image: nginx:alpine
profiles:
- dev
- test
- prod
# другие настройки...

db:
image: postgres:13-alpine
profiles:
- dev
- test
- prod
# другие настройки...

# Остальные сервисы...

Полное решение

3/5
version: "3.9"

services:
app:
image: nginx:alpine
profiles:
- dev
- test
- prod
ports:
- "8080:80"

db:
image: postgres:13-alpine
profiles:
- dev
- test
- prod
environment:
POSTGRES_PASSWORD: example
POSTGRES_DB: app_db

adminer:
image: adminer:latest
profiles:
- dev
- test
ports:
- "8081:8080"

cache:
image: redis:alpine
profiles:
- dev
- prod
ports:
- "6379:6379"

test-runner:
image: alpine:latest
profiles:
- test
command: echo "Running tests..." && sleep 10 && echo "Tests completed!"

Для запуска в разных режимах используйте следующие команды:

Режим разработки: docker-compose --profile dev up -d


Режим тестирования: docker-compose --profile test up -d
Режим продакшн: docker-compose --profile prod up -d

В режиме разработки будут запущены сервисы: app, db, adminer, cache

В режиме тестирования будут запущены сервисы: app, db, adminer, test-runner

В режиме продакшн будут запущены сервисы: app, db, cache

4/5
Напишите текст
Верно решили 10 учащихся

Из всех попыток 100% верных

15 баллов за решение.

5/5
6.10 Практика

Использование профилей в Docker Compose


В этом задании вы научитесь использовать профили в Docker Compose для
избирательного запуска сервисов в зависимости от окружения.

Техническое задание
Необходимо создать Docker Compose конфигурацию для веб-приложения, которое
будет работать в трех разных режимах:

1. Режим разработки (dev) - включает все компоненты для удобства разработки


2. Режим тестирования (test) - включает основные компоненты и инструменты
для тестирования
3. Режим продакшн (prod) - включает только необходимые компоненты

Структура проекта

Подготовьте файл [Link] со следующими сервисами:

app - основное приложение (должно запускаться во всех режимах)


db - база данных (должна запускаться во всех режимах)
adminer - инструмент для управления базой данных (только для dev и test)
cache - Redis кэш (для dev и prod)
test-runner - сервис для запуска тестов (только для test)

Требования к решению
1. Используйте Docker Compose формата версии 3.9 или выше
2. Настройте профили для каждого сервиса в соответствии с требованиями
3. Сервис app и db должны запускаться во всех профилях
4. Сервис adminer должен запускаться только в профилях dev и test
5. Сервис cache должен запускаться в профилях dev и prod
6. Сервис test-runner должен запускаться только в профиле test

Что нужно сделать?


1. Создайте файл [Link] с необходимыми сервисами и профилями
2. Проверьте работу профилей, запустив docker-compose с разными профилями
3. Убедитесь, что в каждом режиме запускаются только нужные сервисы

Проверка результатов

Запустите следующие команды для проверки работы профилей:

1/5
1. Запуск всех сервисов (режим разработки):

docker-compose --profile dev up -d

2. Проверьте, какие сервисы запущены:

docker-compose ps

Должны быть запущены: app, db, adminer, cache


3. Остановите все сервисы:

docker-compose down

4. Запустите сервисы в режиме тестирования:

docker-compose --profile test up -d

5. Проверьте, какие сервисы запущены:

docker-compose ps

Должны быть запущены: app, db, adminer, test-runner


6. Остановите все сервисы:

docker-compose down

7. Запустите сервисы в режиме продакшн:

docker-compose --profile prod up -d

8. Проверьте, какие сервисы запущены:

docker-compose ps

Должны быть запущены: app, db, cache

Таблица ожидаемых результатов

Сервис Профиль dev Профиль test Профиль prod

app ✅ ✅ ✅
db ✅ ✅ ✅
adminer ✅ ✅ ❌
cache ✅ ❌ ✅
test-runner ❌ ✅ ❌
Подсказка (структура [Link])

2/5
version: "3.9"

services:
app:
image: nginx:alpine
profiles:
- dev
- test
- prod
# другие настройки...

db:
image: postgres:13-alpine
profiles:
- dev
- test
- prod
# другие настройки...

# Остальные сервисы...

Полное решение

3/5
version: "3.9"

services:
app:
image: nginx:alpine
profiles:
- dev
- test
- prod
ports:
- "8080:80"

db:
image: postgres:13-alpine
profiles:
- dev
- test
- prod
environment:
POSTGRES_PASSWORD: example
POSTGRES_DB: app_db

adminer:
image: adminer:latest
profiles:
- dev
- test
ports:
- "8081:8080"

cache:
image: redis:alpine
profiles:
- dev
- prod
ports:
- "6379:6379"

test-runner:
image: alpine:latest
profiles:
- test
command: echo "Running tests..." && sleep 10 && echo "Tests completed!"

Для запуска в разных режимах используйте следующие команды:

Режим разработки: docker-compose --profile dev up -d


Режим тестирования: docker-compose --profile test up -d
Режим продакшн: docker-compose --profile prod up -d

В режиме разработки будут запущены сервисы: app, db, adminer, cache

В режиме тестирования будут запущены сервисы: app, db, adminer, test-runner

В режиме продакшн будут запущены сервисы: app, db, cache

4/5
5/5
6.11 Override, секция build

Расширенная конфигурация Docker Compose: секция build и


несколько YAML-файлов
В базовых примерах Docker Compose мы, как правило, используем один файл
[Link] и указываем готовые образы в виде image:.... Но на практике
часто требуется:

Собирать Docker-образы на лету (с помощью build:) напрямую из Docker


Compose, без отдельного вызова docker build.
Разделить конфигурацию на базовый и локальный (override) файлы: чтобы
по-разному настраивать проект для разработки и продакшена.

В этом уроке мы разберём, как Compose поддерживает оба механизма.

1. Секция build в Docker Compose


Не всегда у вас есть готовый образ. Часто удобнее собирать образ прямо при
запуске Docker Compose. Секция build позволяет это сделать, убирая
необходимость в отдельных командах docker build.

1.1 Базовый пример

version: "3.8"
services:
api:
build: .
ports:
- "5000:5000"

build: . говорит: «Возьми Dockerfile из текущей директории (.) и собери


образ.»
Когда делаете docker-compose up, Compose сначала выполнит сборку, потом
запустит контейнер на базе полученного образа.

1.2 Кастомизация секции build

version: "3.8"
services:
app:
build:
context: ./app
dockerfile: [Link]
args:
ENVIRONMENT: "production"
image: myorg/app:latest
ports:
- "8080:8080"

1/4
context: директория, передаваемая в качестве контекста (аналог docker build
./app).
dockerfile: имя файла Dockerfile (если не Dockerfile по умолчанию).
args: build-аргументы (через ARG внутри Dockerfile). Здесь
ENVIRONMENT=production.
image: имя образа (например, myorg/app:latest). Полезно, если вы
планируете потом docker push.

1.3 Польза для CI/CD

Можно хранить один или несколько Compose-файлов в репозитории, а в CI


просто запускать docker-compose build.
Нет лишних скриптов для docker build и docker run.

2. [Link] и несколько YAML-файлов

По умолчанию, если рядом с [Link] лежит docker-


[Link], Compose автоматически сливает их в единое целое при
запуске. Настройки из override перекрывают параметры в основном файле.

2.1 Зачем нужен [Link]?

Разделение окружений: базовый файл описывает общую схему (образы,


сети, тома), а override содержит локальные настройки (bind mounts,
переменные окружения для разработки, debug-настройки).
Локальные правки: вы можете монтировать исходники или добавлять
временные переменные, не меняя основной [Link].
Удобство переключения: при docker-compose up (или docker compose up) и
наличии override-файла всё автоматически объединится. Не нужен ручной
флаг, если файл назван именно [Link].

2.2 Пример

[Link] (базовый):

version: "3.8"
services:
web:
image: myorg/webapp:1.0
container_name: webapp
ports:
- "8080:80"

[Link]:

2/4
version: "3.8"
services:
web:
# Вместо готового образа — build-секция (например, для локальной разработки)
build:
context: .
dockerfile: [Link]

# Монтируем исходный код


volumes:
- ./:/usr/src/app

environment:
- APP_ENV=development

Теперь, если просто ввести docker-compose up, Compose увидит override-файл


и заменит image: myorg/webapp:1.0 на build: ..., а также добавит volumes и
environment переменные.
Результат: локальная сборка + горящий код (bind mount), переменная
APP_ENV=development и т. д.

2.3 Явное перечисление нескольких файлов

docker-compose -f [Link] -f [Link] up

Compose поочерёдно прочитает сначала [Link], потом docker-


[Link]. Если вы укажите другие файлы (например, [Link],
[Link]), то так же объединит их.

2.4 Практика хранения override-файла

В репозитории: если все разработчики используют одинаковые dev-


настройки, имеет смысл хранить override-файл в Git, чтобы все могли
подхватить его.
В .gitignore: если override — это что-то очень локальное (пароли,
специфическая среда), можно не хранить его в репозитории.

3. Итоги

Секция build: в Docker Compose даёт возможность собирать образы


автоматически — удобнее, чем отдельно делать docker build.
Поддерживаются context, dockerfile, args, target, cache_from и т. д.
В результате Compose становится центром управления: сборка, запуск,
сетевые настройки — всё в одном YAML.

3/4
[Link] (и вообще несколько YAML-файлов) позволяют
хранить разные конфигурации (prod/dev) без постоянного редактирования
одного файла.
При docker-compose up файлы автоматически объединяются (если
override-файл назван точно [Link]).
Явный флаг -f помогает комбинировать несколько файлов или
переопределить имя override-файла.

4/4
Расширенная конфигурация Docker Compose
В этом задании вы научитесь использовать расширенные возможности Docker
Compose, включая секцию build для сборки образов и разделение конфигурации на
несколько YAML-файлов.

Техническое задание
Вам необходимо разработать конфигурацию Docker Compose для простого веб-
приложения, которое будет работать в двух режимах:

1. Режим разработки (development) - с монтированием локальных файлов и


использованием собственного Dockerfile для быстрой разработки
2. Режим продакшена (production) - использующий предварительно собранные
образы для стабильной работы

Структура проекта

Создайте следующую структуру файлов:

project/
├── app/
│ ├── [Link]
│ └── [Link]
├── [Link]
├── [Link]
├── [Link]
└── [Link]

Создайте файл app/[Link] со следующим содержимым:

1/6
<!DOCTYPE html>
<html>
<head>
<title>Docker Compose Demo</title>
<link rel="stylesheet" href="[Link]">
</head>
<body>
<div class="container">
<h1>Привет из Docker!</h1>
<p>Это демонстрация расширенной конфигурации Docker Compose.</p>
<p>Режим: <span id="environment">неизвестно</span></p>
</div>

<script>
// Читаем переменную окружения из мета-тега
const env = [Link]('meta[name="environment"]');
if (env) {
[Link]('environment').textContent =
[Link]('content');
}
</script>
</body>
</html>

Создайте файл app/[Link] со следующим содержимым:

body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}

.container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
text-align: center;
}

h1 {
color: #333;
}

#environment {
font-weight: bold;
}

Создайте файл [Link] со следующим содержимым:

2/6
FROM nginx:alpine

RUN apk update && apk add --no-cache bash

COPY ./[Link] /[Link]


RUN chmod +x /[Link]

ENTRYPOINT ["/[Link]"]
CMD ["nginx", "-g", "daemon off;"]

Создайте файл [Link] со следующим содержимым:

FROM nginx:alpine

COPY ./app /usr/share/nginx/html

RUN echo '<meta name="environment" content="production">' >>


/usr/share/nginx/html/[Link]

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Создайте файл [Link] со следующим содержимым:

#!/bin/bash
echo '<meta name="environment" content="development">' >>
/usr/share/nginx/html/[Link]
exec "$@"

Требования к решению
1. Создайте базовый файл [Link] для продакшен-режима,
который:
Использует секцию build для сборки образа с [Link]
Указывает тег для собранного образа: myapp:prod
Пробрасывает порт 80 контейнера на порт 8080 хоста
2. Создайте файл [Link] для режима разработки,
который:
Переопределяет секцию build, чтобы использовать [Link]
Монтирует локальную директорию ./app в контейнер на путь
/usr/share/nginx/html
Изменяет тег образа на myapp:dev
Пробрасывает порт 80 контейнера на порт 8000 хоста (вместо 8080)

Что нужно сделать?


1. Создайте все необходимые файлы с указанным содержимым
2. Напишите [Link] для продакшен-режима
3. Напишите [Link] для режима разработки
4. Проверьте работу в обоих режимах

3/6
Проверка результатов

Режим разработки (с override-файлом):

1. Запустите приложение в режиме разработки:

docker-compose up -d

2. Откройте в браузере [Link] и убедитесь, что:


Страница отображается корректно
В секции "Режим:" указано "development"
3. Измените файл app/[Link] локально (например, добавьте текст) и
обновите страницу - изменения должны быть видны сразу

Режим продакшена (без override-файла):

1. Остановите контейнеры:

docker-compose down

2. Запустите приложение в режиме продакшена:

docker-compose -f [Link] up -d

3. Откройте в браузере [Link] и убедитесь, что:


Страница отображается корректно
В секции "Режим:" указано "production"
4. Измените файл app/[Link] локально и обновите страницу - изменения НЕ
должны отражаться (файлы были скопированы в образ при сборке)

Проверка конфигурации

Вы можете использовать команду docker-compose config для проверки итоговой


конфигурации:

1. Для режима разработки (с override-файлом):

docker-compose config

2. Для режима продакшена (только основной файл):

docker-compose -f [Link] config

Подсказка для [Link]

4/6
version: "3.8"

services:
web:
build:
context: .
dockerfile: [Link]
image: myapp:prod
ports:
- "8080:80"

Подсказка для [Link]

version: "3.8"

services:
web:
build:
context: .
dockerfile: [Link]
image: myapp:dev
ports:
- "8000:80"
volumes:
- ./app:/usr/share/nginx/html

Полное решение
[Link]:

version: "3.8"

services:
web:
build:
context: .
dockerfile: [Link]
image: myapp:prod
ports:
- "8080:80"

[Link]:

version: "3.8"

services:
web:
build:
context: .
dockerfile: [Link]
image: myapp:dev
ports:
- "8000:80"
volumes:
- ./app:/usr/share/nginx/html

5/6
При запуске с командой docker-compose up будет использоваться конфигурация из
обоих файлов, причем настройки из [Link] перекроют
настройки из [Link]. В результате будет использоваться
[Link], порт 8000 и монтирование локальной директории.

При запуске с командой docker-compose -f [Link] up будет


использоваться только основной файл без перекрытий. В результате будет
использоваться [Link] и порт 8080, а монтирования локальной
директории не будет.

6/6
6.13 Балансировка и --scale

Балансировка трафика и --scale в Docker Compose


Одна из возможностей Docker Compose — запускать несколько экземпляров
(реплик) одного сервиса, используя опцию --scale. Это может быть полезно для
тестового горизонтального масштабирования приложения (например, несколько
процессов веб-сервера). Но чтобы такой кластер работал как единое целое, обычно
требуется внешний балансировщик (reverse proxy) или интеграция со
Swarm/Kubernetes. В этом уроке разберём, как всё устроено.

1. Как работает --scale в Docker Compose?


Если у вас в [Link] объявлен сервис, скажем web, вы можете при
запуске указать:

docker-compose up --scale web=3

Compose создаст три контейнера web (например, web_1, web_2, web_3), с


одинаковым образом и конфигурацией, кроме имени контейнера.
Все эти контейнеры будут запущены на одной машине.

Важное ограничение: если вы укажете ports:8080:80, то все три контейнера


пытаются слушать порт 80 внутри контейнера, но снаружи вы не можете забиндить
все на один и тот же порт 8080. Docker разрешит это лишь для одного контейнера,
остальные будут в конфликте, либо Compose раскинет порты динамически
(присвоит следующий свободный порт), что не всегда удобно.

2. Проблема опубликованных портов (publish) в многократных


репликах
Допустим, у вас есть:

services:
web:
image: myorg/webapp
ports:
- "8080:80"

Один контейнер web слушает внутри на 80 порту, а наружу отдаёт 8080. Всё
ок.
Если вы делаете docker-compose up --scale web=3, у вас будет web_1, web_2,
web_3. Но только один из них сможет занять 8080 на хосте. Остальные либо
не запустятся (ошибка), либо Compose назначит им случайные порты
(например 49153, 49154), что запутает маршрутизацию.

1/3
Решение: использовать балансировщик (reverse proxy) в качестве входной точки. В
этом случае вы не публикуете порт у каждого контейнера, а даёте прокси доступ к
контейнерам по внутренней сети. Он может раздавать запросы равномерно по
разным web.

3. Пример с Nginx в роли балансировщика

Предположим, у вас есть сервис webapp, вы хотите масштабировать его до трёх


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

version: "3.8"

services:
nginx:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./[Link]:/etc/nginx/[Link]:ro
depends_on:
- webapp # Нужен, чтобы запустить Nginx после webapp
networks:
- appnet

webapp:
image: myorg/webapp
networks:
- appnet

networks:
appnet:
driver: bridge

В [Link] вы прописываете upstream для webapp_1, webapp_2 и т. д. (или


используете DNS-имена, если Nginx обновляет DNS). Упрощённый пример:

http {
upstream webapp_cluster {
# тут может быть dns-резолвинг "webapp" => нескольких IP
server webapp:80;
}

server {
listen 80;
location / {
proxy_pass [Link]
}
}
}

Теперь, если вы запустите:

docker-compose up -d --scale webapp=3

2/3
Будет один контейнер nginx (публикующий порт 8080 на хосте), и три
контейнера webapp.
Все они в сети appnet.
Если Nginx умеет резолвить webapp в несколько IP, то запросы будут
распределяться.

Внимание: Базовый Nginx не делает автоматического service discovery (то есть


когда появляются новые контейнеры, Nginx без перезапуска может их не увидеть).
Нужно либо перезагружать конфигурацию, либо применять динамический
балансировщик (например, dockerflow/docker-flow-proxy, Traefik, HAProxy) — это
уже более сложные инструменты. Но для локальных экспериментов достаточно
статической привязки.

5. Выводы

--scale service=N позволяет запускать N реплик одного сервиса в Compose,


но все они работают на одной машине.
Порты для всех реплик опубликовать одним static mapping (например,
"8080:80") не выйдет без конфликтов. Обычно используют внешний прокси
или оставляют порты контейнеров «внутренними» (без публикации), а
входящий трафик собирает другой сервис (Nginx, Traefik, HAProxy).
В простых проектах можно сделать fake-load-balancing локально (пример с
Nginx). Для настоящего масштабирования на разные хосты уже нужен Swarm
Mode или Kubernetes.

3/3
Практика: Настройка балансировки трафика и
масштабирование сервисов в Docker Compose
В этой практике вы научитесь масштабировать сервисы с помощью опции --scale в
Docker Compose и настраивать балансировку трафика через Nginx.

Техническое задание

Вам необходимо создать систему из трех компонентов:

1. Веб-приложение: простой сервис, который показывает свой ID контейнера


2. Балансировщик (Nginx): распределяет запросы между экземплярами веб-
приложения
3. Масштабирование: запуск нескольких экземпляров веб-приложения с
помощью --scale

Структура проекта

Создайте следующую структуру файлов:

project/
├── app/
│ ├── Dockerfile
│ └── [Link]
├── nginx/
│ └── [Link]
└── [Link]

Шаги выполнения

1. Создание веб-приложения

Создайте файл app/[Link] со следующим содержимым:

1/7
from flask import Flask
import socket
import os
import time

app = Flask(__name__)

# Получаем хостнейм (ID контейнера)


hostname = [Link]()

# Добавляем счетчик запросов


request_count = 0

@[Link]('/')
def hello():
global request_count
request_count += 1

# Добавляем задержку, чтобы продемонстрировать работу балансировщика


[Link](0.1)

return f"""
<html>
<head>
<title>Docker Scaling Demo</title>
<style>
body {{ font-family: Arial, sans-serif; margin: 20px; background-
color: #f8f9fa; }}
.container {{ background-color: white; padding: 20px; border-radius:
5px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }}
h1 {{ color: #333; }}
.info {{ background-color: #e2f2ff; padding: 15px; border-radius: 5px;
margin-top: 20px; }}
.counter {{ background-color: #ffeeba; padding: 15px; border-radius:
5px; margin-top: 20px; }}
</style>
</head>
<body>
<div class="container">
<h1>Привет из контейнера!</h1>
<div class="info">
<p><strong>Хостнейм (ID контейнера):</strong> {hostname}</p>
<p><strong>Время сервера:</strong> {[Link]('%Y-%m-%d
%H:%M:%S')}</p>
</div>
<div class="counter">
<p><strong>Счетчик запросов к этому контейнеру:</strong>
{request_count}</p>
</div>
</div>
</body>
</html>
"""

if __name__ == '__main__':
[Link](host='[Link]', port=5000)

2/7
Создайте файл app/Dockerfile со следующим содержимым:

FROM python:3.9-slim

WORKDIR /app

COPY [Link] .

RUN pip install flask

EXPOSE 5000

CMD ["python", "[Link]"]

2. Настройка балансировщика Nginx

Создайте файл nginx/[Link] со следующим содержимым:

3/7
user nginx;
worker_processes 1;

error_log /var/log/nginx/[Link] warn;


pid /var/run/[Link];

events {
worker_connections 1024;
}

http {
include /etc/nginx/[Link];
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '


'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/[Link] main;

sendfile on;
keepalive_timeout 65;

upstream app_servers {
server webapp:5000;
}

server {
listen 80;
server_name localhost;

location / {
proxy_pass [Link]
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;

# Добавляем специальные заголовки для трассировки


add_header X-Upstream $upstream_addr;
add_header X-Backend-Server $upstream_addr;
}
}
}

3. Создание [Link]

Создайте файл [Link] со следующим содержимым:

4/7
version: "3.8"

services:
nginx:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./nginx/[Link]:/etc/nginx/[Link]:ro
depends_on:
- webapp
networks:
- app_network

webapp:
build:
context: ./app
networks:
- app_network

networks:
app_network:
driver: bridge

Запуск и тестирование

1. Запуск с одним экземпляром веб-приложения

Соберите и запустите контейнеры:

docker-compose up -d

Откройте веб-браузер и перейдите по адресу [Link] Вы должны


увидеть страницу с информацией о контейнере. Обновите страницу несколько раз -
заметьте, что счетчик запросов увеличивается, а ID контейнера остается тем же.

2. Масштабирование веб-приложения

Теперь масштабируйте сервис webapp до 3 экземпляров:

docker-compose up -d --scale webapp=3

Снова откройте [Link] и обновите страницу несколько раз. Теперь


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

3. Проверка балансировки

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


следующие команды:

docker-compose ps

Вы должны увидеть один контейнер nginx и три контейнера webapp.

5/7
Для наблюдения за распределением запросов можно выполнить команду:

docker-compose logs -f nginx

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


показывающие, что запросы перенаправляются на разные экземпляры webapp.

Дополнительные задания
1. Измените стратегию балансировки: отредактируйте [Link], добавив
алгоритм round-robin или ip_hash.
2. Добавьте sticky sessions: настройте Nginx так, чтобы один клиент всегда
попадал на один и тот же экземпляр webapp.
3. Добавьте health-check: модифицируйте конфигурацию Nginx, добавив
проверку здоровья для сервисов webapp.

Ожидаемые результаты

После выполнения этой практики вы должны:

Понимать, как масштабировать сервисы с помощью Docker Compose


Освоить настройку балансировки трафика через Nginx
Уметь проверять распределение запросов между несколькими экземплярами
сервиса

Подсказки и советы

Если вы внесли изменения в [Link], не забудьте перезапустить сервис


nginx: docker-compose restart nginx
Чтобы проверить, что Nginx действительно видит все экземпляры webapp,
можно проверить логи: docker-compose logs nginx
Для отладки можно зайти внутрь контейнера Nginx: docker-compose exec
nginx bash и выполнить ping webapp для проверки DNS-резолвинга

Решение для дополнительного задания №1 (Изменение стратегии балансировки)


Для изменения стратегии балансировки на round-robin (по умолчанию) или ip_hash,
отредактируйте секцию upstream в файле nginx/[Link]:

upstream app_servers {
ip_hash; # Или можно использовать: least_conn; или weight
server webapp:5000;
}

Стратегия ip_hash гарантирует, что запросы от одного IP-адреса всегда будут


направляться на один и тот же сервер, что полезно для поддержания сессий.

Решение для дополнительного задания №2 (Sticky sessions)


Для настройки sticky sessions, используйте директиву sticky в конфигурации Nginx:

6/7
upstream app_servers {
ip_hash; # Простой способ реализации sticky sessions по IP
server webapp:5000;
}

Для более продвинутой настройки с использованием cookies, понадобится модуль


nginx-sticky-module или использование более новых версий Nginx Plus.

7/7
7.1 Что такое Docker Registry

Что такое Docker Registry?


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

Определение и назначение

Docker Registry — это хранилище Docker-образов, которое позволяет:

Хранить образы централизованно и организованно


Распространять образы между разработчиками, серверами и средами
Версионировать образы с помощью тегов
Контролировать доступ к образам (в случае приватных регистри)

Проще говоря, Registry для Docker-образов — это то же самое, что GitHub для кода:
место, где вы храните свои артефакты и делитесь ими с другими.

Типы реестров

В экосистеме Docker существует несколько типов реестров:

1. Публичные реестры

Docker Hub — официальный публичный реестр от Docker, где хранятся


тысячи открытых образов

1/4
GitHub Container Registry — реестр от GitHub, интегрированный с GitHub
Actions

[Link] — реестр от Red Hat

2. Приватные реестры

Частное хранилище Docker Hub — платные тарифы Docker Hub

2/4
Облачные провайдеры
Локальные реестры — собственный регистри на базе open-source Docker
Registry или Harbor

Docker Hub — основной публичный регистри

Docker Hub ([Link]) — самый популярный реестр, который предоставляет:

Официальные образы от создателей технологий (например, образы от


MySQL, Nginx, Python)
Верифицированные образы от Docker-партнеров
Образы сообщества от обычных пользователей
Автоматическую сборку (связывая реестр с репозиторием кода)

Когда мы используем команду docker pull ubuntu:20.04, Docker по умолчанию


обращается именно к Docker Hub.

Как Docker работает с реестрами


При работе с Docker Registry используются три ключевые команды:

1. docker pull — загружает образ из реестра на локальную машину

docker pull nginx:latest

2. docker push — отправляет образ с локальной машины в реестр

docker push myusername/myapp:1.0

3. docker tag — создаёт тег для существующего образа, часто используется


перед push

docker tag myapp:latest myusername/myapp:1.0

Именование образов
Структура полного имени образа в реестре выглядит так:

registry-host:port/username/repository:tag

Например:

[Link]/library/ubuntu:20.04 — официальный образ Ubuntu


[Link]/project/backend:v1.2.3 — ваш образ в частном
реестре
myusername/myapp:latest — ваш образ в Docker Hub (короткая форма без
[Link])

Приватные образы и аутентификация

3/4
Для работы с приватными образами нужно авторизоваться в реестре:

docker login registry-host:port

После ввода учётных данных Docker сохраняет ваш токен авторизации в файле
~/.docker/[Link], и последующие команды pull и push будут работать с этим
реестром автоматически.

Зачем нужен локальный реестр?


Есть несколько причин развертывать собственный Docker Registry:

Безопасность — хранение конфиденциальных образов локально


Контроль — полный контроль над доступом и версионированием
Производительность — быстрая загрузка образов в локальной сети
Независимость — работа без доступа к публичным реестрам
Интеграция — встраивание в собственный CI/CD конвейер

Альтернативы простому Docker Registry


Если вам нужно более функциональное решение, стоит обратить внимание на:

Harbor — продвинутый реестр с поддержкой ролей, сканирования


уязвимостей и репликацией
Nexus Repository — универсальный репозиторий, поддерживающий не только
Docker, но и npm, Maven и др.
GitLab Container Registry — встроенный в GitLab реестр с тесной
интеграцией с CI/CD

Итог
Docker Registry — это хранилище Docker-образов, позволяющее
распространять и версионировать образы
Docker Hub — основной публичный реестр, но есть и множество альтернатив
С реестрами работают через docker pull, docker push и docker tag
Локальные реестры полезны для корпоративных окружений и CI/CD
интеграции

4/4
7.2 Практика

Базовая работа с Docker Hub


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

Техническое задание

1. Создание аккаунта на Docker Hub:


Регистрация на платформе Docker Hub
Настройка базовой информации профиля
Аутентификация в командной строке
2. Создание простого Docker-образа:
Создание простого приложения на базе Nginx
Сборка Docker-образа с собственным именем
3. Публикация образа в Docker Hub:
Тегирование образа согласно правилам Docker Hub
Загрузка образа в репозиторий
Проверка доступности образа через веб-интерфейс
4. Загрузка образа на другую машину:
Удаление локального образа
Загрузка образа из Docker Hub
Запуск контейнера из загруженного образа

Структура проекта
Подготовьте следующую структуру файлов:

app/[Link] — HTML-файл для нашего веб-сервера


Dockerfile — файл для сборки образа

Создайте файл app/[Link] со следующим содержимым:

1/4
<!DOCTYPE html>
<html>
<head>
<title>Мой первый опубликованный Docker-образ</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: white;
padding: 20px 30px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
h1 {
color: #2474b5;
border-bottom: 2px solid #eaeaea;
padding-bottom: 10px;
}
.success {
background-color: #d4edda;
color: #155724;
padding: 15px;
border-radius: 4px;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>Образ успешно опубликован в Docker Hub!</h1>
<p>Это страница из образа, который вы опубликовали в Docker Hub.</p>

<h2>Что вы уже сделали:</h2>


<ul>
<li>Создали аккаунт на Docker Hub</li>
<li>Создали простой Docker-образ</li>
<li>Опубликовали его в своем репозитории</li>
<li>Загрузили образ на эту машину</li>
</ul>

<div class="success">
<strong>Поздравляем!</strong> Теперь вы умеете работать с Docker Hub и
управлять образами в реестре.
</div>
</div>
</body>
</html>

Создайте файл Dockerfile со следующим содержимым:

2/4
FROM nginx:alpine
COPY app/[Link] /usr/share/nginx/html/[Link]
LABEL maintainer="Ваше имя <ваш.email@[Link]>"
LABEL description="Простой образ для демонстрации работы с Docker Hub"
EXPOSE 80

Задание
1. Зарегистрируйтесь на Docker Hub
2. Авторизуйтесь в Docker CLI
3. Соберите Docker-образ на основе предоставленного Dockerfile
4. Загрузите собранный образ в свой репозиторий на Docker Hub
5. Проверьте доступность образа через веб-интерфейс Docker Hub
6. Удалите локальный образ
7. Загрузите образ из Docker Hub
8. Запустите контейнер из загруженного образа на порту 8080
9. Проверьте работу запущенного контейнера

Что нужно сдать?

1. Ссылку на ваш Docker репозиторий


2. Команды, которые вы использовали для выполнения задания

Требования к решению

Образ должен быть опубликован в публичном репозитории Docker Hub


Образ должен иметь тег latest
В Dockerfile должны быть указаны метки (LABEL) с информацией о создателе и
описанием
Контейнер должен успешно запускаться и отображать созданную HTML-
страницу

Подсказка (команды для выполнения)

# Аутентификация в Docker Hub


docker login

# Сборка образа (замените username на ваш логин на Docker Hub)


docker build -t username/my-first-repo:latest .

# Публикация образа
docker push username/my-first-repo:latest

# Удаление локального образа


docker rmi username/my-first-repo:latest

# Загрузка образа из Docker Hub


docker pull username/my-first-repo:latest

# Запуск контейнера
docker run -d -p 8080:80 username/my-first-repo:latest

3/4
4/4
7.3 Локальный registry

Создание собственного Registry


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

Зачем нужен собственный Registry?


Перед тем как углубиться в техническую реализацию, давайте рассмотрим, в каких
случаях имеет смысл разворачивать собственный реестр:

Приватность — хранение конфиденциальных образов в контролируемой


среде
Скорость — снижение времени доставки образов в локальной сети
Экономия трафика — загрузка образов один раз и локальное
распространение
Отказоустойчивость — работа без зависимости от внешних сервисов
CI/CD интеграция — встраивание Registry в конвейеры непрерывной
интеграции

Как устроен Docker Registry

Docker Registry — это отдельный проект с открытым исходным кодом, который


предоставляет API для хранения и распространения образов Docker. Он сам
поставляется как Docker-образ, что упрощает его развёртывание и управление.

Базовый запуск Registry

Развернуть простой вариант Docker Registry очень легко. Достаточно запустить


официальный образ registry:2:

docker run -d -p 5000:5000 --name registry registry:2

После запуска вы получите работающий реестр, доступный по адресу


localhost:5000. Теперь вы можете:

1. Пометить (tag) образ для отправки в ваш реестр:

docker tag nginx:latest localhost:5000/my-nginx:latest

2. Отправить (push) образ в ваш реестр:

docker push localhost:5000/my-nginx:latest

1/6
3. Загрузить (pull) образ из вашего реестра:

docker pull localhost:5000/my-nginx:latest

Проверить список образов в реестре можно с помощью API-запроса:

curl -X GET [Link]

Этот запрос вернёт список репозиториев в формате JSON:

{"repositories":["my-nginx"]}

Постоянное хранение данных


По умолчанию Registry хранит все образы в своей внутренней файловой системе
контейнера. Это значит, что при удалении контейнера все образы будут потеряны.
Для постоянного хранения данных нужно использовать volume:

docker run -d -p 5000:5000 --name registry \


-v registry-data:/var/lib/registry \
registry:2

Этот вариант создаёт именованный том registry-data, который сохраняется даже


после удаления контейнера.

Настройка безопасности и TLS

Наш базовый реестр работает без шифрования (HTTP), что подходит только для
тестирования на локальной машине. Docker по умолчанию блокирует push/pull
операции с небезопасными реестрами, кроме localhost.

Для использования реестра в реальной среде обязательно нужно настроить TLS


шифрование:

1. Подготовка сертификатов
Вам понадобятся SSL-сертификат и приватный ключ. Для тестовой среды
можно создать самоподписанный сертификат:

# Создание директории для сертификатов


mkdir -p certs
cd certs

# Генерация приватного ключа


openssl genrsa -out [Link] 2048

# Создание самоподписанного сертификата


# (замените [Link] на ваше доменное имя)
openssl req -new -x509 -sha256 -key [Link] -out [Link] -days 365 \
-subj "/CN=[Link]"

2/6
2. Запуск Registry с поддержкой HTTPS

docker run -d -p 5000:5000 --name registry \


-v registry-data:/var/lib/registry \
-v $(pwd)/certs:/certs \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/[Link] \
-e REGISTRY_HTTP_TLS_KEY=/certs/[Link] \
registry:2

Теперь ваш реестр работает по HTTPS, но для самоподписанных


сертификатов клиентам нужно дополнительно:

Добавить сертификат в доверенные на клиентских машинах или


Настроить Docker для работы с небезопасным реестром:

# Linux - добавление в /etc/docker/[Link]


{
"insecure-registries": ["[Link]"]
}

# Рестарт демона Docker


sudo systemctl restart docker

Ограничение доступа

Для защиты вашего реестра необходимо настроить аутентификацию. Самый


простой способ — это базовая HTTP-аутентификация:

1. Создание файла с учётными данными

# Установка htpasswd (входит в пакет apache2-utils)


sudo apt-get install apache2-utils # для Debian/Ubuntu
# или
sudo yum install httpd-tools # для CentOS/RHEL

# Создание директории для аутентификации


mkdir -p auth

# Создание файла с пользователем (замените username и password)


htpasswd -Bc auth/htpasswd username
# Введите пароль дважды при запросе

2. Запуск Registry с аутентификацией

docker run -d -p 5000:5000 --name registry \


-v registry-data:/var/lib/registry \
-v $(pwd)/certs:/certs \
-v $(pwd)/auth:/auth \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/[Link] \
-e REGISTRY_HTTP_TLS_KEY=/certs/[Link] \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
registry:2

3/6
3. Аутентификация для push/pull
Теперь перед работой с реестром нужно аутентифицироваться:

docker login [Link]


# Введите имя пользователя и пароль

После успешной аутентификации Docker сохранит учётные данные локально,


и вы сможете выполнять push/pull операции.

Настройка через docker-compose


Для более удобного управления реестром лучше использовать Docker Compose.
Вот пример [Link] с полной конфигурацией безопасного реестра:

version: '3'

services:
registry:
image: registry:2
ports:
- "5000:5000"
environment:
REGISTRY_HTTP_TLS_CERTIFICATE: /certs/[Link]
REGISTRY_HTTP_TLS_KEY: /certs/[Link]
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_STORAGE_DELETE_ENABLED: "true" # Включает возможность удаления
образов
volumes:
- ./data:/var/lib/registry
- ./certs:/certs
- ./auth:/auth
restart: always

Управление образами в Registry

Docker Registry API v2 позволяет не только хранить образы, но и управлять ими:

1. Просмотр списка репозиториев

curl -X GET [Link] \


--user username:password

2. Просмотр тегов конкретного репозитория

curl -X GET [Link] \


--user username:password

4/6
3. Удаление образа (требует включения
REGISTRY_STORAGE_DELETE_ENABLED)

# Сначала получить digest (хеш) образа


DIGEST=$(curl -v --silent -H "Accept:
application/[Link].v2+json" \
-X GET [Link] \
--user username:password 2>&1 | grep Docker-Content-Digest | awk '{print
$3}' | tr -d '\r')

# Затем удалить манифест по хешу


curl -X DELETE [Link]
nginx/manifests/$DIGEST \
--user username:password

Важно отметить, что удаление манифеста не очищает фактически используемое


дисковое пространство. Для этого нужно запустить сборщик мусора:

docker exec -it registry registry garbage-collect /etc/docker/registry/[Link]

Расширенные возможности
Docker Registry поддерживает множество дополнительных опций, которые могут
быть полезны в продакшн-окружении:

1. Хранилище данных: вместо локальной файловой системы можно


использовать облачные сервисы:

# Пример настройки для Amazon S3


docker run -d -p 5000:5000 --name registry \
-e REGISTRY_STORAGE=s3 \
-e REGISTRY_STORAGE_S3_REGION=us-east-1 \
-e REGISTRY_STORAGE_S3_BUCKET=my-registry-bucket \
-e REGISTRY_STORAGE_S3_ACCESSKEY=AKIAIOSFODNN7EXAMPLE \
-e REGISTRY_STORAGE_S3_SECRETKEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY \
registry:2

2. Уведомления: можно настроить отправку уведомлений о событиях (push, pull,


delete) на внешние системы:

# Пример настройки webhook


-e REGISTRY_NOTIFICATIONS_ENDPOINTS_0_NAME=webhook \
-e REGISTRY_NOTIFICATIONS_ENDPOINTS_0_URL=[Link]
\
-e REGISTRY_NOTIFICATIONS_ENDPOINTS_0_HEADERS_Authorization="Bearer secret-
token"

3. Кэширование: Registry может работать как кэширующий прокси для Docker


Hub или других реестров:

-e REGISTRY_PROXY_REMOTEURL=[Link]

Производительные альтернативы

5/6
Официальный Docker Registry хорош для небольших и средних команд, но для
крупных организаций существуют более функциональные альтернативы:

Harbor — корпоративный реестр с веб-интерфейсом, скетированием


безопасности, управлением ролями и репликацией
Nexus Repository OSS — хранилище не только для Docker, но и для
множества других форматов (Maven, npm, PyPI и т.д.)
GitLab Container Registry — встроенный в GitLab реестр с интеграцией с
CI/CD

Итог
Docker Registry позволяет создать собственное хранилище образов с полным
контролем
Для продакшн-использования обязательно настройте TLS и
аутентификацию
Постоянное хранение обеспечивается с помощью volumes или внешних
хранилищ
Docker Registry предоставляет API для управления образами и интеграции с
другими системами
Для корпоративного использования стоит рассмотреть альтернативы с
расширенными возможностями

Создав собственный Docker Registry, вы получаете полный контроль над


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

6/6
7.4 Практика

Настройка собственного Docker Registry


В этом задании вы создадите и настроите собственный Docker Registry с
поддержкой TLS и базовой аутентификацией. Затем вы научитесь загружать и
скачивать образы из вашего локального реестра.

Техническое задание

1. Локальный реестр:
Развернуть Docker Registry с помощью docker-compose
Обеспечить постоянное хранение данных
Настроить базовую HTTP-аутентификацию
Настроить TLS с самоподписанным сертификатом
2. Работа с реестром:
Авторизоваться в реестре
Загрузить образ в реестр
Скачать образ из реестра
Просмотреть список доступных образов через API

Подготовка инфраструктуры

Создайте следующую структуру файлов и директорий для вашего проекта:

registry-project/
├── [Link]
├── auth/ # Будет содержать файлы аутентификации
├── certs/ # Будет содержать SSL сертификаты
└── data/ # Будет содержать данные реестра

Задания

1. Создание самоподписанного сертификата

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


следующие шаги:

Создайте директорию certs


Сгенерируйте приватный ключ и сертификат для домена [Link]
Сохраните результаты в файлы [Link] и [Link]

2. Создание файла с учетными данными

Создайте директорию auth


Создайте файл с учетными данными пользователя "registryuser" с паролем на
ваш выбор
Сохраните результаты в файл htpasswd

1/3
3. Создание конфигурации docker-compose

Создайте файл [Link], который:


Использует образ registry:2
Привязывает порт 5000
Монтирует созданные директории
Настраивает TLS, аутентификацию и другие необходимые параметры
Включает возможность удаления образов

4. Работа с реестром

Добавьте запись "[Link]" в файл hosts


Запустите реестр с помощью docker-compose
Авторизуйтесь в реестре
Загрузите образ nginx:latest в ваш реестр под тегом [Link]/my-
nginx:latest
Удалите локальный образ и скачайте его из вашего реестра
Проверьте список образов в реестре через API

Что нужно сдать?

1. [Link] — файл конфигурации вашего реестра


2. Команды, которые вы использовали для:
Создания сертификатов
Создания файла с учетными данными
Запуска реестра
Авторизации в реестре
Загрузки и скачивания образов
Проверки списка образов через API

Требования к решению

Обязательно использование docker-compose для запуска реестра


Обязательно использование томов для постоянного хранения данных
Обязательно настроить TLS и базовую аутентификацию
Обязательно добавить возможность удаления образов
В решении должны быть представлены все необходимые команды и
содержимое всех файлов

Подсказки

Добавление записи в файл hosts

# Linux/macOS
sudo sh -c 'echo "[Link] [Link]" >> /etc/hosts'

# Windows (запуск от администратора)


echo [Link] [Link] >> C:\Windows\System32\drivers\etc\hosts

2/3
Создание сертификатов

mkdir -p certs
cd certs
openssl genrsa -out [Link] 2048
openssl req -new -x509 -sha256 -key [Link] -out [Link] -days 365 -subj
"/CN=[Link]"

Создание файла с учетными данными

# Установка утилиты htpasswd


sudo apt-get install apache2-utils # для Debian/Ubuntu
# или
sudo yum install httpd-tools # для CentOS/RHEL

# Создание файла htpasswd


mkdir -p auth
htpasswd -Bc auth/htpasswd registryuser
# Введите пароль при запросе

Пример [Link]

version: '3'

services:
registry:
image: registry:2
ports:
- "5000:5000"
environment:
REGISTRY_HTTP_TLS_CERTIFICATE: /certs/[Link]
REGISTRY_HTTP_TLS_KEY: /certs/[Link]
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_STORAGE_DELETE_ENABLED: "true"
volumes:
- ./data:/var/lib/registry
- ./certs:/certs
- ./auth:/auth
restart: always

3/3
7.5 API Registry

Управление Docker Registry через API и интеграция с CI/CD


В предыдущих уроках мы научились создавать и настраивать собственный Docker
Registry. Теперь рассмотрим, как эффективно взаимодействовать с ним через API,
управлять образами и интегрировать Registry в процессы непрерывной интеграции
и доставки (CI/CD).

Docker Registry API v2


Docker Registry предоставляет RESTful API, который позволяет управлять
репозиториями и образами программно. Это открывает широкие возможности для
автоматизации и интеграции с различными инструментами.

Базовые эндпоинты API

Рассмотрим основные эндпоинты, с которыми вы можете взаимодействовать:

Проверка API — получение информации о доступности и версии API:

curl -X GET [Link]

Список репозиториев — получение каталога всех доступных репозиториев:

curl -X GET [Link]

Ответ будет содержать список репозиториев в формате JSON:

{"repositories":["app1", "app2", "nginx"]}

Список тегов — получение всех тегов конкретного репозитория:

curl -X GET [Link]

Ответ будет в формате:

{"name":"nginx","tags":["1.19", "latest", "stable"]}

Аутентификация в API

Если ваш Registry защищен аутентификацией (что рекомендуется), то к запросам


нужно добавлять учетные данные:

curl -X GET [Link] \


--user username:password

Или через токен (Bearer Authentication):

1/10
# Сначала получаем токен
TOKEN=$(curl -s -H "Authorization: Basic $(echo -n 'username:password' | base64)"
\
"[Link]
service=[Link]&scope=repository:nginx:pull" \
| jq -r '.token')

# Используем токен для запроса


curl -H "Authorization: Bearer $TOKEN" \
[Link]

Управление образами через API


API позволяет не только получать информацию, но и выполнять операции с
образами.

Получение метаданных образа

Для работы с образом часто нужно получить его манифест и digest (хэш):

# Получение манифеста по тегу


curl -H "Accept: application/[Link].v2+json" \
-X GET [Link] \
--user username:password

Обратите внимание на заголовок Accept — он указывает формат манифеста. В


ответе будет заголовок Docker-Content-Digest, содержащий хэш образа.

Удаление образа

Для удаления образа нужно использовать его digest:

# Сначала получаем digest образа


DIGEST=$(curl -v --silent -H "Accept:
application/[Link].v2+json" \
-X GET [Link] \
--user username:password 2>&1 | grep Docker-Content-Digest | awk '{print $3}' |
tr -d '\r')

# Затем удаляем образ по digest


curl -X DELETE [Link] \
--user username:password

Важно помнить, что для возможности удаления нужно включить соответствующую


опцию при запуске Registry (REGISTRY_STORAGE_DELETE_ENABLED=true).

Очистка неиспользуемого пространства

После удаления манифестов необходимо запустить сборщик мусора для


освобождения дискового пространства:

docker exec -it registry registry garbage-collect /etc/docker/registry/[Link]

Для автоматизации процесса очистки можно создать скрипт:

2/10
#!/bin/bash
# [Link]

# Переменные
REGISTRY_URL="[Link]
USERNAME="admin"
PASSWORD="password"
REPO="nginx"
TAG="old-tag"

# Получение digest
DIGEST=$(curl -s -H "Accept: application/[Link].v2+json"
\
--user $USERNAME:$PASSWORD \
$REGISTRY_URL/v2/$REPO/manifests/$TAG | jq -r '.[Link]')

# Удаление образа
curl -X DELETE --user $USERNAME:$PASSWORD \
$REGISTRY_URL/v2/$REPO/manifests/$DIGEST

# Запуск сборщика мусора


docker exec -it registry registry garbage-collect /etc/docker/registry/[Link]

echo "Образ $REPO:$TAG успешно удален и пространство очищено"

Интеграция Docker Registry с CI/CD

Собственный Registry становится особенно полезным при интеграции с системами


непрерывной интеграции и доставки.

Пример интеграции с GitLab CI/CD

Рассмотрим пример настройки пайплайна в .[Link] для сборки, публикации


и деплоя образа:

3/10
stages:
- build
- test
- deploy

variables:
REGISTRY_URL: [Link]
IMAGE_NAME: $REGISTRY_URL/myapp
IMAGE_TAG: $CI_COMMIT_SHORT_SHA

# Сборка образа
build:
stage: build
image: docker:20.10
services:
- docker:20.10-dind
before_script:
- echo $REGISTRY_PASSWORD | docker login $REGISTRY_URL -u $REGISTRY_USERNAME -
-password-stdin
script:
- docker build -t $IMAGE_NAME:$IMAGE_TAG .
- docker push $IMAGE_NAME:$IMAGE_TAG
after_script:
- docker logout $REGISTRY_URL

# Тестирование образа
test:
stage: test
image: docker:20.10
services:
- docker:20.10-dind
before_script:
- echo $REGISTRY_PASSWORD | docker login $REGISTRY_URL -u $REGISTRY_USERNAME -
-password-stdin
script:
- docker pull $IMAGE_NAME:$IMAGE_TAG
- docker run --rm $IMAGE_NAME:$IMAGE_TAG ./run_tests.sh
after_script:
- docker logout $REGISTRY_URL

# Деплой на продакшн
deploy:
stage: deploy
image: docker:20.10
services:
- docker:20.10-dind
before_script:
- echo $REGISTRY_PASSWORD | docker login $REGISTRY_URL -u $REGISTRY_USERNAME -
-password-stdin
script:
- docker pull $IMAGE_NAME:$IMAGE_TAG
- docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_NAME:latest
- docker push $IMAGE_NAME:latest
- curl -X POST [Link]
after_script:
- docker logout $REGISTRY_URL

4/10
only:
- main

В этом примере:

CI система авторизуется в вашем Registry


Собирает Docker-образ из исходного кода
Публикует его в ваш Registry с тегом, основанным на хеше коммита
Запускает тесты на собранном образе
При успешном тестировании помечает образ как latest и уведомляет систему
деплоя

Пример для GitHub Actions

Аналогичный пайплайн для GitHub Actions (.github/workflows/[Link]):

name: Build and Push Docker Image

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up Docker Buildx


uses: docker/setup-buildx-action@v1

- name: Login to private registry


uses: docker/login-action@v1
with:
registry: [Link]
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}

- name: Build and push


uses: docker/build-push-action@v2
with:
context: .
push: true
tags: |
[Link]/myapp:${{ [Link] }}
[Link]/myapp:latest

- name: Deploy
if: [Link] == 'refs/heads/main'
run: |
curl -X POST [Link]

5/10
Автоматизация очистки старых образов

Важной частью управления Registry является регулярная очистка устаревших


образов. Вот пример скрипта для удаления старых тегов, который можно добавить в
CI/CD пайплайн:

#!/bin/bash
# [Link]

REGISTRY_URL="[Link]
USERNAME="admin"
PASSWORD="password"
REPO="myapp"
KEEP_LAST=5

# Получение всех тегов, сортировка и выбор старых


TAGS=$(curl -s --user $USERNAME:$PASSWORD \
$REGISTRY_URL/v2/$REPO/tags/list | jq -r '.tags[]' | sort -r | tail -n
+$((KEEP_LAST+1)))

for TAG in $TAGS; do


echo "Удаление тега $REPO:$TAG"

# Получение digest для тега


DIGEST=$(curl -s -H "Accept:
application/[Link].v2+json" \
--user $USERNAME:$PASSWORD \
$REGISTRY_URL/v2/$REPO/manifests/$TAG | jq -r '.[Link]')

# Удаление образа
curl -X DELETE --user $USERNAME:$PASSWORD \
$REGISTRY_URL/v2/$REPO/manifests/$DIGEST
done

# Запуск сборщика мусора


docker exec -it registry registry garbage-collect /etc/docker/registry/[Link]

echo "Очистка завершена, оставлены последние $KEEP_LAST тегов"

Этот скрипт можно запускать как отдельный job в CI/CD пайплайне или по
расписанию через cron.

Webhook для автоматического обновления

Docker Registry поддерживает механизм уведомлений через webhook. Это


позволяет автоматически реагировать на события в Registry (например, загрузку
нового образа).

Пример настройки webhook в конфигурации Registry:

6/10
version: '3'

services:
registry:
image: registry:2
ports:
- "5000:5000"
environment:
REGISTRY_HTTP_TLS_CERTIFICATE: /certs/[Link]
REGISTRY_HTTP_TLS_KEY: /certs/[Link]
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_NOTIFICATIONS_ENDPOINTS_0_NAME: webhook
REGISTRY_NOTIFICATIONS_ENDPOINTS_0_URL:
[Link]
REGISTRY_NOTIFICATIONS_ENDPOINTS_0_HEADERS_Authorization: "Bearer
secrettoken"
REGISTRY_NOTIFICATIONS_ENDPOINTS_0_TIMEOUT: 500ms
REGISTRY_NOTIFICATIONS_ENDPOINTS_0_THRESHOLD: 5
REGISTRY_NOTIFICATIONS_ENDPOINTS_0_BACKOFF: 1s
volumes:
- ./data:/var/lib/registry
- ./certs:/certs
- ./auth:/auth
restart: always

На стороне сервера деплоя можно реализовать обработчик webhook, который будет


обновлять приложение при получении уведомления о новом образе:

7/10
// Простой пример на [Link]
const express = require('express');
const { exec } = require('child_process');
const app = express();

[Link]([Link]());

[Link]('/hooks/registry', (req, res) => {


const authHeader = [Link];

// Проверка авторизации
if (authHeader !== 'Bearer secrettoken') {
return [Link](401).send('Unauthorized');
}

// Получение данных о событии


const events = [Link];

for (const event of events) {


if ([Link] === 'push' && [Link] === 'myapp') {
[Link](`Получено уведомление о новом образе:
${[Link]}:${[Link]}`);

// Запуск скрипта деплоя


exec('./[Link]', (error, stdout, stderr) => {
if (error) {
[Link](`Ошибка деплоя: ${error}`);
return;
}
[Link](`Деплой успешно выполнен: ${stdout}`);
});
}
}

[Link](200).send('Webhook обработан');
});

[Link](3000, () => {
[Link]('Webhook-сервер запущен на порту 3000');
});

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

Мульти-стейдж сборка и оптимизация образов

Для эффективной работы CI/CD с Registry важно оптимизировать размер образов.


Пример мульти-стейдж сборки в Dockerfile:

8/10
# Стадия сборки
FROM node:14-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Финальный образ
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Версионирование и стратегии тегирования

Разумная стратегия тегирования образов в Registry упрощает управление и откат


изменений:

# В CI/CD пайплайне
VERSION=$(cat VERSION)
GIT_COMMIT=$(git rev-parse --short HEAD)
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

# Теги для образа


docker build -t $REGISTRY_URL/myapp:latest .
docker tag $REGISTRY_URL/myapp:latest $REGISTRY_URL/myapp:$VERSION
docker tag $REGISTRY_URL/myapp:latest $REGISTRY_URL/myapp:$VERSION-$GIT_COMMIT
docker tag $REGISTRY_URL/myapp:latest $REGISTRY_URL/myapp:$GIT_COMMIT

# Публикация всех тегов


docker push $REGISTRY_URL/myapp:latest
docker push $REGISTRY_URL/myapp:$VERSION
docker push $REGISTRY_URL/myapp:$VERSION-$GIT_COMMIT
docker push $REGISTRY_URL/myapp:$GIT_COMMIT

Зеркалирование и кэширование публичных образов

Собственный Registry можно использовать для кэширования образов из Docker Hub


или других публичных реестров:

version: '3'

services:
registry-mirror:
image: registry:2
ports:
- "5000:5000"
environment:
REGISTRY_PROXY_REMOTEURL: [Link]
volumes:
- ./mirror-data:/var/lib/registry
restart: always

9/10
После настройки зеркала нужно добавить его в конфигурацию Docker на клиентских
машинах (/etc/docker/[Link]):

{
"registry-mirrors": ["[Link]
}

10/10
8.1 Ограничение ресурсов и настройка cgroups

Ограничение ресурсов и настройка cgroups


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

Что такое cgroups и как Docker их использует


Control Groups (cgroups) — это технология ядра Linux, которая позволяет
ограничивать, учитывать и изолировать использование ресурсов (CPU, память,
дисковый ввод-вывод, сеть и т.д.) для групп процессов.

Docker активно использует cgroups для:

Изоляции ресурсов — каждый контейнер получает свою долю системных


ресурсов
Учёта использования — контроль над тем, сколько ресурсов потребляет
каждый контейнер
Ограничения ресурсов — установка лимитов на использование CPU, памяти
и других ресурсов
Приоритизации — назначение приоритетов для доступа к ресурсам

Важно понимать, что cgroups — это один из двух основных механизмов изоляции в
Docker (второй — namespaces, отвечающий за изоляцию процессов, сетей,
файловых систем).

Docker демон автоматически создает cgroups для каждого контейнера на основе


параметров, которые вы передаете при его запуске.

Ограничение CPU
Docker предоставляет несколько способов ограничения использования CPU:

1. --cpus

Самый простой и понятный способ — указать, сколько полных ядер CPU может
использовать контейнер:

docker run --cpus=2 nginx

Эта команда ограничит контейнер Nginx использованием максимум 2 ядер CPU,


даже если хост имеет больше ядер. Можно указывать дробные значения, например
--cpus=0.5 для ограничения контейнера половиной ядра CPU.

1/6
2. --cpu-shares

Определяет относительный вес контейнера при распределении процессорного


времени (по умолчанию 1024):

docker run --cpu-shares=512 nginx

В этом примере контейнер получит половину стандартного веса CPU. Важно


понимать, что --cpu-shares работает только когда есть конкуренция за CPU:

Если на хосте запущен только один контейнер, он может использовать 100%


CPU независимо от значения cpu-shares
Если запущено несколько контейнеров, время CPU распределяется
пропорционально их весам

3. --cpu-period и --cpu-quota

Эти два параметра работают в паре для более тонкой настройки ограничений CPU:

docker run --cpu-period=100000 --cpu-quota=50000 nginx

--cpu-period определяет период измерения в микросекундах (по умолчанию


100000, или 100мс).

--cpu-quota задает квоту использования CPU в микросекундах за один период.

В примере выше контейнер может использовать 50000 микросекунд CPU-времени


за каждые 100000 микросекунд, что эквивалентно 50% одного CPU-ядра.

4. --cpuset-cpus

Позволяет привязать контейнер к конкретным CPU-ядрам:

docker run --cpuset-cpus="0,3" nginx

Эта команда ограничит контейнер использованием только ядер CPU с индексами 0


и 3. Можно также использовать диапазоны: --cpuset-cpus="0-2".

Привязка к определенным ядрам может быть полезна для оптимизации


производительности или изоляции критически важных контейнеров.

Ограничение памяти

1. --memory

Устанавливает жесткий лимит на использование памяти контейнером:

docker run --memory=1g nginx

2/6
Контейнер в этом примере не сможет использовать больше 1 гигабайта памяти.
Если процессы внутри контейнера попытаются выделить больше памяти, они могут
быть завершены OOM-киллером (Out-Of-Memory Killer).

Поддерживаются суффиксы: b, k, m, g (для байт, килобайт, мегабайт, гигабайт).

2. --memory-swap

Задает общий лимит памяти + своп:

docker run --memory=1g --memory-swap=2g nginx

В этом примере контейнер может использовать 1 ГБ физической памяти и 1 ГБ


свопа (суммарно 2 ГБ).

Если --memory-swap равен --memory, использование свопа отключено.

Если --memory-swap равен -1, контейнер может использовать неограниченное


количество свопа.

3. --memory-reservation

Устанавливает мягкий лимит памяти, который активируется только при нехватке


памяти на хосте:

docker run --memory=1g --memory-reservation=800m nginx

В этом примере контейнер может использовать до 1 ГБ памяти, но если на хосте


возникнет нехватка памяти, система попытается сократить использование памяти
контейнером до 800 МБ.

4. --memory-swappiness

Настраивает склонность контейнера к использованию свопа (0-100):

docker run --memory-swappiness=0 nginx

Значение 0 означает, что контейнер будет использовать своп только в крайнем


случае. Значение 100 делает использование свопа более агрессивным.

5. --oom-kill-disable

Отключает OOM-киллер для контейнера:

docker run --memory=1g --oom-kill-disable nginx

Это предотвращает автоматическое завершение процессов контейнера при


нехватке памяти. Внимание: используйте этот флаг с осторожностью и только в
сочетании с --memory, иначе контейнер может привести к зависанию всей системы!

Ограничение I/O операций

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

1. --device-read-bps и --device-write-bps

Ограничивают пропускную способность чтения и записи (байт в секунду):

docker run --device-read-bps /dev/sda:10mb --device-write-bps /dev/sda:5mb nginx

В этом примере контейнер сможет читать с устройства /dev/sda со скоростью не


более 10 МБ/с и записывать со скоростью не более 5 МБ/с.

2. --device-read-iops и --device-write-iops

Ограничивают количество операций ввода-вывода в секунду:

docker run --device-read-iops /dev/sda:1000 --device-write-iops /dev/sda:500 nginx

Этот контейнер сможет выполнять не более 1000 операций чтения и 500 операций
записи в секунду на устройстве /dev/sda.

Настройка Ulimits

Ulimits (user limits) — это механизм Linux, который ограничивает


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

docker run --ulimit nofile=1024:2048 --ulimit nproc=100:200 nginx

В этом примере:

nofile=1024:2048 — ограничивает количество открытых файлов (1024 —


мягкий лимит, 2048 — жесткий)
nproc=100:200 — ограничивает количество процессов (100 — мягкий лимит,
200 — жесткий)

Общий синтаксис: --ulimit <имя>=<мягкий_лимит>:<жесткий_лимит>

Доступные типы ulimits:

core — размер core-файлов (в байтах)


cpu — максимальное время CPU (в секундах)
data — максимальный размер данных процесса (в байтах)
fsize — максимальный размер файла (в байтах)
locks — максимальное количество файловых блокировок
memlock — максимальный размер заблокированной памяти (в байтах)
msgqueue — максимальный размер очередей сообщений POSIX (в байтах)
nice — максимальное значение nice для повышения приоритета
nofile — максимальное количество открытых файлов

4/6
nproc — максимальное количество процессов
rss — максимальный размер резидентной памяти (в байтах)
rtprio — максимальный приоритет реального времени
rttime — максимальное время CPU реального времени (в микросекундах)
sigpending — максимальное количество ожидающих сигналов
stack — максимальный размер стека (в байтах)

Пример использования всех ограничений вместе

docker run \
--name limited-container \
--cpus=1.5 \
--cpu-shares=512 \
--memory=1g \
--memory-swap=1.5g \
--memory-reservation=800m \
--device-read-bps /dev/sda:10mb \
--device-write-bps /dev/sda:8mb \
--ulimit nofile=1024:4096 \
--ulimit nproc=50:100 \
nginx

Этот контейнер будет ограничен следующим образом:

Максимальное использование CPU: 1.5 ядра


Вес CPU: 512 (половина стандартного)
Максимальная память: 1 ГБ
Максимальная память + своп: 1.5 ГБ
Мягкий лимит памяти: 800 МБ
Максимальная скорость чтения с диска: 10 МБ/с
Максимальная скорость записи на диск: 8 МБ/с
Максимальное количество открытых файлов: 1024 (мягкий), 4096 (жесткий)
Максимальное количество процессов: 50 (мягкий), 100 (жесткий)

Проверка ограничений контейнера


После установки ограничений полезно проверить, что они действительно
применены:

1. Проверка ограничений CPU и памяти

docker stats limited-container

Команда docker stats покажет текущее использование CPU, памяти и других


ресурсов контейнером, а также установленные лимиты.

2. Проверка cgroups напрямую

docker inspect limited-container | grep -A 15 "HostConfig"

5/6
Команда docker inspect покажет все настройки контейнера, включая
установленные ограничения ресурсов.

Также можно проверить cgroups напрямую в файловой системе (если ваша система
использует cgroupfs):

cat /sys/fs/cgroup/cpu/docker/<container_id>/cpu.cfs_quota_us
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.limit_in_bytes

Ограничения ресурсов в Docker Compose


Все эти ограничения можно также применять в файле [Link]:

version: '3'
services:
webapp:
image: nginx
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
ulimits:
nofile:
soft: 1024
hard: 4096

В Docker Compose версии 3 и выше ограничения ресурсов указываются в секции


deploy > resources.

Итог

cgroups — механизм ядра Linux, который позволяет Docker ограничивать


ресурсы контейнеров
Docker предоставляет гибкие возможности для ограничения CPU (--cpus, --cpu-
shares, --cpu-period, --cpu-quota, --cpuset-cpus)
Контроль использования памяти возможен через множество параметров (--
memory, --memory-swap, --memory-reservation, --oom-kill-disable)
I/O операции ограничиваются с помощью --device-read-bps, --device-write-bps, -
-device-read-iops, --device-write-iops
Сетевой трафик можно ограничивать с помощью дополнительных
инструментов Linux (tc, wondershaper)

6/6
Управление ресурсами контейнеров на практике
В этом практическом задании вы научитесь настраивать и тестировать различные
ограничения ресурсов для Docker-контейнеров, а также создадите окружение с
несколькими контейнерами с разными приоритетами ресурсов.

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

1. API-сервис (высокий приоритет):


Базовый образ — nginx:alpine
Проброс порта 8001 на хосте на порт 80 в контейнере
Ограничение CPU: 1 ядро
Ограничение памяти: 512MB
Имя контейнера: api-service
2. Сервис обработки данных (средний приоритет):
Базовый образ — python:3.9-slim
Команда запуска — python скрипт для генерации нагрузки на CPU
Ограничение CPU: 0.5 ядра
Ограничение памяти: 256MB
Имя контейнера: data-processor
3. Фоновый сервис (низкий приоритет):
Базовый образ — python:3.9-slim
Команда запуска — python скрипт для генерации нагрузки на CPU
Вес CPU (cpu-shares): 256 (в 4 раза меньше стандартного)
Ограничение памяти: 128MB
Имя контейнера: background-service
4. База данных с ограничением I/O:
Базовый образ — postgres:13-alpine
Переменные окружения:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
Ограничение I/O операций чтения: 20MB/s
Ограничение I/O операций записи: 10MB/s
Использование именованного тома для хранения данных
Имя контейнера: limited-db

Структура проекта

1/8
Подготовьте следующую структуру файлов:

[Link] — файл, который вы должны создать


stress_cpu.py — Python-скрипт для создания нагрузки на CPU
load_test.sh — Bash-скрипт для тестирования распределения ресурсов

Создайте файл stress_cpu.py со следующим содержимым:

import time
import multiprocessing

def cpu_intensive_task():
"""Функция, создающая нагрузку на CPU."""
start_time = [Link]()
# Бесконечный цикл для загрузки CPU
while True:
# Вычисляем много чисел Фибоначчи
fibonacci(35)

# Каждые 10 секунд выводим статистику


current_time = [Link]()
if current_time - start_time > 10:
print(f"[{[Link]('%H:%M:%S')}] Still working...")
start_time = current_time

def fibonacci(n):
"""Рекурсивное вычисление числа Фибоначчи (намеренно неэффективное)."""
if n <= 1:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)

if __name__ == "__main__":
print(f"[{[Link]('%H:%M:%S')}] Starting CPU stress test...")

# Количество процессов (можно настроить)


num_processes = 2

# Создаем и запускаем процессы


processes = []
for _ in range(num_processes):
p = [Link](target=cpu_intensive_task)
[Link]()
[Link](p)

# Ждем завершения всех процессов (не произойдет, так как они бесконечные)
for p in processes:
[Link]()

Создайте файл load_test.sh со следующим содержимым:

2/8
#!/bin/bash

# Цвета для вывода


GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color

echo -e "${GREEN}=== Docker Resource Limits Test ===${NC}"


echo -e "${BLUE}This script will help you test resource limits on your
containers${NC}"
echo

# Тест 1: Проверка ограничений CPU


echo -e "${YELLOW}Test 1: Checking CPU limits${NC}"
echo "Running 'docker stats' for 10 seconds to observe CPU usage..."
docker stats --no-stream &
STATS_PID=$!
sleep 10
kill $STATS_PID
echo

# Тест 2: Проверка ограничений памяти


echo -e "${YELLOW}Test 2: Checking memory limits${NC}"
echo "Starting a simple memory usage test..."

for container in api-service data-processor background-service limited-db; do


echo -e "${BLUE}Container: $container${NC}"
docker exec $container bash -c "cat
/sys/fs/cgroup/memory/memory.limit_in_bytes" 2>/dev/null || \
docker exec $container sh -c "cat /sys/fs/cgroup/memory/memory.limit_in_bytes"
2>/dev/null || \
echo -e "${RED}Could not access memory limits for $container${NC}"
echo
done

# Тест 3: Проверка ограничений I/O


echo -e "${YELLOW}Test 3: Checking I/O limits for limited-db${NC}"
echo "I/O limits are harder to verify directly, but we can check the
configuration:"
docker inspect --format='{{json .[Link]}}' limited-db
docker inspect --format='{{json .[Link]}}' limited-db
echo

echo -e "${GREEN}=== Tests completed ===${NC}"


echo -e "${BLUE}Для более детального мониторинга вы можете использовать 'docker
stats'${NC}"
echo -e "${BLUE}Remember to observe resource usage under load for a realistic
assessment.${NC}"

Убедитесь, что скрипт load_test.sh имеет права на выполнение:

chmod +x load_test.sh

Что нужно сдать?

3/8
1. [Link] — файл, который запускает все четыре сервиса с
указанными ограничениями ресурсов.
2. Проверить работу композа:

docker-compose up -d
# Или для более новых версий Docker
docker compose up -d

3. Проверка работы:
Запустите скрипт ./load_test.sh для проверки ограничений ресурсов
Выполните команду docker stats для наблюдения за использованием
ресурсов в реальном времени
Проверьте, что API-сервис получает больше ресурсов CPU, чем сервис
обработки данных
Проверьте, что фоновый сервис получает наименьший приоритет при
конкуренции за CPU

Требования к решению

Все сервисы должны быть в одной сети для обеспечения связи между ними.
База данных должна сохранять данные между перезапусками контейнеров
(использовать именованный том).
Все ограничения ресурсов должны быть правильно настроены согласно
техническому заданию.
Версия docker-compose файла должна быть не ниже 3.0.
Для тестирования I/O ограничений вы должны указать правильное устройство
(например, /dev/sda) в конфигурации.

Подсказка (структура [Link])

4/8
version: "3.8"

services:
api:
image: nginx:alpine
container_name: api-service
ports:
- "8001:80"
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
networks:
- resource-test-net

data-processor:
image: python:3.9-slim
container_name: data-processor
volumes:
- ./stress_cpu.py:/app/stress_cpu.py
working_dir: /app
command: python stress_cpu.py
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
networks:
- resource-test-net

background:
image: python:3.9-slim
container_name: background-service
volumes:
- ./stress_cpu.py:/app/stress_cpu.py
working_dir: /app
command: python stress_cpu.py
deploy:
resources:
limits:
memory: 128M
# cpu-shares требует особого подхода в новых версиях Docker
networks:
- resource-test-net

db:
image: postgres:13-alpine
container_name: limited-db
environment:
- POSTGRES_DB=testdb
- POSTGRES_USER=testuser
- POSTGRES_PASSWORD=testpass
volumes:
- db-data:/var/lib/postgresql/data
# device read/write bps также требуют особого подхода

5/8
networks:
- resource-test-net

volumes:
db-data:

networks:
resource-test-net:

Полное решение

6/8
version: "3.8"

services:
api:
image: nginx:alpine
container_name: api-service
ports:
- "8001:80"
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
networks:
- resource-test-net

data-processor:
image: python:3.9-slim
container_name: data-processor
volumes:
- ./stress_cpu.py:/app/stress_cpu.py
working_dir: /app
command: python stress_cpu.py
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
networks:
- resource-test-net

background:
image: python:3.9-slim
container_name: background-service
volumes:
- ./stress_cpu.py:/app/stress_cpu.py
working_dir: /app
command: python stress_cpu.py
# Для cpu-shares в docker-compose v3 мы используем специальный подход
# Так как [Link] не поддерживает cpu-shares напрямую
# Мы используем командную строку docker вместо docker-compose
# В реальном проекте следует запустить отдельно:
# docker update --cpu-shares=256 background-service
deploy:
resources:
limits:
memory: 128M
networks:
- resource-test-net

db:
image: postgres:13-alpine
container_name: limited-db
environment:
- POSTGRES_DB=testdb
- POSTGRES_USER=testuser

7/8
- POSTGRES_PASSWORD=testpass
volumes:
- db-data:/var/lib/postgresql/data
# Для device-read-bps и device-write-bps в docker-compose v3
# также нет прямой поддержки в секции deploy
# В реальном проекте следует запустить отдельно:
# docker update --device-read-bps /dev/sda:20mb --device-write-bps
/dev/sda:10mb limited-db
networks:
- resource-test-net

volumes:
db-data:

networks:
resource-test-net:

Примечание: В Docker Compose версии 3 некоторые ограничения ресурсов


(например, cpu-shares, device-read-bps и device-write-bps) не поддерживаются
напрямую через секцию [Link]. Для их настройки нужно использовать
docker update после запуска контейнеров:

# После запуска docker-compose up -d выполните:


docker update --cpu-shares=256 background-service
docker update --device-read-bps /dev/sda:20mb --device-write-bps /dev/sda:10mb
limited-db

Альтернативно, можно использовать версию Compose файла 2.x, которая


поддерживает эти опции напрямую, но лишена некоторых новых функций:

version: "2.4"

services:
api:
image: nginx:alpine
container_name: api-service
ports:
- "8001:80"
cpus: 1.0
mem_limit: 512m
networks:
- resource-test-net

data-processor:
image: python:3.9-slim
container_name: data-processor
volumes:
- ./stress_cpu.py:/app/stress_cpu.py
working_dir: /app
command: python stress_cpu.

8/8
8.3 Мониторинг использования ресурсов

Мониторинг использования ресурсов


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

Встроенные инструменты мониторинга (docker stats)


Docker предоставляет простой встроенный инструмент для мониторинга
использования ресурсов контейнерами — команду docker stats.

docker stats

Эта команда в реальном времени отображает основные метрики использования


ресурсов для всех запущенных контейнеров:

CONTAINER ID и NAME — идентификатор и имя контейнера


CPU % — процент использования CPU
MEM USAGE / LIMIT — текущее использование памяти и установленный
лимит
MEM % — процент использования доступной памяти
NET I/O — объем входящего и исходящего сетевого трафика
BLOCK I/O — объем операций чтения и записи на диск
PIDS — количество процессов, запущенных в контейнере

Вы можете фильтровать вывод для определенных контейнеров:

docker stats container1 container2

Или получить одноразовый снимок статистики без обновления в реальном времени:

docker stats --no-stream

Для получения статистики в формате JSON (удобно для скриптов):

docker stats --format "{{json .}}" --no-stream

Преимущества docker stats:

Не требует установки дополнительного ПО


Прост в использовании
Предоставляет базовую информацию

Недостатки docker stats:

1/12
Нет долгосрочного хранения метрик и их истории
Ограниченная детализация
Отсутствие визуализации и алертинга
Нет информации о хост-системе

Prometheus и Grafana для мониторинга Docker

Для серьезного мониторинга в продакшн-среде рекомендуется использовать связку


Prometheus и Grafana.

Prometheus
Prometheus — это система мониторинга с открытым исходным кодом, которая:

Собирает метрики из настроенных целей через HTTP


Хранит все метрики в временных рядах
Обеспечивает мощный язык запросов (PromQL)
Не требует распределенного хранилища
Обеспечивает отправку предупреждений (алертинг)

Grafana

Grafana — это платформа для визуализации и аналитики, которая:

Предоставляет богатые возможности для построения дашбордов


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

Настройка мониторинга с помощью Prometheus и Grafana

Давайте рассмотрим базовую настройку с использованием Docker Compose:

2/12
version: '3.8'

services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus/[Link]:/etc/prometheus/[Link]
- prometheus_data:/prometheus
command:
- '--[Link]=/etc/prometheus/[Link]'
- '--[Link]=/prometheus'
- '--[Link]=/etc/prometheus/console_libraries'
- '--[Link]=/etc/prometheus/consoles'
- '--[Link]-lifecycle'
restart: unless-stopped
networks:
- monitoring

grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_USERS_ALLOW_SIGN_UP=false
restart: unless-stopped
depends_on:
- prometheus
networks:
- monitoring

volumes:
prometheus_data:
grafana_data:

networks:
monitoring:
driver: bridge

Содержимое файла prometheus/[Link]:

3/12
global:
scrape_interval: 15s
evaluation_interval: 15s

scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']

- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']

- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']

После запуска этого compose-файла:

1. Prometheus будет доступен по адресу [Link]


2. Grafana будет доступна по адресу [Link] (login: admin,
password: admin)

В Grafana нужно:

1. Добавить Prometheus как источник данных (Configuration → Data Sources →


Add data source → Prometheus, URL: [Link]
2. Импортировать готовые дашборды для Docker (ID: 893, 1860, 10619)

cAdvisor и Node Exporter

Prometheus сам по себе не собирает метрики с контейнеров и хост-системы. Для


этого используются экспортёры — специальные программы, которые собирают
метрики и предоставляют их в формате, понятном для Prometheus.

cAdvisor (Container Advisor) — инструмент от Google для сбора, агрегации и


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

cAdvisor собирает метрики:

Использование CPU, памяти, диска и сети контейнерами


Исторические ресурсные тренды
Информацию о лимитах и резервировании ресурсов
Аппаратную конфигурацию хоста

Давайте добавим cAdvisor в наш compose-файл:

4/12
cadvisor:
image: [Link]/cadvisor/cadvisor:latest
container_name: cadvisor
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
restart: unless-stopped
networks:
- monitoring

cAdvisor будет доступен по адресу [Link] и будет предоставлять


web-интерфейс с детальной информацией о контейнерах, а также endpoint /metrics
для Prometheus.

Node Exporter

Node Exporter — экспортёр Prometheus, который собирает широкий спектр метрик о


состоянии хост-системы:

Загрузка CPU
Использование памяти
Использование дискового пространства
Сетевая статистика
Информация о файловой системе
Температура и другие аппаратные метрики

Добавим Node Exporter в наш compose-файл:

node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--[Link]=/host/proc'
- '--[Link]=/host/sys'
- '--[Link]=/rootfs'
- '--[Link]-mount-points=^/(sys|proc|dev|host|etc)
($$|/)'
restart: unless-stopped
networks:
- monitoring

Node Exporter будет доступен по адресу [Link]

5/12
Теперь у нас есть полная система мониторинга:

cAdvisor собирает метрики о контейнерах


Node Exporter собирает метрики о хост-системе
Prometheus хранит все эти метрики и предоставляет API для их запроса
Grafana визуализирует метрики в удобных дашбордах

Алертинг при превышении лимитов ресурсов

Мониторинг без алертинга — это всего лишь наблюдение. Для эффективного


управления инфраструктурой необходимо настроить оповещения о критических
ситуациях.

Настройка алертинга в Prometheus

Prometheus имеет встроенный механизм алертинга, который проверяет условия на


основе выражений PromQL и отправляет оповещения через Alertmanager.

Добавим Alertmanager в наш compose-файл:

alertmanager:
image: prom/alertmanager:latest
container_name: alertmanager
ports:
- "9093:9093"
volumes:
- ./alertmanager/[Link]:/etc/alertmanager/[Link]
restart: unless-stopped
networks:
- monitoring

Создадим конфигурацию для Alertmanager (alertmanager/[Link]):

global:
resolve_timeout: 5m
smtp_smarthost: '[Link]'
smtp_from: 'alertmanager@[Link]'
smtp_auth_username: 'username'
smtp_auth_password: 'password'

route:
group_by: ['alertname', 'instance', 'severity']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'email-notifications'

receivers:
- name: 'email-notifications'
email_configs:
- to: 'admin@[Link]'
send_resolved: true

6/12
Обновим конфигурацию Prometheus для работы с Alertmanager и добавим правила
алертинга:

global:
scrape_interval: 15s
evaluation_interval: 15s

alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager:9093']

rule_files:
- "/etc/prometheus/rules/*.yml"

scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']

- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']

- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']

Создадим правила алертинга (prometheus/rules/container_alerts.yml):

7/12
groups:
- name: container_alerts
rules:
- alert: ContainerCpuUsageHigh
expr: (sum(rate(container_cpu_usage_seconds_total{name!=""}[5m])) by (name) *
100) > 80
for: 5m
labels:
severity: warning
annotations:
summary: "Container CPU usage high (instance {{ $[Link] }})"
description: "Container {{ $[Link] }} CPU usage is above 80% for 5
minutes\n VALUE = {{ $value }}%"

- alert: ContainerMemoryUsageHigh
expr: (container_memory_usage_bytes{name!=""} /
container_spec_memory_limit_bytes{name!=""} * 100) > 80
for: 5m
labels:
severity: warning
annotations:
summary: "Container memory usage high (instance {{ $[Link] }})"
description: "Container {{ $[Link] }} memory usage is above 80% for 5
minutes\n VALUE = {{ $value }}%"

- alert: ContainerNearMemoryLimit
expr: (container_memory_usage_bytes{name!=""} /
container_spec_memory_limit_bytes{name!=""} * 100) > 95
for: 5m
labels:
severity: critical
annotations:
summary: "Container near memory limit (instance {{ $[Link] }})"
description: "Container {{ $[Link] }} is using more than 95% of its
memory limit for 5 minutes\n VALUE = {{ $value }}%"

Эти правила создадут предупреждения, когда:

Контейнер использует более 80% CPU в течение 5 минут


Контейнер использует более 80% выделенной ему памяти в течение 5 минут
Контейнер приближается к лимиту памяти (95% и выше) в течение 5 минут
(критическая ситуация)

Настройка алертинга в Grafana

Grafana также имеет собственную систему алертинга, которая может отправлять


уведомления через различные каналы (email, Slack, PagerDuty и т.д.).

Для настройки алертов в Grafana:

1. На панели с графиком нажмите Edit


2. Перейдите на вкладку Alert
3. Нажмите Create Alert

8/12
4. Настройте условия (например, "WHEN avg() OF query(A, 5m, now) IS ABOVE
80")
5. Настройте уведомления (в разделе Notifications)

Преимущество алертинга в Grafana — его тесная интеграция с визуализацией и


возможность настраивать алерты прямо на графиках. Недостаток — менее мощный
язык запросов по сравнению с PromQL.

Тестирование поведения при ограничениях ресурсов

Недостаточно просто настроить лимиты ресурсов и мониторинг — необходимо


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

Тестирование ограничений CPU

Для тестирования ограничений CPU можно использовать утилиту stress:

docker run --rm -it --cpus=0.5 --name=cpu-test alpine sh -c "apk add --no-cache
stress-ng && stress-ng --cpu 4 --timeout 60s"

Эта команда запустит контейнер с ограничением в 0.5 ядра CPU и попытается


создать нагрузку на 4 ядра в течение 60 секунд.

Во время выполнения этой команды в другом терминале запустите docker stats


cpu-test, чтобы наблюдать за использованием ресурсов.

Вы должны увидеть, что использование CPU не превышает 50%, несмотря на


попытку утилиты stress-ng нагрузить систему по максимуму.

Тестирование ограничений памяти

Для тестирования ограничений памяти можно использовать скрипт на Python:

docker run --rm -it --memory=100m --name=memory-test python:alpine python -c "


import numpy as np
try:
# Постепенно увеличиваем размер массива
for i in range(1, 50):
size = i * 10 * 1024 * 1024 # i * 10 МБ
print(f'Allocating array of size {size / (1024 * 1024):.1f} MB')
data = [Link](size, dtype=np.uint8)
# Немного подождем, чтобы увидеть результат в docker stats
import time
[Link](1)
except Exception as e:
print(f'Error: {e}')
"

Запустите docker stats memory-test в другом терминале. Вы увидите, как


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

9/12
Тестирование ограничений I/O

Для тестирования ограничений ввода-вывода можно использовать утилиту dd:

docker run --rm -it --device-write-bps /dev/sda:10mb --name=io-test alpine sh -c


"dd if=/dev/zero of=/tmp/test bs=1M count=100 oflag=direct"

Эта команда попытается записать 100 МБ нулей в файл со скоростью, не


превышающей 10 МБ/с.

Запустите docker stats io-test в другом терминале, чтобы наблюдать за BLOCK


I/O. Вы увидите, что скорость записи не превышает установленный лимит.

Комплексное тестирование

Для проверки, как система ведет себя с множеством контейнеров, работающих на


пределе своих ресурсов, можно создать docker-compose файл, который запускает
несколько контейнеров с различными ограничениями и нагрузками:

version: '3.8'

services:
high-cpu:
image: alpine
container_name: high-cpu
command: sh -c "apk add --no-cache stress-ng && stress-ng --cpu 2 --timeout
1h"
deploy:
resources:
limits:
cpus: '0.7'
memory: 100M

high-memory:
image: python:alpine
container_name: high-memory
command: python -c "import numpy as np; data = [Link](80 * 1024 * 1024,
dtype=np.uint8); import time; [Link](3600)"
deploy:
resources:
limits:
cpus: '0.3'
memory: 100M

balanced:
image: alpine
container_name: balanced
command: sh -c "apk add --no-cache stress-ng && stress-ng --cpu 1 --vm 1 --vm-
bytes 50M --timeout 1h"
deploy:
resources:
limits:
cpus: '0.5'
memory: 70M

10/12
Запустите этот compose-файл и наблюдайте за поведением системы через docker
stats или Prometheus/Grafana. Обратите внимание, как Docker распределяет
ресурсы между контейнерами и как система реагирует, когда контейнеры
приближаются к своим лимитам или превышают их.

Поведение Docker при превышении лимитов

Важно понимать, как Docker реагирует на превышение различных лимитов


ресурсов:

При превышении лимита CPU

Docker не завершает контейнеры, которые пытаются использовать больше CPU,


чем им выделено. Вместо этого Docker ограничивает использование CPU до
установленного значения.

Например, если контейнер ограничен 0.5 ядрами CPU и пытается использовать 2


ядра, его фактическое использование будет ограничено до 0.5 ядра.

При превышении лимита памяти

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


происходит следующее:

1. Если --oom-kill-disable не установлен (по умолчанию), OOM-киллер


завершает процессы в контейнере
2. Если --oom-kill-disable установлен, контейнер может зависнуть, пока не
освободится память

В любом случае, превышение лимита памяти может привести к нестабильной


работе приложения.

При превышении лимита I/O

Docker ограничивает скорость чтения/записи до установленного значения, но не


завершает контейнер при попытке выполнить операции I/O быстрее, чем позволяет
лимит. Это приводит к замедлению работы приложения, но не к его завершению.

Итог
docker stats — простой встроенный инструмент для базового мониторинга
контейнеров
Prometheus и Grafana — мощная связка для профессионального мониторинга
и визуализации
cAdvisor — собирает детальные метрики о контейнерах
Node Exporter — собирает метрики о хост-системе
Alertmanager — управляет оповещениями и их маршрутизацией
Тестирование лимитов — необходимый шаг для проверки поведения
системы в экстремальных условиях

11/12
В продакшн-среде комбинация правильно настроенных ограничений ресурсов,
мониторинга и алертинга — необходимый минимум для обеспечения стабильной и
предсказуемой работы контейнерной инфраструктуры.

12/12
8.4 Docker Secrets

Docker Secrets
При разработке и развертывании приложений в контейнерах возникает важный
вопрос: как безопасно передавать чувствительную информацию (пароли, токены
API, SSL-сертификаты) в контейнеры? Простое добавление этих данных в Dockerfile
или передача через переменные окружения не обеспечивает должного уровня
безопасности. Docker Secrets предлагает решение этой проблемы.

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


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

Основные преимущества Docker Secrets:

Централизованное хранение — секреты хранятся в централизованном месте


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

Секреты могут включать в себя различные типы чувствительных данных:

Пароли от баз данных


Токены доступа к API
SSH-ключи
TLS-сертификаты и ключи
Конфигурационные файлы с чувствительными данными

Важно понимать: Docker Secrets изначально разработан для работы с Docker


Swarm (режим оркестрации), но также может использоваться с одиночными
контейнерами и Docker Compose с некоторыми ограничениями.

Создание и управление секретами

Инициализация Docker Swarm

Для полноценной работы с Docker Secrets необходимо инициализировать Docker


Swarm:

1/14
docker swarm init

Если у вас несколько сетевых интерфейсов, может потребоваться указать IP-адрес:

docker swarm init --advertise-addr=[Link]

Создание секрета

Существует несколько способов создания секрета:

1. Создание секрета из файла:

# Сначала создаем файл с секретом


echo "myStrongPassword123" > db_password.txt

# Затем создаем секрет из этого файла


docker secret create db_password db_password.txt

# После создания секрета рекомендуется удалить исходный файл


rm db_password.txt

2. Создание секрета непосредственно из стандартного ввода:

echo "myApiToken123" | docker secret create api_token -

Команда создает секрет с именем api_token, содержимое которого берется из


переданного потока.

3. Создание секрета интерактивно:

docker secret create interactive_secret -

После выполнения команды вы можете ввести содержимое секрета и нажать Ctrl+D


для завершения ввода.

Просмотр списка секретов

Для просмотра списка всех секретов используйте команду:

docker secret ls

Результат будет выглядеть примерно так:

ID NAME CREATED UPDATED


rlafn2qsbgn2igy99qyjnzkdn api_token 6 seconds ago 6 seconds ago
2jrhpzf1103560chdmtk0gm4q db_password 1 minute ago 1 minute ago

Просмотр детальной информации о секрете

Для получения более подробной информации о конкретном секрете:

docker secret inspect db_password

2/14
Обратите внимание, что вы не сможете увидеть фактическое содержимое секрета,
только метаданные:

[
{
"ID": "2jrhpzf1103560chdmtk0gm4q",
"Version": {
"Index": 17
},
"CreatedAt": "2023-10-25T15:42:56.363Z",
"UpdatedAt": "2023-10-25T15:42:56.363Z",
"Spec": {
"Name": "db_password",
"Labels": {}
}
}
]

Удаление секрета

Когда секрет больше не нужен, его можно удалить:

docker secret rm db_password

Важно: Перед удалением секрета убедитесь, что он не используется ни одним


сервисом, иначе операция завершится с ошибкой.

Использование секретов в Docker Compose

Docker Compose (начиная с версии 3.1) поддерживает работу с секретами. Вот как
это можно реализовать:

Пример [Link] с использованием секретов

3/14
version: '3.8'

services:
web:
image: nginx:alpine
ports:
- "80:80"
secrets:
- [Link]
- [Link]
volumes:
- ./[Link]:/etc/nginx/[Link]:ro

db:
image: postgres:13
environment:
- POSTGRES_USER=postgres
secrets:
- source: db_password
target: postgres_password
uid: '999' # UID пользователя postgres в контейнере
gid: '999' # GID пользователя postgres в контейнере
mode: 0400 # Только чтение для владельца

secrets:
[Link]:
file: ./secrets/[Link]
[Link]:
file: ./secrets/[Link]
db_password:
file: ./secrets/db_password.txt
# или можно использовать внешний секрет, созданный в Docker Swarm
# external: true

В этом примере:

Мы определяем три секрета: [Link], [Link] и db_password.


Сервис web имеет доступ к двум секретам ([Link] и [Link]).
Сервис db имеет доступ к секрету db_password, который монтируется с
указанными uid, gid и режимом доступа.

Опции для определения секрета в services

При определении секрета в разделе services можно использовать краткую или


расширенную форму:

Краткая форма:

services:
myservice:
secrets:
- my_secret

Расширенная форма:

4/14
services:
myservice:
secrets:
- source: my_secret
target: /run/secrets/my_custom_name # Переименование секрета внутри
контейнера
uid: '103' # UID владельца файла секрета
gid: '103' # GID владельца файла секрета
mode: 0440 # Права доступа к файлу секрета

Опции для объявления секрета в разделе secrets

При объявлении секрета в верхнеуровневом разделе secrets есть несколько


способов:

Из файла:

secrets:
my_secret:
file: ./path/to/[Link]

Из внешнего секрета, созданного в Docker Swarm:

secrets:
my_secret:
external: true

С явным указанием имени внешнего секрета:

secrets:
my_secret:
external: true
name: actual_secret_name_in_swarm

Важное замечание: При использовании Docker Compose без Swarm (с docker-


compose up вместо docker stack deploy), секреты реализуются как обычные файлы
(без шифрования), монтируемые в /run/secrets/ внутри контейнера. Это удобно
для разработки, но не обеспечивает реальной безопасности в продакшн-среде.

Доступ к секретам из контейнеров

Когда секрет монтируется в контейнер, он доступен как обычный файл в директории


/run/secrets/.

Базовый пример доступа к секрету

Рассмотрим простой пример контейнера, который использует секрет для


подключения к базе данных:

5/14
# Инициализация Docker Swarm
docker swarm init

# Создание секрета
echo "db_password123" | docker secret create db_password -

# Запуск сервиса, использующего секрет


docker service create \
--name db-client \
--secret db_password \
alpine sh -c "while true; do cat /run/secrets/db_password; sleep 10; done"

Этот пример создает сервис, который каждые 10 секунд выводит содержимое


секрета.

Доступ к секрету в различных языках программирования

Bash:

#!/bin/bash
DB_PASSWORD=$(cat /run/secrets/db_password)
echo "Connecting to database with password: $DB_PASSWORD"

Python:

import os

def get_secret(secret_name):
try:
with open(f'/run/secrets/{secret_name}', 'r') as secret_file:
return secret_file.read().strip()
except IOError:
return None

db_password = get_secret('db_password')
print(f"Connecting to database with password: {db_password}")

[Link]:

const fs = require('fs');

function getSecret(secretName) {
try {
return [Link](`/run/secrets/${secretName}`, 'utf8').trim();
} catch (err) {
return null;
}
}

const dbPassword = getSecret('db_password');


[Link](`Connecting to database with password: ${dbPassword}`);

Использование секретов с популярными образами

6/14
Многие официальные образы уже поддерживают Docker Secrets. Например, образ
MySQL может автоматически использовать секрет mysql-root-password:

version: '3.8'

services:
db:
image: mysql:8.0
environment:
- MYSQL_DATABASE=mydb
- MYSQL_USER=user
secrets:
- mysql-root-password

secrets:
mysql-root-password:
file: ./[Link]

Содержимое секрета mysql-root-password будет использовано как пароль root в


MySQL.

Стратегии ротации секретов

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


Docker Secrets предоставляет возможности для обновления секретов без простоев
сервисов.

Базовый подход к ротации секретов

Процесс ротации секрета обычно включает следующие шаги:

1. Создание нового секрета (новая версия)


2. Обновление сервиса для использования нового секрета
3. Удаление старого секрета (когда он больше не используется)

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

# Создание нового секрета


echo "newStrongPassword456" | docker secret create db_password_v2 -

# Обновление сервиса для использования нового секрета


docker service update \
--secret-rm db_password \
--secret-add db_password_v2 \
my-service

# После успешного обновления можно удалить старый секрет


docker secret rm db_password

Более сложные стратегии ротации

В реальных продакшн-средах часто используются более сложные стратегии:

7/14
1. Плавная ротация с периодом перекрытия:
Создайте новый секрет
Обновите ваше приложение для поддержки как старого, так и нового
секрета одновременно
Обновите сервис для доступа к обоим секретам
Постепенно переведите клиентов на новый секрет
Когда старый секрет больше не используется, удалите его
2. Ротация через временные метки:
Используйте схему именования с временными метками (например,
db_password_20231025)
Автоматизируйте создание новых секретов по расписанию
Настройте автоматическое обновление сервисов для использования
новейшего секрета

Автоматизация ротации с помощью CI/CD

Для автоматизации процесса ротации секретов можно использовать CI/CD-


пайплайны:

#!/bin/bash
# Пример скрипта для автоматической ротации секрета

# Генерация нового пароля


NEW_PASSWORD=$(openssl rand -base64 20)

# Создание нового секрета


echo "$NEW_PASSWORD" | docker secret create db_password_new -

# Обновление сервиса
docker service update \
--secret-rm db_password \
--secret-add source=db_password_new,target=db_password \
my-service

# Обновление ссылки для следующей ротации


docker secret rm db_password || true
docker secret create db_password -
echo "$NEW_PASSWORD" | docker secret create db_password -

# Удаление временного секрета


docker secret rm db_password_new

Альтернативы (Vault, AWS Secrets Manager и т.д.)


Docker Secrets — отличное решение в рамках экосистемы Docker Swarm, но
существуют и другие системы управления секретами, которые предлагают
дополнительные возможности.

HashiCorp Vault

8/14
HashiCorp Vault — один из самых мощных инструментов для управления
секретами.

Основные преимущества Vault:

Динамическое создание секретов (например, учетных данных базы данных по


запросу)
Полный аудит доступа к секретам
Автоматическая ротация секретов
Криптографические операции (шифрование/дешифрование данных)
Гибкие политики доступа
Поддержка различных методов аутентификации (LDAP, JWT, Cloud IAM и т.д.)

Пример интеграции Vault с Docker:

version: '3.8'

services:
app:
image: myapp:latest
environment:
- VAULT_ADDR=[Link]
entrypoint:
- /bin/sh
- -c
- |
export VAULT_TOKEN=$(cat /run/secrets/vault-token)
# Получение секрета из Vault
export DB_PASSWORD=$(vault kv get -field=password secret/database)
# Запуск приложения с полученными секретами
exec npm start
secrets:
- vault-token

vault:
image: vault:latest
ports:
- "8200:8200"
environment:
- VAULT_DEV_ROOT_TOKEN_ID=dev-only-token
cap_add:
- IPC_LOCK

secrets:
vault-token:
file: ./[Link]

AWS Secrets Manager

AWS Secrets Manager — решение от Amazon для управления секретами в облаке


AWS.

Ключевые возможности:

9/14
Централизованное хранение и управление секретами
Автоматическая ротация секретов
Интеграция с другими сервисами AWS
Детальный контроль доступа через IAM
Шифрование с использованием AWS KMS

Пример интеграции с Docker:

version: '3.8'

services:
app:
image: myapp:latest
environment:
- AWS_REGION=us-east-1
volumes:
- ~/.aws:/root/.aws:ro # Монтирование AWS-конфигурации для доступа к
Secrets Manager
entrypoint:
- /bin/sh
- -c
- |
# Получение секрета из AWS Secrets Manager
export DB_PASSWORD=$(aws secretsmanager get-secret-value --secret-id
database-credentials --query SecretString --output text | jq -r .password)
# Запуск приложения
exec npm start

Google Cloud Secret Manager

Google Cloud Secret Manager предоставляет сервис для хранения и доступа к


секретам в Google Cloud.

Особенности:

Централизованное хранение секретов в Google Cloud


Версионирование секретов
Интеграция с IAM для контроля доступа
Аудит доступа
Интеграция с другими сервисами Google Cloud

Kubernetes Secrets

Если вы используете Kubernetes вместо Docker Swarm, Kubernetes Secrets


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

Особенности Kubernetes Secrets:

Интеграция с подами Kubernetes


Поддержка различных типов секретов (Opaque, TLS, Docker registry, и т.д.)
Возможность монтирования как файлов или переменных окружения

10/14
Интеграция с внешними системами управления секретами через CSI
(Container Storage Interface)

Сравнение решений для управления секретами

Лучше всего
Решение подходит для Особенности Ограничения

Docker Docker Swarm Простота Ограниченная


Secrets окружения использования, функциональность, привязка
интеграция с к Swarm
Docker

HashiCorp Крупные Максимальная Сложность настройки и


Vault компании, гибкость, обслуживания
мультиоблачные динамические
окружения секреты

AWS Приложения в Интеграция с Привязка к облачной


Secrets AWS сервисами AWS платформе Amazon
Manager

Google Приложения в Интеграция с Привязка к облачной


Cloud Google Cloud сервисами платформе Google
Secret Google Cloud
Manager

Kubernetes Кластеры Тесная По умолчанию хранятся без


Secrets Kubernetes интеграция с шифрования (требуются
Kubernetes дополнительные настройки)

Пример использования Docker Secrets в реальном проекте

Давайте рассмотрим более комплексный пример использования Docker Secrets в


реальном проекте:

11/14
# Инициализация Docker Swarm
docker swarm init

# Создание секретов для различных компонентов


echo "myStrongDbPassword" | docker secret create db_password -
echo "myVerySecretApiKey" | docker secret create api_key -
openssl req -newkey rsa:2048 -nodes -keyout [Link] -x509 -days 365 -out [Link]
docker secret create tls_cert [Link]
docker secret create tls_key [Link]
rm [Link] [Link]

# Создание Docker Compose файла для деплоя


cat > [Link] << 'EOF'
version: '3.8'

services:
nginx:
image: nginx:alpine
ports:
- "443:443"
secrets:
- source: tls_cert
target: /etc/nginx/ssl/[Link]
- source: tls_key
target: /etc/nginx/ssl/[Link]
configs:
- source: nginx_config
target: /etc/nginx/conf.d/[Link]
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 10s
networks:
- frontend
- backend

api:
image: myapp/api:latest
environment:
- NODE_ENV=production
secrets:
- source: api_key
target: /run/secrets/app_api_key
- source: db_password
target: /run/secrets/app_db_password
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
networks:
- backend

db:
image: postgres:13

12/14
environment:
- POSTGRES_USER=app
- POSTGRES_DB=appdb
secrets:
- source: db_password
target: /run/secrets/postgres_password
uid: '999'
gid: '999'
mode: 0400
deploy:
placement:
constraints:
- [Link] == manager
volumes:
- db-data:/var/lib/postgresql/data
networks:
- backend

networks:
frontend:
backend:

volumes:
db-data:

configs:
nginx_config:
content: |
server {
listen 443 ssl;
ssl_certificate /etc/nginx/ssl/[Link];
ssl_certificate_key /etc/nginx/ssl/[Link];

location / {
proxy_pass [Link]
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}

secrets:
db_password:
external: true
api_key:
external: true
tls_cert:
external: true
tls_key:
external: true
EOF

# Деплой стека
docker stack deploy -c [Link] myapp

В этом примере мы:

13/14
1. Создаем несколько секретов: пароль для БД, API-ключ и TLS-сертификаты.
2. Настраиваем Docker Compose-файл для использования этих секретов в
разных сервисах.
3. Монтируем секреты в разные локации в зависимости от потребностей
приложения.
4. Устанавливаем специальные права доступа для секрета БД внутри контейнера
PostgreSQL.

14/14
8.5 Docker BuildKit

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

Что такое BuildKit и его преимущества

BuildKit — это новый инструмент сборки, разработанный Docker для создания


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

BuildKit был создан с нуля с учетом современных требований к процессам сборки


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

Основные преимущества BuildKit:

1. Повышенная эффективность — параллельное выполнение этапов сборки,


которые не зависят друг от друга
2. Умное кеширование — более умные алгоритмы определения, когда кеш
можно переиспользовать
3. Улучшенная изоляция — отдельные этапы сборки изолированы друг от друга
4. Безопасная передача секретов — возможность безопасно передавать
приватные ключи, пароли и другие секреты в процессе сборки
5. Поддержка SSH-агента — позволяет использовать ваши SSH-ключи внутри
процесса сборки
6. Условные инструкции — возможность использования условной логики в
Dockerfile
7. Улучшенная отчетность — более информативный вывод о ходе сборки
8. Более эффективные сборки многоэтапных образов — оптимизация для
multi-stage builds

Включение BuildKit

Есть несколько способов включить BuildKit в Docker:

1. Через переменную окружения:

# В Linux/macOS
export DOCKER_BUILDKIT=1

# В Windows PowerShell
$env:DOCKER_BUILDKIT=1

1/12
2. В конфигурационном файле Docker:

Создайте или отредактируйте файл /etc/docker/[Link] (на Linux) или


C:\ProgramData\docker\config\[Link] (на Windows):

{
"features": {
"buildkit": true
}
}

После изменения конфигурации необходимо перезапустить демон Docker:

# На Linux
sudo systemctl restart docker

# На Windows
Restart-Service docker

3. Явное указание при запуске сборки:

DOCKER_BUILDKIT=1 docker build .

После включения BuildKit вы заметите другой вывод при запуске docker build —
более структурированный и информативный.

Параллельное выполнение этапов сборки

Одно из главных преимуществ BuildKit — способность параллельно выполнять


независимые этапы сборки, что существенно ускоряет весь процесс.

Как это работает?

В традиционном процессе сборки Docker, каждая инструкция в Dockerfile


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

Для демонстрации создадим многоэтапный Dockerfile, где некоторые стадии не


зависят друг от друга:

2/12
# Многоэтапный Dockerfile с независимыми этапами
FROM golang:1.18 AS build-backend
WORKDIR /app
COPY backend/[Link] backend/[Link] ./
RUN go mod download
COPY backend/ ./
RUN go build -o server .

FROM node:16 AS build-frontend


WORKDIR /app
COPY frontend/[Link] frontend/[Link] ./
RUN npm install
COPY frontend/ ./
RUN npm run build

FROM alpine:3.15
WORKDIR /app
COPY --from=build-backend /app/server ./
COPY --from=build-frontend /app/dist ./static
EXPOSE 8080
CMD ["./server"]

В этом примере:

Этапы build-backend и build-frontend не зависят друг от друга


BuildKit определит это и запустит их параллельно
Заключительный этап будет ждать завершения обоих предыдущих этапов

Вы можете визуально увидеть параллельное выполнение в выводе команды docker


build при включенном BuildKit.

Преимущества параллельной сборки

Увеличение скорости сборки — независимые этапы выполняются


одновременно
Эффективное использование ресурсов — полная загрузка всех доступных
CPU-ядер
Уменьшение времени ожидания для CI/CD — ускорение пайплайнов
непрерывной интеграции

Кеширование и ускорение сборки


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

Улучшенное кеширование в BuildKit

1. Интеллектуальное распознавание изменений — BuildKit более точно


определяет, когда содержимое файлов действительно изменилось
2. Кеширование на основе содержимого — в отличие от старой системы,
которая основывалась на инструкциях Docker и их аргументах

3/12
3. Распределенное кеширование — возможность использовать удаленный кеш,
доступный всем членам команды или CI-серверам
4. Пропуск ненужных этапов — BuildKit может пропустить этапы, результаты
которых не используются в итоговом образе

Оптимизация кеширования: практические рекомендации

1. Управление зависимостями отдельно от кода

FROM node:16
WORKDIR /app

# Копируем только файлы зависимостей


COPY [Link] [Link] ./
RUN npm install

# Теперь копируем остальной код


COPY . .
RUN npm run build

Этот подход позволяет переиспользовать кеш для установки зависимостей, пока


файлы [Link] и [Link] не изменились.

2. Использование директивы COPY --link

BuildKit поддерживает новую опцию --link для команды COPY, которая улучшает
кеширование:

COPY --link /src /dest

Флаг --link изменяет механизм копирования, делая его более эффективным в


контексте BuildKit.

3. Внешний кеш через Registry

BuildKit поддерживает импорт и экспорт кеша через Docker Registry:

# Экспорт кеша в registry


docker buildx build --push \
--cache-to type=registry,ref=[Link]/myuser/myapp:cache \
-t [Link]/myuser/myapp:latest .

# Импорт кеша из registry


docker buildx build --push \
--cache-from type=registry,ref=[Link]/myuser/myapp:cache \
-t [Link]/myuser/myapp:latest .

Это особенно полезно для CI/CD-систем и распределенных команд.

4. Кеширование через Docker socket

Для локальной разработки можно использовать более простой подход:

4/12
docker build --cache-from myapp:latest -t myapp:latest .

Этот подход говорит Docker использовать слои из существующего образа


myapp:latest для ускорения новой сборки.

Тайные переменные (secret) при сборке


Одна из важнейших особенностей BuildKit — возможность безопасно передавать
секреты в процесс сборки без их сохранения в слоях образа.

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

В традиционном процессе сборки Docker, если вам нужен доступ к приватному


репозиторию, токену API или другим секретам, у вас было несколько не очень
хороших вариантов:

Добавить секреты прямо в Dockerfile (крайне небезопасно)


Использовать build args (видны в истории образа)
Монтировать секреты через комплексные решения вне Docker

BuildKit решает эту проблему с помощью временных монтирований, которые


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

Как использовать секреты в BuildKit

1. Передача секрета при сборке

# Создаем файл с секретом


echo "my-secret-token" > ./[Link]

# Используем секрет при сборке


docker build --secret id=mysecret,src=./[Link] .

2. Доступ к секрету в Dockerfile

# В Dockerfile
FROM alpine

# Доступ к секрету во время выполнения RUN


RUN --mount=type=secret,id=mysecret \
cat /run/secrets/mysecret | xargs echo "Value of secret:"

# Остальная часть Dockerfile


CMD ["echo", "Image built with secret"]

В этом примере:

Секрет mysecret монтируется в путь /run/secrets/mysecret только во время


выполнения инструкции RUN
Секрет не сохраняется в слоях образа
После завершения инструкции RUN секрет больше не доступен

5/12
Практические примеры использования секретов

1. Доступ к приватному npm-репозиторию

# Создаем .npmrc с токеном


echo "//[Link]/:_authToken=my-npm-token" > .[Link]

# Запускаем сборку с секретом


docker build --secret id=npmrc,src=.[Link] .

# В Dockerfile
FROM node:16
WORKDIR /app
COPY [Link] [Link] ./

# Используем секрет для доступа к приватному репозиторию


RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm install

COPY . .
RUN npm run build
CMD ["npm", "start"]

2. Клонирование приватного Git-репозитория

# Запускаем сборку с секретом


docker build --secret id=gitcredentials,src=$HOME/.git-credentials .

# В Dockerfile
FROM alpine
RUN apk add --no-cache git

# Клонирование приватного репозитория с использованием секрета


RUN --mount=type=secret,id=gitcredentials,target=/root/.git-credentials \
git config --global [Link] 'store --file /root/.git-credentials' &&
\
git clone [Link]

# После этой инструкции учетные данные Git не сохраняются в образе


CMD ["echo", "Repository cloned"]

SSH-агент при сборке


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

Как это работает?

SSH-сокет с вашей хост-машины может быть временно смонтирован в процесс


сборки. Это позволяет командам внутри сборки использовать ваши SSH-ключи и
авторизацию, не требуя копирования ключей.

Использование SSH-агента в BuildKit

6/12
1. Базовый пример с SSH-агентом

# Запускаем сборку с использованием SSH-агента


docker build --ssh default .

# В Dockerfile
FROM alpine
RUN apk add --no-cache openssh-client git

# Клонирование репозитория через SSH


RUN --mount=type=ssh \
mkdir -p -m 0700 ~/.ssh && \
ssh-keyscan [Link] >> ~/.ssh/known_hosts && \
git clone git@[Link]:username/[Link]

CMD ["echo", "Repository cloned via SSH"]

В этом примере:

SSH-агент с хост-машины монтируется в контейнер через --mount=type=ssh


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

2. Использование нескольких идентификаторов SSH

Можно указать несколько идентификаторов SSH для разных хостов:

# Запускаем сборку с несколькими SSH-идентификаторами


docker build \
--ssh github=~/.ssh/github_id_rsa \
--ssh gitlab=~/.ssh/gitlab_id_rsa \
.

# В Dockerfile
FROM alpine
RUN apk add --no-cache openssh-client git

# Клонирование с использованием конкретного идентификатора SSH


RUN --mount=type=ssh,id=github \
git clone git@[Link]:username/[Link]

RUN --mount=type=ssh,id=gitlab \
git clone git@[Link]:username/[Link]

CMD ["echo", "Repositories cloned"]

Практический пример: сборка приложения с приватными зависимостями

# Запускаем сборку с SSH


docker build --ssh default .

7/12
# В Dockerfile
FROM golang:1.18 AS builder
RUN apt-get update && apt-get install -y openssh-client git
WORKDIR /app

# Настройка Git для использования SSH


RUN mkdir -p -m 0700 ~/.ssh && \
ssh-keyscan [Link] >> ~/.ssh/known_hosts

# Клонирование приватного Go-модуля через SSH


RUN --mount=type=ssh \
git config --global url."git@[Link]:".insteadOf "[Link]

COPY [Link] [Link] ./


RUN go mod download
COPY . .
RUN go build -o app .

FROM alpine:3.15
COPY --from=builder /app/app /usr/local/bin/
CMD ["app"]

Этот пример показывает, как собрать Go-приложение, которое зависит от приватных


модулей, доступных только через SSH.

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

BuildKit добавляет возможность использования условной логики в Dockerfile, что


делает процесс сборки более гибким и адаптируемым к разным окружениям и
ситуациям.

Синтаксис условных инструкций

BuildKit поддерживает новый синтаксис в Dockerfile для условных инструкций:

# Условная инструкция с IF
RUN --mount=type=bind,source=.,target=/src \
if [ -f /src/[Link] ]; then \
pip install -r /src/[Link]; \
fi

# Условная инструкция с IF-ELSE


RUN if [ "$ENVIRONMENT" = "production" ]; then \
echo "Building for production"; \
else \
echo "Building for development"; \
fi

Примеры использования условных инструкций

1. Установка разных зависимостей в зависимости от окружения

8/12
FROM python:3.9

WORKDIR /app
COPY [Link] .

ARG ENVIRONMENT=development

RUN if [ "$ENVIRONMENT" = "production" ]; then \


pip install -r [Link]; \
else \
pip install -r [Link] && \
pip install pytest pytest-cov flake8; \
fi

COPY . .

CMD ["python", "[Link]"]

При сборке можно указать окружение:

docker build --build-arg ENVIRONMENT=production -t myapp:prod .

2. Выбор базового образа в зависимости от архитектуры

# syntax=docker/dockerfile:1.3

ARG ARCH=amd64

FROM --platform=$BUILDPLATFORM alpine AS detect


ARG BUILDPLATFORM
RUN echo "Building on $BUILDPLATFORM"

FROM alpine:3.15 AS base-amd64


RUN echo "Using AMD64 image"

FROM arm64v8/alpine:3.15 AS base-arm64


RUN echo "Using ARM64 image"

FROM base-${ARCH} AS final


COPY --from=detect /etc/alpine-release /alpine-release

CMD ["cat", "/alpine-release"]

При сборке можно выбрать архитектуру:

docker build --build-arg ARCH=arm64 -t myapp:arm64 .

3. Условная компиляция в зависимости от наличия файла

9/12
FROM golang:1.18 AS builder
WORKDIR /app

COPY [Link] [Link] ./


RUN go mod download

COPY . .

RUN if [ -f "main_$(uname -m).go" ]; then \


go build -o app main_$(uname -m).go; \
else \
go build -o app [Link]; \
fi

FROM alpine:3.15
COPY --from=builder /app/app /usr/local/bin/
CMD ["app"]

Этот пример компилирует разные версии приложения в зависимости от архитектуры


хост-машины.

Передовые приемы с условными инструкциями

1. Многоэтапная сборка с условным выбором этапа

# syntax=docker/dockerfile:1.3

FROM golang:1.18 AS builder-debug


WORKDIR /app
COPY . .
RUN go build -gcflags="all=-N -l" -o app .

FROM golang:1.18 AS builder-release


WORKDIR /app
COPY . .
RUN go build -ldflags="-s -w" -o app .

FROM alpine:3.15
ARG BUILD_TYPE=release
COPY --from=builder-${BUILD_TYPE} /app/app /usr/local/bin/
CMD ["app"]

При сборке можно выбрать тип сборки:

docker build --build-arg BUILD_TYPE=debug -t myapp:debug .

2. Комбинирование условных инструкций с секретами

10/12
FROM node:16
WORKDIR /app
COPY [Link] ./

ARG USE_PRIVATE_REGISTRY=false

RUN if [ "$USE_PRIVATE_REGISTRY" = "true" ]; then \


--mount=type=secret,id=npmrc,target=/root/.npmrc \
npm install; \
else \
npm install; \
fi

COPY . .
CMD ["npm", "start"]

Этот пример позволяет опционально использовать приватный npm-регистр.

Дополнительные возможности BuildKit

1. Улучшенный вывод и диагностика

BuildKit обеспечивает более структурированный вывод при сборке, включая:

Отображение прогресса для каждого этапа


Цветовое кодирование для лучшего восприятия
Более подробные сообщения об ошибках

2. Встроенная поддержка мультиплатформенных сборок

BuildKit через docker buildx делает кроссплатформенные сборки значительно


проще:

docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest .

3. Новые бэкенды хранения кеша

BuildKit поддерживает различные бэкенды для хранения кеша:

Registry (Docker Hub, ECR, GCR и т.д.)


Local (локальная файловая система)
S3-совместимые хранилища
Distributed хранилища (для кластеров)

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

11/12
Продвинутое кеширование минимизирует время повторных сборок
благодаря умному определению изменений.
Секреты позволяют безопасно использовать чувствительную информацию во
время сборки без ее включения в образ.
SSH-агент дает возможность использовать SSH-ключи хост-машины для
доступа к приватным ресурсам.
Условные инструкции делают Dockerfile более гибким и адаптируемым к
разным ситуациям.

BuildKit становится стандартом для сборки Docker-образов и предлагает множество


преимуществ, которые делают процесс сборки быстрее, безопаснее и удобнее.
Внедрение BuildKit в рабочий процесс позволяет значительно оптимизировать
CI/CD-пайплайны и улучшить опыт разработки с Docker.

12/12
8.6 Docker Plugins

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

Что такое Docker Plugin


Docker Plugin — это специальный контейнер, который регистрируется в Docker как
плагин и взаимодействует с ним через определённый API. Плагин запускается и
управляется демоном Docker, поэтому он должен соответствовать определённым
требованиям к формату и структуре.

Ключевые особенности Docker Plugin:

Изоляция: плагин запускается в контейнеризированной среде, что повышает


безопасность и упрощает управление жизненным циклом плагина.
Определённые права (privileges): плагин может запрашивать определённые
привилегии (доступ к сокетам, сетевым интерфейсам и т.д.), что даёт Docker
чёткое понимание, к чему плагину необходим доступ.
Управление через Docker CLI: плагины можно создавать, публиковать,
устанавливать, включать, отключать и удалять стандартными командами
Docker, без ручного управления служебными файлами.
Версионирование и распространение: плагины можно публиковать в Docker
Hub или в приватных реестрах, поддерживается версия плагина (plugin version)
и его обновление.

Типы Docker-плагинов

Network plugins — обеспечивают дополнительную сетевую функциональность


(например, интеграция с SDN-системами вроде Weave, Calico и т.д.).
Volume plugins — позволяют использовать внешние хранилища (NFS, Ceph,
GlusterFS и др.) как тома Docker.
Authorization plugins — позволяют внедрять собственную логику
аутентификации и авторизации для Docker.
Log plugins — дают возможность подключать нестандартные драйверы
логирования (например, отправка логов в конкретные внешние сервисы).
Metrics/Monitoring plugins — расширяют мониторинг и метрики Docker,
отправляя данные в Prometheus, Datadog, New Relic и т.п.

1/6
Other plugins — любые другие плагины, реализующие необходимую вам
функциональность (например, секреты, шифрование томов и т.д.).

Установка, включение и управление плагинами


Docker предоставляет набор команд для работы с плагинами:

docker plugin ls — список установленных плагинов.


docker plugin install <PLUGIN> — установка плагина из реестра или
локального архива.
docker plugin enable <PLUGIN> — включение плагина.
docker plugin disable <PLUGIN> — отключение плагина.
docker plugin upgrade <PLUGIN> — обновление плагина.
docker plugin remove <PLUGIN> — удаление плагина.
docker plugin create <REPO:TAG> . — создание собственного плагина из
Dockerfile-подобного манифеста ([Link] и содержимого).
docker plugin push <REPO:TAG> — отправка плагина в Docker Hub или другой
реестр.
docker plugin pull <REPO:TAG> — загрузка плагина из реестра.

Установка плагина

docker plugin install store/docker/example-plugin:latest

По умолчанию Docker при установке плагина спрашивает разрешения,


необходимые плагину. Например, если плагину нужен доступ к сокету Docker или
сети, это будет отображено при установке.

Plugin "store/docker/example-plugin:latest" is requesting the following


privileges:
- network: [host]
- mount: [/var/lib/docker/plugins/]
Do you grant the above permissions? [y/N]

После подтверждения плагин будет скачан и автоматически включён (если он не


требует ручного подтверждения).

Отключение и удаление плагина

Если нужно временно отключить плагин:

docker plugin disable store/docker/example-plugin:latest

Для полного удаления из системы:

docker plugin remove store/docker/example-plugin:latest

Обновление плагина

docker plugin upgrade store/docker/example-plugin:latest

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

Пример использования Volume Plugin


Рассмотрим один из самых популярных сценариев — использование volume plugin
для подключения внешнего хранилища (NFS, Amazon EFS, GlusterFS и т.д.).
Допустим, существует плагин rexray/efs, позволяющий использовать Amazon EFS
как том:

docker plugin install rexray/efs

Далее плагин попросит подтверждения необходимых привилегий. После успешной


установки и включения плагина можно создать том с использованием драйвера от
этого плагина:

docker volume create \


--driver rexray/efs \
--name myefsvolume \
-o size=1G \
-o encrypt=true

Затем при запуске контейнеров можно указать этот том:

docker run -d --name app \


-v myefsvolume:/data \
nginx:alpine

В результате, все данные внутри контейнера по пути /data будут храниться во


внешнем хранилище Amazon EFS через плагин rexray/efs.

Создание собственного плагина

Если в официальном реестре или других источниках нет подходящего плагина,


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

Структура плагина

Плагин описывается файлом [Link], в котором указываются:

Interfaces: какие API реализует плагин (например, [Link]/1.0


для томов, [Link]/1.0 для авторизации и т.д.).
Entrypoint: исполняемый файл/процесс, который будет запущен внутри
контейнера плагина.
Args: аргументы запуска.
Env: переменные окружения.
WorkDir и rootfs: где располагается корневая файловая система.
Privileges: список привилегий, которые плагин запрашивает (монтирования,
доступ к сокетам, капабилити и т.д.).

3/6
Упрощённый пример [Link] для плагина:

{
"Description": "Example Volume Plugin",
"Documentation": "[Link]
"Interface": {
"Types": ["[Link]/1.0"],
"Socket": "[Link]"
},
"Entrypoint": ["/usr/bin/example-plugin"],
"WorkDir": "",
"Rootfs": "rootfs",
"Env": [
{
"Name": "LOG_LEVEL",
"Value": "info"
}
],
"Linux": {
"Capabilities": ["CAP_SYS_ADMIN"],
"AllowAllDevices": false,
"Devices": null
}
}

rootfs — это директория, содержащая всё необходимое для запуска плагина


(бинарники, библиотеки и т.д.). Её можно сформировать с помощью копирования
содержимого из собранного Docker-образа.

Создание плагина и загрузка его в реестр

1. Создайте Docker-контейнер, в котором установите и настройте всё нужное для


работы вашего будущего плагина (библиотеки, бинарники и т.д.).
2. Запустите этот контейнер и сохраните его в виде архива (docker export) или
используйте docker cp, чтобы извлечь нужные файлы в директорию rootfs.
3. Подготовьте файл [Link] с описанием плагина.
4. Перейдите в папку, содержащую rootfs и [Link], и запустите команду:

docker plugin create myrepo/myplugin:1.0 .

После успешного создания, плагин отобразится в списке:

docker plugin ls

Можно установить и включить плагин локально:

docker plugin enable myrepo/myplugin:1.0

Чтобы опубликовать плагин в реестре Docker Hub (или другом реестре), нужно
войти в учётную запись Docker (docker login) и выполнить:

docker plugin push myrepo/myplugin:1.0

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

Mounts: пути в хостовой файловой системе, доступные плагину в режиме


чтения/записи.
Capabilities: дополнительные привилегии в Linux (например, CAP_SYS_ADMIN,
CAP_NET_ADMIN и др.).
Devices: физические или виртуальные устройства (например, GPU-
устройства).
Network: возможность слушать на определённых портах, доступ к сетевым
интерфейсам.

Если плагин пытается запросить слишком широкие права, целесообразно понять,


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

Практические примеры

1. Использование плагина для логирования

Допустим, мы хотим отправлять логи всех контейнеров в внешний сервис через


кастомный драйвер логирования. Предположим, существует плагин myorg/log-plugin:

docker plugin install myorg/log-plugin


docker plugin enable myorg/log-plugin

Затем при запуске контейнера можно указать:

docker run -d --log-driver myorg/log-plugin --name app nginx

Теперь логи контейнера будут перенаправлены во внешний сервис, который


реализуется плагином myorg/log-plugin.

2. Плагин авторизации (AuthZ Plugin)

AuthZ Plugin позволяет реализовать собственные правила доступа для Docker.


Например, twistedogic/docker-authz-plugin может проверять, имеет ли пользователь
право запускать контейнер из определённого образа, или ограничивать список
возможных команд Docker.

docker plugin install twistedogic/docker-authz-plugin


docker plugin enable twistedogic/docker-authz-plugin

В /etc/docker/[Link] на хосте можно прописать:

5/6
{
"authorization-plugins": [
"twistedogic/docker-authz-plugin"
]
}

После перезапуска Docker (systemctl restart docker) каждый запрос к Docker


будет проходить через плагин авторизации. Это даёт гибкость в вопросах
безопасности и политик доступа.

Отладка и логирование плагинов

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

Просмотреть журнал Docker: journalctl -u docker или docker logs (если


сам плагин предоставляет логи через контейнерный вывод).
Проверить статус плагина: docker plugin ls покажет, запущен ли плагин,
отключён или в ошибке.
Проверить совместимость: некоторые плагины могут не работать с
определёнными версиями Docker или иметь зависимость от конкретной
платформы/операционной системы.
Обновить плагин: иногда просто установка более свежей версии плагина
решает проблему с багами или несовместимыми API.

Итоги

Плагины Docker — это контейнеризированные расширения, позволяющие


добавлять и настраивать функциональность Docker без прямого
вмешательства в код Docker Engine.
Через плагины можно интегрироваться с внешними системами хранения
(Volume Plugins), сетевыми решениями (Network Plugins), системами
логирования (Log Plugins), системами безопасности (Authorization Plugins) и
многими другими сервисами.
Управление жизненным циклом плагинов осуществляется стандартными
командами Docker (docker plugin install/enable/disable/remove и т.д.).
Каждый плагин имеет свой набор привилегий, которые администратор должен
тщательно проверять перед установкой и использованием.
При необходимости можно создать собственный плагин, описав его в файле
[Link] и предоставив корневую файловую систему rootfs.
Плагины повышают гибкость и расширяемость Docker, но требуют внимания к
вопросам безопасности и обновления.

Таким образом, Docker Plugins являются мощным механизмом для адаптации и


расширения Docker под конкретные нужды — от интеграции с корпоративными
хранилищами и сетями до настройки кастомной логики авторизации и логирования.

6/6

Вам также может понравиться