Полный курс по Docker: от новичка до профи

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

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

Введение в Docker: архитектура, установка и запуск первых контейнеров

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

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

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

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

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

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

Контейнеризация против Виртуализации

Чтобы понять гениальность Docker, нужно сравнить его с классическими виртуальными машинами (VM).

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

Виртуальная машина — это эмуляция целого компьютера. На физическом сервере работает гипервизор, который позволяет запускать несколько гостевых операционных систем (ОС). Каждая VM имеет свою полную копию ОС, выделенную оперативную память и дисковое пространство. Это надежно, но очень «тяжело». Если вам нужно запустить 5 микросервисов, вам придется запустить 5 операционных систем, каждая из которых потребляет ресурсы просто на свое существование.

Контейнеры

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

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

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

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

1. Docker Daemon (dockerd)

Это «мозг» системы. Фоновый процесс, работающий на хост-машине. Он управляет объектами Docker (образами, контейнерами, сетями, томами) и выполняет всю тяжелую работу по их созданию и запуску.

2. Docker Client (docker)

Это то, с чем вы взаимодействуете в терминале. Когда вы пишете команду docker run, клиент отправляет запрос демону через REST API. Клиент и демон могут работать как на одной системе, так и на разных.

3. Docker Registry (Реестр)

Это хранилище образов. Самый известный публичный реестр — Docker Hub. Когда вы просите Docker запустить программу, которой у вас нет локально, он ищет её именно там.

!Схема взаимодействия клиента, демона и реестра при выполнении команд.

Основные понятия: Образ и Контейнер

Новички часто путают эти два понятия. Давайте раз и навсегда проясним разницу, используя аналогию из объектно-ориентированного программирования.

Образ (Image): Это класс*. Это неизменяемый шаблон, содержащий исходный код, библиотеки, зависимости, инструменты и другие файлы, необходимые для приложения. Образ «заморожен» и доступен только для чтения. Контейнер (Container): Это экземпляр класса (объект)*. Это запущенный образ. Когда вы создаете контейнер, Docker добавляет к образу тонкий слой для записи, позволяя вносить изменения во время работы программы. Из одного образа можно запустить сколько угодно контейнеров.

> Контейнер — это процесс в операционной системе, который изолирован от других процессов и имеет собственное пространство имен файловой системы.

Установка Docker

Процесс установки зависит от вашей операционной системы. Я приведу краткий обзор, но всегда рекомендую обращаться к официальной документации Docker.

Windows и macOS

Для этих систем существует Docker Desktop. Это удобное приложение, которое устанавливает всё необходимое (Docker Engine, Docker CLI, Docker Compose) и предоставляет графический интерфейс.

  • Скачайте инсталлятор с официального сайта.
  • Запустите установку.
  • После установки запустите Docker Desktop и дождитесь, пока иконка кита в трее перестанет мигать.
  • Linux (Ubuntu/Debian)

    На Linux Docker работает нативно, поэтому производительность здесь максимальная. Установка обычно производится через терминал:

    После установки проверьте, что все работает, введя в терминале:

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

    Теперь самое интересное. Давайте перейдем от теории к практике. Откройте ваш терминал (или PowerShell/CMD в Windows).

    Hello World

    По традиции, начнем с простейшего. Введите команду:

    Что произошло?

  • Docker клиент связался с демоном.
  • Демон проверил, есть ли у вас локально образ hello-world.
  • Так как его не было, демон скачал (pull) его из Docker Hub.
  • Демон создал новый контейнер из этого образа.
  • Контейнер запустился, вывел приветственное сообщение в терминал и завершил работу.
  • Запуск веб-сервера Nginx

    hello-world сразу выключается. Давайте запустим что-то, что работает постоянно, например, веб-сервер Nginx.

    Введите команду:

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

    * docker run: команда создания и запуска контейнера. * -d (detach): запуск в фоновом режиме. Контейнер не захватит ваш терминал, а вернет вам длинный ID контейнера. * -p 8080:80 (publish): проброс портов. Это критически важно. Синтаксис: хост_порт:порт_контейнера. Мы говорим: «Перенаправь всё, что приходит на порт 8080 моего компьютера, на порт 80 внутри контейнера». * nginx: имя образа.

    Теперь откройте браузер и перейдите по адресу http://localhost:8080. Вы увидите приветственную страницу «Welcome to nginx!». Поздравляю, вы запустили веб-сервер в изолированном контейнере за одну секунду!

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

    Поскольку мы запустили контейнер в фоновом режиме, нам нужно уметь им управлять.

    1. Посмотреть запущенные контейнеры:

    Вы увидите таблицу с ID контейнера, образом, статусом и портами.

    2. Остановить контейнер:

    Возьмите CONTAINER ID (достаточно первых 3-4 символов) из предыдущей команды и выполните:

    3. Удалить контейнер:

    Остановленный контейнер все еще занимает место на диске. Чтобы удалить его:

    > Важно: Команда docker rm удаляет контейнер, а docker rmi удаляет образ.

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

    Подводя итог, типичный жизненный цикл выглядит так:

  • Pull: Скачивание образа из реестра (docker pull или автоматически при run).
  • Create: Создание контейнера из образа.
  • Start: Запуск процесса внутри контейнера.
  • Stop: Остановка процесса (контейнер сохраняется).
  • Remove: Удаление слоя контейнера (образ остается).
  • В следующей статье мы углубимся в работу с образами, научимся создавать свои собственные Dockerfile и упаковывать ваши личные приложения. А пока — выполните домашнее задание, чтобы закрепить материал.

    2. Магия образов: написание Dockerfile, слои и оптимизация сборки приложений

    Магия образов: написание Dockerfile, слои и оптимизация сборки приложений

    В предыдущей статье мы научились запускать готовые контейнеры. Мы взяли hello-world и nginx из Docker Hub, словно купили готовую мебель в магазине. Но настоящая сила Docker раскрывается тогда, когда вы начинаете создавать собственные образы.

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

    Что такое Dockerfile?

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

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

    Анатомия Dockerfile

    Давайте рассмотрим основные команды, из которых состоит этот файл. Почти любой Dockerfile начинается с базы и заканчивается командой запуска.

  • FROM: Фундамент. Указывает базовый образ, на основе которого мы строим свой. Например, FROM python:3.9 означает, что мы берем Linux с уже установленным Python версии 3.9.
  • WORKDIR: Рабочая папка. Создает директорию внутри образа и делает её текущей. Все последующие команды будут выполняться в ней. Это аналог команды cd.
  • COPY: Загрузка файлов. Копирует файлы с вашего компьютера (хоста) внутрь образа.
  • RUN: Установка зависимостей. Выполняет команду во время сборки образа. Обычно используется для установки библиотек (например, pip install или apt-get install).
  • CMD: Старт. Указывает команду, которая выполнится при запуске контейнера. В Dockerfile может быть только одна инструкция CMD.
  • !Процесс сборки: текстовый файл превращается в бинарный образ.

    Практика: Собираем свой первый образ

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

    Шаг 1. Подготовка файлов

    Создайте папку my-docker-app и внутри неё создайте файл app.py:

    Шаг 2. Создание Dockerfile

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

    Шаг 3. Сборка образа

    Теперь самое интересное. Откройте терминал в этой папке и введите команду:

    Разберем команду: * docker build: команда сборки. -t my-python-app: флаг tag* (тег), дающий имя нашему образу. . (точка): указывает на контекст сборки* (текущую папку), где Docker должен искать Dockerfile и файлы для копирования.

    Шаг 4. Запуск

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

    Вы увидите вывод: Привет! Я работаю внутри контейнера Docker!.

    Погружение в слои (Layers)

    Чтобы писать эффективные Dockerfile, нужно понимать, как Docker хранит данные. Образ Docker — это не один монолитный файл, а «слоеный пирог».

    Каждая инструкция в Dockerfile (FROM, COPY, RUN) создает новый слой.

    * Слои доступны только для чтения (Read-Only). * Каждый следующий слой накладывается поверх предыдущего, добавляя изменения. * Когда вы запускаете контейнер, Docker добавляет сверху еще один тонкий слой — контейнерный слой, доступный для записи (Read-Write).

    !Структура файловой системы Docker: неизменяемые слои образа и записываемый слой контейнера.

    Union File System (UnionFS)

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

    Почему это важно? Из-за кэширования.

    Оптимизация сборки и кэширование

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

    Однако, как только один слой меняется, все последующие слои пересобираются заново.

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

    Рассмотрим типичную ошибку новичка. Допустим, у нас есть веб-приложение на Python (Flask), которому нужен файл зависимостей requirements.txt.

    Плохой Dockerfile:

    Почему это плохо? Каждый раз, когда вы меняете хоть одну строчку в коде вашего приложения (app.py), слой COPY . . меняется. Следовательно, кэш сбрасывается, и Docker вынужден заново выполнять RUN pip install ..., скачивая все библиотеки из интернета. Это долго.

    Хороший Dockerfile:

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

    Уменьшение размера образа

    Размер имеет значение. Большие образы дольше скачиваются, занимают больше места на диске и дольше запускаются.

    1. Используйте легкие базовые образы

    Вместо стандартного образа (например, node или python), который основан на полноценном Debian/Ubuntu, используйте версии Alpine Linux. Alpine — это сверхлегкий дистрибутив Linux (около 5 МБ).

    * FROM python:3.9 ~ 900 МБ * FROM python:3.9-slim ~ 120 МБ * FROM python:3.9-alpine ~ 50 МБ

    > Примечание: Alpine использует другую стандартную библиотеку C (musl вместо glibc), что иногда может вызывать проблемы совместимости с некоторыми пакетами. Версия slim (на базе Debian) часто является золотой серединой.

    2. Объединение команд (Chaining)

    Помните: каждая инструкция RUN создает новый слой. Если вы напишете:

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

    Правильный подход — объединять команды через &&:

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

    3. Файл .dockerignore

    Этот файл работает так же, как .gitignore. Он указывает Docker, какие файлы не нужно копировать в контекст сборки.

    Обязательно добавляйте туда: * .git (история версий весит много) * venv или node_modules (локальные библиотеки не нужны, мы ставим их внутри) * Временные файлы и секреты.

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

    Это «киллер-фича» для компилируемых языков (Go, Java, C++) или фронтенд-сборки (React, Vue).

    Представьте, что вы собираете приложение на Go. Вам нужен компилятор Go, который весит сотни мегабайт. Но для запуска готового бинарного файла компилятор уже не нужен.

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

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

    В итоге ваш образ будет весить 10 МБ (размер Alpine + бинарник), а не 800 МБ (размер образа Golang).

    Заключение

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

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

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

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

    Добро пожаловать обратно! В предыдущих статьях мы научились запускать контейнеры и создавать собственные образы с помощью Dockerfile. Мы разобрали, что контейнеры — это эфемерные (временные) сущности. И здесь возникает главный вопрос, с которым сталкивается каждый новичок: «Куда деваются мои данные, когда я удаляю контейнер?».

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

    В этой статье мы решим эту проблему раз и навсегда, изучив Volumes (Тома). А затем мы научим контейнеры общаться друг с другом, создав изолированные виртуальные сети (Networks).

    Проблема хранения данных

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

    Главное правило: Жизненный цикл данных внутри контейнера жестко связан с жизненным циклом самого контейнера. Если вы удалите контейнер командой docker rm, слой записи удалится вместе с ним.

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

  • Volumes (Тома)
  • Bind Mounts (Привязка папок)
  • !Визуализация того, как данные выносятся за пределы жизненного цикла контейнера.

    1. Docker Volumes (Тома)

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

    Преимущества: * Легко делать резервные копии. * Безопасно (другие процессы не повредят данные). * Работают одинаково на Linux, Windows и macOS.

    #### Практика: Создание и использование Volume

    Давайте создадим хранилище для наших данных:

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

    Разберем флаг -v: * Синтаксис: -v источник:цель * Источник: my-db-data (имя нашего тома). * Цель: /var/lib/postgresql/data (путь внутри контейнера).

    Теперь, если вы остановите и удалите контейнер postgres-db, а затем запустите новый с тем же томом my-db-data, все ваши таблицы и записи будут на месте. Это похоже на подключение внешней USB-флешки: компьютер можно поменять, а флешку с данными вставить в новый.

    2. Bind Mounts (Привязка папок)

    Bind Mount — это прямое отображение папки с вашего компьютера (хоста) в контейнер. В отличие от Volumes, вы точно знаете, где лежат файлы, и можете их редактировать любым редактором кода.

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

    #### Практика: Запуск веб-сервера с вашим кодом

    Создайте на рабочем столе папку my-site и внутри файл index.html с текстом <h1>Привет из Docker!</h1>.

    Теперь запустим веб-сервер Nginx, подменив его стандартную папку на нашу:

    Если вы на Windows (PowerShell), используйте {PWD}/my-site:/usr/share/nginx/html \ nginx bash docker network ls bash docker network create my-app-net bash docker run -d \ --name mysql-server \ --network my-app-net \ -e MYSQL_ROOT_PASSWORD=secret \ mysql:5.7 bash docker run -it --rm \ --network my-app-net \ alpine sh bash ping mysql-server `

    Вы увидите ответ: 64 bytes from mysql-server (172.18.0.2)....

    Это победа! Docker автоматически понял, что под именем mysql-server скрывается IP-адрес 172.18.0.2. Вашему приложению больше не нужно знать IP-адреса, ему достаточно знать имя хоста.

    Изоляция сетей

    Еще одно важное преимущество пользовательских сетей — изоляция.

    Представьте, что у вас на сервере крутится два разных проекта: Интернет-магазин и Блог. * Создайте сеть shop-net для магазина. * Создайте сеть blog-net` для блога.

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

    Резюме

    Сегодня мы сделали огромный шаг вперед. Теперь наши контейнеры:

  • Не теряют данные благодаря Volumes.
  • Удобны в разработке благодаря Bind Mounts.
  • Видят друг друга по именам благодаря Networks.
  • В следующей статье мы соберем все эти знания воедино и научимся описывать сложные системы из множества контейнеров в одном файле. Готовьтесь, мы переходим к Docker Compose — инструменту, без которого не живет ни один современный разработчик.

    4. Docker Compose: оркестрация многоконтейнерных приложений и микросервисов

    Docker Compose: оркестрация многоконтейнерных приложений и микросервисов

    Приветствую вас на четвертом этапе нашего курса «Полный курс по Docker: от новичка до профи».

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

    Но давайте будем честны: запускать сложные системы вручную — это кошмар. Представьте, что ваше приложение состоит из веб-сервера (Nginx), бэкенда (Python/Node.js), базы данных (PostgreSQL), кэша (Redis) и очереди сообщений (RabbitMQ).

    Чтобы поднять этот стек, вам придется ввести пять длинных команд docker run, не забыв правильно указать все флаги, порты, переменные окружения и сети. А если нужно перезапустить всё это? Или передать проект коллеге?

    Здесь на сцену выходит Docker Compose. Это инструмент, который превращает хаос ручных команд в стройную симфонию.

    Что такое Docker Compose?

    Docker Compose — это инструмент для определения и запуска многоконтейнерных приложений Docker.

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

    Основная идея заключается в декларативном подходе. Вместо того чтобы писать команды (сделай то, потом сделай это), вы описываете желаемое состояние системы в одном файле конфигурации. Вы говорите Docker: «Я хочу, чтобы у меня было два сервиса: веб и база данных, и они должны видеть друг друга». Docker Compose берет на себя всю грязную работу по созданию сетей, томов и запуску контейнеров.

    !Сравнение ручного запуска множества контейнеров и использования Docker Compose.

    Формат YAML: язык Docker Compose

    Конфигурация Docker Compose пишется на языке YAML (YAML Ain't Markup Language). Это человекочитаемый формат данных, который использует отступы для обозначения вложенности.

    > Важно: В YAML отступы имеют критическое значение. Нельзя использовать табуляцию, только пробелы (обычно 2 или 4 пробела на уровень).

    Файл конфигурации по умолчанию называется docker-compose.yml. Давайте разберем его анатомию на примере реального приложения.

    Практика: Создаем приложение с счетчиком посещений

    Чтобы понять мощь Compose, мы создадим классический пример: веб-приложение на Python (Flask), которое хранит количество посещений в базе данных Redis.

    Шаг 1: Подготовка кода приложения

    Создайте новую папку my-compose-app. Внутри создайте файл app.py:

    Теперь создайте файл зависимостей requirements.txt:

    Шаг 2: Создание Dockerfile

    Нам нужно упаковать наше Python-приложение. Создайте Dockerfile:

    Шаг 3: Магия Docker Compose

    А теперь самое главное. Вместо того чтобы собирать образ и запускать Redis вручную, мы опишем всё в файле docker-compose.yml. Создайте его в той же папке:

    Давайте детально разберем каждую строчку этого файла. Это фундамент ваших знаний.

    #### 1. Version version: "3.9" указывает версию формата файла Compose. Это важно для совместимости с версией Docker Engine.

    #### 2. Services (Сервисы) Раздел services — это сердце конфигурации. Здесь мы перечисляем контейнеры, которые хотим запустить. В нашем примере их два: web и redis.

    Сервис web: * build: .: Говорит Docker: «Не ищи готовый образ, а собери его из Dockerfile в текущей папке (.)». * ports: Список проброса портов. "5000:5000" означает, что порт 5000 на вашем компьютере будет вести на порт 5000 внутри контейнера. * volumes: Мы используем Bind Mount (о котором говорили в прошлой статье). .:/code связывает текущую папку проекта с папкой /code внутри контейнера. Это позволит нам менять код на лету без перезапуска. * environment: Установка переменных окружения прямо в манифесте. * depends_on: Указывает порядок запуска. Docker запустит redis перед запуском web.

    Сервис redis: * image: "redis:alpine": Здесь мы не собираем образ, а берем готовый из Docker Hub.

    Запуск и управление

    Теперь, когда файл готов, откройте терминал в папке проекта.

    Запуск (Up)

    Введите команду:

    Что произойдет?

  • Docker Compose создаст сеть по умолчанию (обычно my-compose-app_default).
  • Соберет образ для сервиса web.
  • Скачает образ для сервиса redis.
  • Запустит оба контейнера в правильном порядке.
  • Подключит их к одной сети.
  • Вы увидите логи обоих сервисов в одном терминале. Откройте браузер по адресу http://localhost:5000. Вы увидите сообщение: «Привет! Я был просмотрен 1 раз». Обновите страницу — счетчик увеличится.

    Фоновый режим и другие команды

    Чтобы запустить сервисы в фоне (как docker run -d), используйте флаг -d:

    Полезные команды:

    * docker-compose ps: Показать статус контейнеров, запущенных через Compose. * docker-compose logs -f: Просмотр логов (флаг -f означает follow, следить в реальном времени). * docker-compose stop: Остановить контейнеры, но не удалять их. * docker-compose down: Полная остановка и очистка. Эта команда останавливает контейнеры, удаляет их и удаляет созданную сеть.

    > Примечание: В новых версиях Docker команда пишется как docker compose (через пробел, без дефиса), так как Compose V2 стал частью основного CLI. Но старый синтаксис docker-compose все еще широко используется.

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

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

    Посмотрите на наш код Python: cache = redis.Redis(host='redis', port=6379)

    Мы указали хост redis. Почему это работает? Потому что в docker-compose.yml мы назвали сервис redis. Docker Compose создал DNS-запись внутри сети контейнеров, которая сопоставляет имя сервиса redis с его внутренним IP-адресом.

    Если бы вы назвали сервис в YAML файле db, то в Python коде нужно было бы писать host='db'.

    !Иллюстрация автоматического создания сети и разрешения имен сервисов.

    Работа с томами (Volumes) в Compose

    Мы уже использовали Bind Mount для кода. Но что насчет базы данных? Если мы удалим контейнер Redis, данные счетчика пропадут. Нам нужен именованный том (Named Volume).

    Давайте модифицируем наш docker-compose.yml:

    Обратите внимание на два изменения:

  • В сервисе redis мы добавили volumes: - redis-data:/data. Это говорит подключить том redis-data к папке /data внутри контейнера (где Redis хранит данные).
  • В самом низу файла мы добавили раздел верхнего уровня volumes, где объявили redis-data. Это аналог команды docker volume create.
  • Теперь, даже если вы выполните docker-compose down, том redis-data останется на диске. При следующем запуске docker-compose up Redis подхватит старые данные.

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

    Хорошей практикой является вынос настроек (пароли, порты, ключи API) из кода и даже из самого docker-compose.yml в отдельный файл.

    Docker Compose автоматически ищет файл .env в папке проекта.

    Создайте файл .env:

    Теперь можно использовать эти переменные в docker-compose.yml:

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

    Резюме

    Docker Compose — это инструмент, который переводит вас из разряда «новичок, запускающий контейнеры руками» в разряд «инженер, проектирующий системы».

    Мы изучили:

  • Декларативный подход: Описание системы в YAML-файле.
  • Сервисы: Как определять и связывать контейнеры.
  • Автоматическую сеть: Как сервисы видят друг друга по именам.
  • Персистентность: Как объявлять тома в Compose.
  • Теперь вы умеете оркестрировать локальную разработку. Но что делать, если ваше приложение настолько популярно, что один сервер не справляется? В следующих статьях мы поговорим о том, как оптимизировать наши образы для продакшена и коснемся темы масштабирования.

    5. Docker в продакшене: безопасность, реестры образов и лучшие практики эксплуатации

    Docker в продакшене: безопасность, реестры образов и лучшие практики эксплуатации

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

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

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

    Оптимизация образов: меньше — значит лучше

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

    1. Выбор правильного базового образа

    Мы уже упоминали Alpine Linux. В продакшене использование минималистичных образов становится стандартом. Сравните:

    * FROM python:3.9 (основан на Debian) — весит около 900 МБ. * FROM python:3.9-slim (урезанный Debian) — весит около 120 МБ. * FROM python:3.9-alpine — весит около 50 МБ.

    Используя Alpine или Slim версии, вы не тащите в продакшен лишние утилиты, которые могут быть использованы хакерами.

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

    Это самая мощная техника для компилируемых языков (Go, Java, C++) и фронтенда (Node.js/React). Идея проста: в одном образе мы собираем приложение (нужны компиляторы, исходники, библиотеки), а в финальный образ копируем только готовый бинарный файл или статику.

    Пример для Node.js приложения:

    В итоге в продакшен улетает легкий Nginx с HTML-файлами, а тяжелая папка node_modules остается в истории сборки и не попадает в финальный образ.

    !Визуализация процесса Multi-stage build, где из тяжелого образа извлекается только результат сборки.

    3. Файл .dockerignore

    Никогда не забывайте про .dockerignore. Если вы случайно скопируете папку .git, локальные конфиги или временные файлы, вы не только увеличите размер образа, но и можете слить секретные данные.

    Безопасность: не доверяйте никому

    Контейнеры изолированы, но эта изоляция не абсолютна. Если злоумышленник взломает приложение внутри контейнера, он попытается выбраться наружу (на хост-систему).

    1. Не запускайте от имени root

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

    Решение: Всегда создавайте пользователя и переключайтесь на него.

    2. Управление секретами

    Никогда, запомните, никогда не пишите пароли, API-ключи и токены прямо в Dockerfile. Любой, кто получит доступ к образу, сможет прочитать их командой docker history.

    Плохо:

    Хорошо: Передавайте секреты как переменные окружения при запуске контейнера, но не «запекайте» их в образ. В чистом Docker это делается через файл .env (добавленный в .gitignore), а в оркестраторах (Kubernetes, Docker Swarm) есть специальные механизмы Secrets.

    3. Сканирование образов

    В библиотеках, которые вы используете, постоянно находят новые уязвимости. Используйте инструменты для сканирования образов, такие как Trivy или Docker Scan.

    Пример запуска сканирования:

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

    Реестры образов (Docker Registries)

    Когда образ готов, его нужно где-то хранить, чтобы сервер мог его скачать. Это место называется Registry (Реестр).

    Публичные vs Приватные

    * Docker Hub: Самый популярный публичный реестр. Отлично подходит для open-source проектов. * Приватные реестры: Для коммерческой разработки код нельзя выкладывать публично. Компании используют приватные репозитории: GitLab Container Registry, AWS ECR, Google GCR или поднимают свой собственный реестр (например, Harbor).

    Тегирование: проклятие latest

    Новички любят использовать тег latest. В продакшене это антипаттерн.

    Представьте: у вас в docker-compose.yml написано image: my-app:latest. Вы выпустили обновление, запушили новый latest. Сервер перезагрузился, скачал новый образ... и всё сломалось. Как откатиться назад? Предыдущий latest перезаписан.

    Лучшая практика: Используйте семантическое версионирование или хеш коммита.

    * my-app:v1.0.0 — стабильная версия. * my-app:v1.0.1 — исправление бага. * my-app:git-a1b2c3d — версия, привязанная к конкретному коду.

    Так вы всегда точно знаете, что именно запущено на сервере, и можете легко откатиться на v1.0.0, если v1.0.1 окажется с ошибкой.

    Эксплуатация: чтобы всё работало как часы

    Запустить контейнер — полдела. Нужно, чтобы он жил долго и счастливо.

    1. Healthchecks (Проверка здоровья)

    Docker знает, запущен ли процесс (PID), но он не знает, работает ли приложение. Если ваш веб-сервер завис и выдает ошибку 500, процесс всё еще жив, и Docker думает, что всё в порядке.

    Инструкция HEALTHCHECK в Dockerfile позволяет научить Docker проверять состояние сервиса.

    Теперь Docker будет каждые 30 секунд делать запрос. Если сервер не ответит, контейнер получит статус unhealthy, и система оркестрации сможет его перезапустить.

    2. Ограничение ресурсов (Limits)

    Один контейнер с утечкой памяти может «положить» весь сервер, съев всю оперативную память (RAM). В продакшене обязательно нужно ставить лимиты.

    В docker-compose.yml это выглядит так:

    3. Логирование

    В классическом администрировании приложения пишут логи в файлы. В Docker принят подход «Logs as Event Stream».

    Приложение должно писать логи в стандартный вывод (stdout) и стандартный поток ошибок (stderr). Docker сам перехватывает эти потоки. Это позволяет централизованно собирать логи со всех контейнеров (например, в ELK Stack или Graylog) без необходимости настраивать каждый контейнер отдельно.

    > Совет: Не используйте print в Python или console.log в Node.js для отладки в продакшене бездумно. Используйте библиотеки логирования, которые умеют форматировать вывод в JSON. Это упростит автоматический анализ логов.

    CI/CD Pipeline: Автоматизация пути в продакшен

    В современной разработке никто не собирает образы руками на сервере. Весь процесс автоматизирован через CI/CD (Continuous Integration / Continuous Delivery).

    Типичный конвейер выглядит так:

  • Code: Разработчик делает git push.
  • Test: CI-система (GitHub Actions, GitLab CI) запускает тесты.
  • Build: Если тесты прошли, собирается Docker-образ.
  • Scan: Образ сканируется на уязвимости.
  • Push: Образ загружается в Registry с уникальным тегом (например, v1.2.3).
  • Deploy: CI-система дает команду серверу обновить версию приложения в docker-compose.yml на v1.2.3.
  • !Схема автоматизированного процесса доставки кода в продакшен с использованием Docker.

    Заключение

    Docker в продакшене — это не просто docker run. Это культура безопасности, оптимизации и наблюдаемости.

    Сегодня мы узнали: * Как уменьшить размер образа с помощью Multi-stage builds. * Почему нельзя работать под root и хранить пароли в Dockerfile. * Зачем нужны теги версий вместо latest. * Как настроить Healthchecks и лимиты ресурсов.

    Теперь вы обладаете знаниями, которые отличают новичка от профессионала. Ваш контейнер не просто работает — он работает безопасно, стабильно и эффективно.

    На этом наш базовый курс завершен, но мир контейнеризации огромен. Дальше вас ждут Kubernetes, Service Mesh и облачные технологии. Удачи в плавании, капитан!