Управление данными: Volumes и Bind Mounts

Курс посвящен механизмам персистентности в Docker. Вы научитесь разделять жизненный цикл данных и контейнера, выбирать между Volumes и Bind Mounts и обеспечивать сохранность состояния приложений.

Проблема эфемерности: Почему данные исчезают вместе с контейнером

Проблема эфемерности: Почему данные исчезают вместе с контейнером

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

Чтобы понять, куда испарились данные, нам нужно разобраться с фундаментальным свойством Docker-контейнеров — их эфемерностью.

Иллюзия виртуальной машины

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

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

Контейнеры не предназначены для того, чтобы их «чинили» или «обновляли» изнутри. Если нужна новая версия библиотеки или конфигурации, мы не заходим в запущенный контейнер через терминал, а пересобираем образ и запускаем новый контейнер. Но как эта философия влияет на файлы, которые приложение создает во время работы?

Анатомия файловой системы: Слой контейнера

Вспомним, как устроен образ: это набор неизменяемых (Read-Only) слоев. Когда вы выполняете команду docker run, демон Docker берет эти слои и добавляет поверх них еще один, финальный слой — слой контейнера (Container Layer).

В отличие от слоев образа, слой контейнера доступен для записи (Writable Layer).

Именно здесь происходит вся магия файловых операций во время работы приложения:

  1. Создание: Если ваше приложение пишет лог-файл или сохраняет загруженную пользователем картинку, этот файл физически создается в Writable-слое.
  2. Изменение: Благодаря механизму Copy-on-Write (CoW), если приложение решает изменить конфигурационный файл, изначально лежащий в базовом образе, Docker копирует этот файл из нижнего Read-Only слоя наверх, в Writable-слой, и уже там изменяет его.
  3. Удаление: Если приложение удаляет файл, Docker не может удалить его из неизменяемого образа. Вместо этого в Writable-слое создается специальная пометка (whiteout), которая скрывает файл от приложения.

Слой контейнера (Container Layer)

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

Жизненный цикл данных

Ключевая проблема кроется в том, что слой контейнера жестко привязан к жизненному циклу самого контейнера.

Если вы остановите контейнер командой docker stop, слой контейнера сохранится на диске хост-машины. Вы можете запустить его снова (docker start), и приложение увидит все созданные ранее файлы.

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

Баг или фича?

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

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

Чтобы система работала надежно, инженерам пришлось разделить приложения на два архитектурных типа:

  1. Stateless (без состояния): Приложения, которые не сохраняют данные между сессиями. Например, веб-сервер Nginx или микросервис на Python, который просто принимает запрос, вычисляет результат и отдает ответ. Такие приложения идеально ложатся на философию Docker. Их можно убивать и запускать тысячами, они полностью эфемерны.
  2. Stateful (с состоянием): Приложения, чья главная задача — хранить и накапливать данные. Базы данных (PostgreSQL, MongoDB), брокеры сообщений (Kafka), хранилища логов.

Правило профессионального управления инфраструктурой гласит: контейнер должен быть stateless, даже если внутри работает stateful-приложение.

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

Bind Mounts: Прямая связь контейнера с файловой системой хоста

Bind Mounts: Прямая связь контейнера с файловой системой хоста

Представьте типичный рабочий процесс: вы пишете код приложения на Python или Node.js. Чтобы запустить его в изолированной среде, вы собираете Docker-образ и запускаете контейнер. Но что делать, если вы нашли опечатку в коде? Исправлять файл на хосте, заново запускать docker build, ждать пересборки слоев, убивать старый контейнер и запускать новый ради одной строчки кода?

Этот цикл убивает продуктивность. Нам нужен способ пробить изоляцию контейнера и сказать ему: «Не читай файлы из своего образа, читай их напрямую из этой папки на моем ноутбуке». Именно эту задачу решает механизм Bind Mounts.

Пробивая эфемерность

В прошлой главе мы выяснили, что все изменения внутри контейнера оседают в его эфемерном Writable-слое и исчезают при удалении.

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

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

Это создает двустороннюю связь в реальном времени:

  1. Вы сохраняете файл в IDE на ноутбуке — процесс в контейнере мгновенно видит изменения.
  2. Скрипт в контейнере генерирует лог-файл или скачивает картинку — этот файл тут же появляется в папке на вашем ноутбуке.

Синтаксис: как настроить мост

Для создания Bind Mount при запуске контейнера используется флаг -v (от слова volume) или более современный и читаемый --mount.

Рассмотрим классический синтаксис с флагом -v. Он состоит из трех частей, разделенных двоеточием: путь_на_хосте:путь_в_контейнере:права_доступа.

docker run -d \
  -p 8080:80 \
  -v /home/user/projects/webapp/src:/app/src:ro \
  nginx:latest

Разберем параметры монтирования:

  • /home/user/projects/webapp/src — что мы отдаем. Жесткое правило: для Bind Mount путь на хосте всегда должен быть абсолютным.
  • /app/src — куда мы это кладем внутри контейнера.
  • ro (read-only) — опциональный флаг безопасности. Он запрещает контейнеру изменять файлы на вашем хосте. Если его не указать, по умолчанию используется rw (read-write).

Писать длинные абсолютные пути вручную неудобно, поэтому в терминале часто используют подстановку текущей директории через $(pwd) (Print Working Directory):

docker run -d -v $(pwd)/src:/app/src nginx:latest

Эффект маскирования (Masking)

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

Допустим, в вашем Dockerfile есть инструкция COPY . /app, которая копирует 100 файлов исходного кода внутрь образа. Вы запускаете контейнер и монтируете пустую папку с хоста поверх директории /app в контейнере. Что произойдет?

Файлы на хосте и в контейнере не объединяются. Директория хоста полностью перекрывает (маскирует) содержимое директории контейнера.

Если вы примонтируете пустую папку с хоста в /app, то процесс внутри контейнера увидит абсолютно пустую директорию /app. Исходные 100 файлов из образа никуда не удалились — они всё ещё лежат в слоях образа (Read-Only Layer), но они становятся недоступны до тех пор, пока монтирование активно. Как только вы остановите и удалите этот контейнер, а затем запустите новый без флага -v, файлы из образа снова станут видны.

Ловушка прав доступа (Permissions)

Поскольку Bind Mount напрямую связывает хост и контейнер, они начинают делить одни и те же файлы. И здесь возникает проблема с правами доступа (User ID / Group ID).

Операционная система Linux ничего не знает об именах пользователей, она оперирует числами — UID. На вашем ноутбуке ваш пользователь, скорее всего, имеет UID 1000. По умолчанию главный процесс в Docker-контейнере запускается от имени пользователя root, который имеет UID 0.

Если контейнер работает от root и создает файл в примонтированной директории, этот файл на вашем ноутбуке тоже будет принадлежать root. Вы не сможете отредактировать или удалить его в своей IDE без использования sudo.

И наоборот: если в Dockerfile использована инструкция USER node (например, UID 1001), а вы монтируете папку, принадлежащую вашему пользователю (UID 1000), процесс node внутри контейнера получит ошибку Permission Denied при попытке записать туда данные, так как UID не совпадают.

Когда использовать Bind Mounts

Bind Mounts — это мощный инструмент, но его применение в профессиональной среде строго ограничено конкретными сценариями:

  1. Локальная разработка (Hot Reload). Вы пробрасываете папку с исходным кодом в контейнер. Фреймворк (например, React, Django или Spring Boot) замечает изменение файлов и автоматически перезагружает приложение. Вам не нужно пересобирать образ при каждом сохранении файла.
  2. Проброс конфигурационных файлов. Если у вас есть готовый образ Nginx или Prometheus, вам не обязательно собирать свой кастомный образ только ради того, чтобы подложить туда свой nginx.conf. Вы можете просто примонтировать один конфигурационный файл с хоста прямо поверх дефолтного файла в контейнере: -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro.

Почему Bind Mounts не подходят для баз данных в Production? Bind Mount жестко привязывает контейнер к конкретной структуре папок на конкретном сервере. Если вы укажете -v /home/admin/db_data:/var/lib/postgresql/data, ваш контейнер не сможет запуститься на другом сервере, где нет папки /home/admin/db_data. Кроме того, вам придется вручную решать проблемы с правами доступа (UID/GID) на каждом новом сервере.

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

Docker Volumes: Управляемое хранилище под контролем демона

Docker Volumes: Управляемое хранилище под контролем демона

Попытка запустить базу данных PostgreSQL с использованием Bind Mount часто заканчивается катастрофой в первые же секунды. Вы пробрасываете локальную папку хоста в контейнер, запускаете команду, и контейнер мгновенно падает с ошибкой: chown: changing ownership of '/var/lib/postgresql/data': Permission denied.

Причина кроется в архитектуре Bind Mounts: локальная папка принадлежит вашему пользователю (или root), а процесс СУБД внутри контейнера работает от имени встроенного пользователя postgres (например, с UID 999). У него просто нет прав на запись в вашу директорию. Когда речь заходит о production-ready решениях и stateful-приложениях, прямая связь с файловой системой хоста становится обузой. Здесь на сцену выходят Docker Volumes.

Суть управляемого хранилища

Именованный том (Named Volume) — это механизм хранения данных, который полностью создается, управляется и защищается самим демоном Docker.

В отличие от Bind Mount, где вы сами указываете любой абсолютный путь на жестком диске, при использовании Volumes вы делегируете выбор места Docker-демону. На Linux-системах Docker создает для томов специальную защищенную зону, обычно по пути /var/lib/docker/volumes/.

Том — это «песочница» для данных. Никакие другие процессы хост-системы, кроме самого Docker, не должны напрямую читать или изменять файлы в этой директории.

Такая изоляция решает сразу несколько проблем:

  1. Отсутствие конфликтов прав доступа. Docker сам назначает нужные права на директорию тома в момент его монтирования к контейнеру. База данных сможет писать туда без ошибок Permission denied.
  2. Кроссплатформенность. Пути в macOS, Windows и Linux различаются. Bind Mount с путем C:\data сломает запуск на Linux. Имя тома my_data будет работать везде одинаково.
  3. Безопасность. Случайный скрипт на хост-машине не удалит файлы базы данных, так как они скрыты глубоко в системной директории Docker.

Жизненный цикл тома: CLI команды

Тома — это самостоятельные объекты в экосистеме Docker, такие же, как образы или контейнеры. Они имеют собственный жизненный цикл и могут существовать даже тогда, когда ни один контейнер не запущен.

Управление томами происходит через группу команд docker volume:

  • Создание: docker volume create pgdata
  • Просмотр списка: docker volume ls
  • Удаление: docker volume rm pgdata (не сработает, если том примонтирован хотя бы к одному контейнеру, даже остановленному).

Чтобы узнать, где физически на хосте лежат данные тома, используется команда инспекции:

docker volume inspect pgdata

В выводе JSON нас интересует поле Mountpoint — это и есть реальный путь к данным на диске хост-машины.

Синтаксис монтирования

Подключение тома к контейнеру выполняется через уже знакомый флаг -v (или --volume). Главное отличие от Bind Mount заключается в левой части выражения.

Сравните:

  • Bind Mount: docker run -v /opt/data:/var/lib/mysql mysql
  • Volume: docker run -v db_data:/var/lib/mysql mysql

Правило парсера Docker: если строка до двоеточия начинается со слеша / (или буквы диска в Windows) — это абсолютный путь, и Docker создает Bind Mount. Если строка начинается с обычного слова — это имя тома, и Docker ищет (или автоматически создает) Volume с таким именем.

Магия Copy-up: Предварительное заполнение

Самое мощное архитектурное отличие томов от Bind Mounts проявляется в момент первого монтирования.

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

Именованные тома ведут себя иначе. Они используют механизм Copy-up (предварительное заполнение).

Если вы монтируете пустой (только что созданный) том в директорию контейнера, в которой уже есть файлы (заложенные туда на этапе сборки образа инструкциями COPY или RUN), Docker сначала аккуратно скопирует содержимое этой директории из контейнера внутрь тома, и только потом выполнит монтирование.

Это означает, что вы можете создать пустой том nginx_config, примонтировать его к /etc/nginx, и контейнер успешно запустится. Том автоматически наполнится оригинальными конфигурационными файлами из образа, после чего вы сможете их безопасно редактировать, не теряя при перезапусках.

Практика: Запуск PostgreSQL

Соберем все вместе и запустим stateful-приложение (базу данных) по всем правилам.

  1. Создаем управляемое хранилище:
docker volume create pg_storage
  1. Запускаем контейнер, монтируя созданный том в стандартную директорию PostgreSQL, где она хранит файлы данных:
docker run -d \
  --name my_db \
  -e POSTGRES_PASSWORD=supersecret \
  -v pg_storage:/var/lib/postgresql/data \
  postgres:15

Что произошло под капотом?

  • Демон Docker нашел том pg_storage.
  • При запуске контейнера процесс postgres (PID 1) инициировал создание структуры базы данных в /var/lib/postgresql/data.
  • Поскольку эта директория связана с томом, все файлы физически записались в защищенную зону демона на хосте.
  • Права доступа были урегулированы демоном автоматически — процесс СУБД получил полный доступ к записи.

Теперь, если мы удалим контейнер (docker rm -f my_db) и создадим новый, указав тот же том pg_storage, база данных мгновенно подхватит старое состояние. Данные пережили смерть контейнера, оставшись в безопасности под управлением Docker.

Анонимные тома: Скрытая логика сохранения данных в Dockerfile

Анонимные тома: Скрытая логика сохранения данных в Dockerfile

Вы активно работаете с Docker, регулярно удаляете старые контейнеры командой docker rm -f, и уверены, что система поддерживается в чистоте. Но однажды на сервере заканчивается место. Вы вводите команду docker system df и с удивлением обнаруживаете, что десятки гигабайт заняты неизвестными томами, которые вы никогда не создавали.

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

Что такое анонимный том

В прошлой главе мы разобрали именованные тома (Named Volumes). Вы создавали их явно, задавая понятное имя: docker run -v pg_data:/var/lib/postgresql/data.

Но что произойдет, если во флаге -v передать только путь внутри контейнера, опустив имя тома?

docker run -v /var/lib/postgresql/data postgres:15

Docker не выдаст ошибку. Вместо этого он поймет: «Пользователь хочет вынести эту директорию из Writable-слоя контейнера в безопасное хранилище демона, но не сказал, как его назвать». Демон сгенерирует тому уникальное имя — длинный случайный хэш (например, f8a9b2c3d4...) — и привяжет его к контейнеру.

Анонимный том (Anonymous Volume) — это полноценный управляемый том Docker, которому демон автоматически присвоил имя в виде уникального хэша. Технически он ничем не отличается от именованного тома, кроме отсутствия человекочитаемого идентификатора.

Если вы выполните docker volume ls, вы увидите список из десятков подобных строк: local f8a9b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1

Использовать CLI для создания анонимных томов бессмысленно — вы не сможете легко найти этот хэш, чтобы подключить том к другому контейнеру. Главный источник появления таких томов кроется не в консоли, а в исходном коде образов.

Инструкция VOLUME в Dockerfile

Создатели официальных образов баз данных (PostgreSQL, MySQL, MongoDB) знают о правиле эфемерности контейнеров. Они понимают: если разработчик запустит базу данных и забудет пробросить том через флаг -v, все сохраненные данные исчезнут при первом же удалении контейнера.

Чтобы защитить пользователей от фатальных ошибок, авторы образов используют инструкцию VOLUME прямо в Dockerfile.

Вот реальная строка из официального Dockerfile для PostgreSQL: VOLUME /var/lib/postgresql/data

Эта инструкция работает как страховка. Она говорит Docker-демону: «Каждый раз, когда из этого образа запускается контейнер, обязательно выноси директорию /var/lib/postgresql/data в отдельный том».

Если при запуске docker run вы укажете свой именованный том или Bind Mount для этого пути, Docker послушает вас — настройки из командной строки всегда приоритетнее. Но если вы запустите контейнер «голым» (docker run -d postgres:15), Docker автоматически создаст для этой директории анонимный том.

Проблема «сиротских» томов (Dangling Volumes)

Страховка в виде инструкции VOLUME спасает данные, но порождает архитектурную проблему — утечку дискового пространства.

Вспомним жизненный цикл: тома управляются демоном и существуют независимо от контейнеров. Когда вы удаляете контейнер командой docker rm, привязанный к нему анонимный том остается на диске.

Представьте процесс локальной разработки:

  1. Вы запустили контейнер базы данных: родился анонимный том hash_1.
  2. Вы остановили и удалили контейнер. Том hash_1 остался на диске.
  3. Вы снова запустили контейнер из того же образа. Docker не знает, что вы хотите использовать старые данные (ведь вы не передали имя тома), и создает новый анонимный том hash_2.
  4. Вы повторяете это 50 раз за месяц.

В результате на хост-машине скапливается 50 тяжеловесных томов, которые больше не привязаны ни к одному существующему контейнеру. Такие тома называются сиротскими (dangling).

Как очистить систему

Чтобы найти все тома, которые больше не используются ни одним контейнером, применяется фильтр: docker volume ls -f dangling=true

А для безопасного удаления всех сиротских томов разом существует специальная команда очистки: docker volume prune

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

Правило хорошего тона

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

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

Всегда переопределяйте пути, помеченные инструкцией VOLUME, с помощью явных именованных томов или Bind Mounts при запуске контейнера.

Сравнение и выбор: Когда использовать Bind Mounts, а когда Volumes

Сравнение и выбор: Когда использовать Bind Mounts, а когда Volumes

Представьте ситуацию: вы настраиваете базу данных для production-сервера. Желая сохранить данные, вы пробрасываете директорию с хоста через Bind Mount в /opt/db_data. Месяц всё работает идеально. Но однажды системный администратор обновляет политики безопасности ОС и меняет права доступа на папку /opt. Ваш контейнер мгновенно падает с ошибкой «Permission denied», а приложение перестает обслуживать пользователей.

Инструмент был применён технически верно, но архитектурно — ошибочно. Знать механику работы Bind Mounts и Volumes недостаточно. Главный навык инженера — понимать, кому в конкретный момент времени мы готовы доверить контроль над данными.

Ось контроля: Хост против Демона

Фундаментальная разница между механизмами хранения заключается не в синтаксисе флага -v, а в зоне ответственности. Выбирая тип монтирования, вы отвечаете на один вопрос: кто является владельцем этих данных?

Bind Mount — это территория хоста. Когда вы используете Bind Mount, Docker выступает в роли арендатора. Он получает доступ к файлам, но правила диктует операционная система хоста. Если другой процесс на сервере удалит файл, изменит права доступа (UID/GID) или переместит папку — Docker ничего не сможет с этим сделать, контейнер просто столкнется с последствиями. Это даёт максимальную свободу инженеру, но лишает хранилище предсказуемости.

Volume — это территория Docker. Именованные тома создаются в защищенной зоне (обычно /var/lib/docker/volumes/). Операционная система хоста «видит» эти файлы, но негласно действует правило: ни один процесс, кроме демона Docker, не должен их трогать. Это гарантирует, что права доступа останутся неизменными, эффект Copy-up отработает штатно, а данные не будут случайно повреждены скриптами очистки ОС.

Матрица принятия решений

Опираясь на ось контроля, мы можем вывести четкие правила для типичных инженерных задач.

Сценарий Выбор Обоснование (почему именно так)
Базы данных в Production (PostgreSQL, MySQL, Redis) Named Volumes Данные БД критичны. Нам нужна гарантия, что права доступа не сломаются из-за настроек хоста. Эффект Copy-up позволит корректно инициализировать пустой том дефолтными файлами БД из образа.
Локальная разработка (Hot-reload кода) Bind Mounts Исходный код принадлежит вашей IDE и вам. Вы постоянно меняете файлы на хосте, и контейнер должен мгновенно видеть эти изменения через маскирование.
Единичные конфигурации (nginx.conf, prometheus.yml) Bind Mounts Конфиг управляется системами контроля версий (Git) или инструментами автоматизации (Ansible) на хосте. Контейнеру нужен только доступ на чтение (read-only).
Кэш компиляторов или временные файлы Anonymous Volumes Нам нужно вынести интенсивную запись за пределы Writable-слоя для скорости, но сами данные не представляют ценности. Имя тома не имеет значения.

Главное правило DevOps: Если данные генерируются внутри контейнера и должны пережить его перезапуск — используйте Volumes. Если данные создаются снаружи (вами или другими программами на хосте) и контейнер должен их только прочитать или исполнить — используйте Bind Mounts.

Симбиоз: Паттерн резервного копирования

Иногда возникает задача, которая кажется тупиковой: как сделать бэкап базы данных, которая хранится в Named Volume?

Мы знаем, что лезть напрямую в /var/lib/docker/volumes/ через консоль хоста — это антипаттерн, нарушающий изоляцию. Нам нужен способ извлечь данные из зоны ответственности Docker и безопасно передать их в зону ответственности хоста. Здесь оба механизма начинают работать вместе в рамках одного временного контейнера.

Этот классический паттерн называется Backup Container (контейнер-архиватор).

Механика процесса выглядит так:

  1. Запускается легковесный временный контейнер (например, на базе alpine или ubuntu).
  2. К нему подключается Named Volume (с данными БД) в директорию /data.
  3. К нему же подключается Bind Mount (папка на хосте) в директорию /backup.
  4. Внутри контейнера выполняется команда архивации (например, tar), которая берет файлы из /data и сохраняет архив в /backup.

Команда запуска такого процесса выглядит так: docker run --rm -v pg_storage:/data -v $(pwd):/backup ubuntu tar cvf /backup/db_archive.tar /data

Как только архивация завершается, временный контейнер удаляется (благодаря флагу --rm), оставляя базу данных нетронутой в томе, а готовый архив — на вашем хосте. Этот пример идеально демонстрирует, что Bind Mounts и Volumes — не взаимоисключающие конкуренты, а два разных инструмента, которые в профессиональной инфраструктуре дополняют друг друга.

Практика: Подключение внешней конфигурации и сохранение базы данных

Практика: Подключение внешней конфигурации и сохранение базы данных

Мы разобрали изолированные механизмы: Bind Mounts дают нам прямой контроль над файлами с хоста, а Volumes обеспечивают надежное хранилище под управлением демона. Но в реальных задачах эти инструменты редко используются поодиночке. Базе данных нужно и то, и другое: ее данные должны лежать в защищенном томе, а ее настройки мы хотим удобно редактировать прямо в IDE на хост-машине.

Давайте объединим всё, что мы изучили, и построим production-like конфигурацию для PostgreSQL. Наша цель — запустить базу данных так, чтобы она пережила полное удаление контейнера, но при этом работала по нашим кастомным правилам.

Шаг 1: Подготовка конфигурации (Bind Mount)

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

Вместо этого мы создадим файл на хосте и пробросим его в контейнер через Bind Mount.

Создадим локальный файл custom_pg.conf в текущей директории:

echo "max_connections = 200" > custom_pg.conf
echo "shared_buffers = 256MB" >> custom_pg.conf

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

Шаг 2: Подготовка хранилища (Named Volume)

Данные таблиц и индексов — это самое ценное, что есть в БД. Использовать для них Bind Mount опасно из-за возможных конфликтов прав доступа между хостом и контейнером. Идеальный выбор — именованный том.

Создадим его заранее:

docker volume create pg_prod_data

Теперь у нас есть защищенная директория в /var/lib/docker/volumes/, которой безраздельно управляет демон Docker.

Шаг 3: Великий синтез

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

docker run -d \
  --name my_postgres \
  -e POSTGRES_PASSWORD=supersecret \
  -v pg_prod_data:/var/lib/postgresql/data \
  -v $(pwd)/custom_pg.conf:/etc/postgresql/postgresql.conf \
  postgres:15 \
  -c config_file=/etc/postgresql/postgresql.conf

Разберем эту команду по слоям:

  1. -e POSTGRES_PASSWORD=supersecret — обязательная переменная окружения для официального образа.
  2. -v pg_prod_data:/var/lib/postgresql/data — подключаем Named Volume в стандартную директорию, куда PostgreSQL пишет файлы таблиц.
  3. -v $(pwd)/custom_pg.conf:/etc/postgresql/postgresql.conf — подключаем Bind Mount, пробрасывая конкретный файл с хоста внутрь контейнера.
  4. -c config_file=... — это аргументы, которые передаются процессу (переопределение CMD), заставляющие базу читать наш проброшенный файл.

Шаг 4: Проверка на устойчивость

Архитектура построена. Теперь проведем краш-тест. Зайдем внутрь работающего контейнера и создадим таблицу с данными:

docker exec -it my_postgres psql -U postgres -c "CREATE TABLE users (name VARCHAR(50)); INSERT INTO users VALUES ('Alice'), ('Bob');"

Данные записаны. А теперь совершим то, что в мире без томов привело бы к катастрофе — полностью уничтожим контейнер:

docker rm -f my_postgres

Контейнера my_postgres больше не существует. Его Writable-слой удален навсегда. Но наши данные и настройки были вынесены за пределы этого слоя.

Запустим ту же самую длинную команду docker run из Шага 3 еще раз. Контейнер создастся с нуля, но при старте он обнаружит, что в томе pg_prod_data уже лежит инициализированная база данных, а по пути /etc/postgresql/postgresql.conf находится наш файл.

Проверим, выжили ли данные:

docker exec -it my_postgres psql -U postgres -c "SELECT * FROM users;"

Вывод покажет наших пользователей Alice и Bob. Мы добились полной независимости состояния приложения от жизненного цикла самого контейнера.

Итоги управления данными

Мы прошли путь от понимания эфемерности контейнеров до построения устойчивой архитектуры. Вы научились:

  • Использовать Bind Mounts, когда вам нужен прямой доступ к файлам с хоста (код, конфиги).
  • Использовать Volumes, когда вам нужно надежное, быстрое и прозрачное хранилище для состояния (базы данных, кэши).
  • Комбинировать подходы для создания production-ready окружений.

Единственная проблема нашего текущего решения — это огромная и сложная команда docker run, которую легко забыть или написать с опечаткой. В следующем курсе мы решим эту проблему, перейдя от ручного ввода команд к декларативному описанию инфраструктуры.