Холодный резерв mailcow скопирован, а Compose не находит external volume: что проверить перед запуском
Если после копирования холодного резерва mailcow команда docker compose up на запасном сервере сначала ругается «volume already exists but was not created by Docker Compose», а после правки docker-compose.yml — «external volume not found», дело не в повреждённом бэкапе. Разбираю, почему так происходит и как запускать резерв, не редактируя compose-файл вручную.
Резерв скопирован, а Compose спотыкается на volumes — в чём симптом
Служба доставки «Курьер Плюс» — 30 рабочих мест, собственная почта на mailcow для диспетчерской, бухгалтерии и уведомлений курьерам о статусе доставки — обратилась ко мне в сентябре с обычным DR-тестом: раз в квартал разворачиваем холодный резерв на втором VPS в другом дата-центре и проверяем, что он реально поднимается, а не просто лежит папкой на диске. Мы делаем такие проверки для клиентов в рамках работ по резервному копированию и защите почтовой инфраструктуры регулярно, потому что разница между «бэкап есть» и «бэкап поднимается за 20 минут без сюрпризов» — это ровно то, что решает исход простоя, а не факт наличия файлов на резервном диске.
У «Курьер Плюс» резервная копия создавалась штатным create_cold_standby.sh из репозитория mailcow-dockerized, синхронизация по rsync на резервный сервер отрабатывала без ошибок каждую ночь. А вот при первой попытке реально поднять резерв — docker compose up -d на втором сервере — Docker Compose выдал серию предупреждений вида «volume … already exists but was not created by Docker Compose. Use external: true to use an existing volume» на добрый десяток томов: vmail, mysql, redis, postfix, clamd и так далее. Администратор клиента, недолго думая, добавил external: true во все секции volumes в docker-compose.yml — и получил уже не предупреждение, а фатальную ошибку: external volume «postfix-vol-1» not found, при этом сам том в docker volume ls был на месте.
Дальше в статье разбираю, откуда берётся это противоречие — тома вроде бы есть, а Compose то ругается, что они «чужие», то не находит их вовсе — какая часть официальной документации mailcow это уже учитывает, и какой порядок действий перед запуском резерва снимает проблему без ручной правки compose-файла, которая, как правило, не переживает следующее обновление.
Откуда берётся предупреждение «not created by Docker Compose»
Первое предупреждение — не признак поломки, а особенность того, как Docker Compose именует и опознаёт тома. По умолчанию Compose привязывает имя каждого тома к имени проекта: реальное имя в Docker получается как <имя_проекта>_<имя_тома_из_yml>, например mailcowdockerized_vmail-vol-1, а не просто vmail-vol-1, как написано в самом docker-compose.yml. Имя проекта Compose берёт из переменной COMPOSE_PROJECT_NAME (в mailcow она задаётся в mailcow.conf) или, если её нет, из имени каталога, где лежит compose-файл. Если вы только начинаете разбираться, как Compose вообще управляет томами и сетями, полезно сначала закрыть базу — у нас есть разбор основных команд Docker для новичков, где эта механика объяснена без привязки к конкретному проекту.
Когда старая версия create_cold_standby.sh готовит резервный сервер, тома на нём появляются до первого запуска Compose и без его служебных меток проекта — в терминах Compose это тома, которые существуют на Docker-хосте, но не были заведены именно этим проектом через docker compose up или docker compose create. Отсюда и формулировка предупреждения: Docker честно говорит, что видит том с нужным именем, но не может подтвердить, что он его создатель, и предлагает явно объявить том внешним (external: true), если это ожидаемо.
Проблема в том, что это предупреждение — а не ошибка. Сами контейнеры при этом обычно всё равно запускаются и примонтируют существующие тома, потому что Compose использует уже существующий том с совпадающим именем, если явно не указано иное. То есть у «Курьер Плюс» до правки docker-compose.yml резерв формально мог бы подняться и с одними предупреждениями в логе — административная паника из-за текста warning, а не из-за реальной невозможности запуска, встречается на практике чаще, чем кажется.
Почему external: true в лоб — не решение, а новая проблема
Разбор issue #5970 в репозитории mailcow-dockerized описывает почти дословно ситуацию «Курьер Плюс»: автор на mailcow 2024-06c, Docker 27.1.1, Docker Compose 2.27.3 и Ubuntu 22.04 LTS получил серию предупреждений о 14 томах после запуска резервной копии, созданной create_cold_standby.sh, и последовал совету Docker — добавил external: true в секции volumes. Результат — ошибка external volume 'postfix-vol-1' not found, хотя в docker volume ls том был — только под именем mailcowdockerized_postfix-vol-1. Запуститься автору удалось, лишь когда он прописал префикс mailcowdockerized_ во всех ссылках на тома по compose-файлам, — но такая правка живёт ровно до следующего обновления, о чём он сам и написал в issue.
Причина рассинхрона — в том, как Compose трактует поле name у внешнего тома. По спецификации Compose File Reference, если у тома указано external: true без отдельного поля name, Compose ищет том именно с тем именем, что указано как ключ в секции volumes того сервиса (то есть postfix-vol-1 буквально), а не с автоматически подставленным префиксом проекта — в отличие от обычного, не-внешнего тома, где префикс подставляется автоматически. Если реальный том в Docker называется mailcowdockerized_postfix-vol-1 (с префиксом), а в yml просто написано postfix-vol-1: {external: true} — Compose ищет ровно postfix-vol-1 и не находит его, потому что физически он называется иначе. Отдельное поле name: в связке с external: true как раз и существует для того, чтобы явно указать Compose фактическое имя тома на диске, когда оно отличается от ключа в yml — но угадать его без проверки через docker volume ls и docker inspect почти невозможно, а ошибиться легко в обе стороны.
Issue #5970 в итоге закрыли с пометками «not-a-bug» и «support» — вопрос перенаправили в каналы поддержки сообщества, то есть формально признали это не багом самого mailcow, а следствием ручного редактирования compose-файла в обход штатного сценария восстановления. Для практики это значит: ручная правка external: true по томам холодного резерва — рабочий, но хрупкий путь, который легко сломать на следующем обновлении mailcow, потому что update.sh переписывает docker-compose.yml поверх ваших правок.
Что на самом деле должен делать штатный сценарий cold standby
Если вы разворачивали mailcow с нуля по пошаговому регламенту — с дефолтным COMPOSE_PROJECT_NAME и без правок docker-compose.yml — вероятность упереться в эту проблему заметно ниже: имена томов предсказуемы, а обновления идут штатно. Официальная инструкция mailcow по холодному резерву прямо предупреждает: скрипт create_cold_standby.sh рассчитан на установки по умолчанию и «может ломаться при нестандартных переопределениях томов» (volume overrides). Скрипт определяет реальные пути монтирования каждого тома через docker volume ls -qf name=<имя_проекта> и docker inspect (поле Mountpoint), а не полагается на жёстко зашитые пути — это важно понимать, если у вас том вынесен на отдельный диск или в NFS через override, как описано в официальном руководстве по переносу Maildir.
В репозитории есть отдельный исправленный сценарий именно для проблемы «Курьер Плюс»: в декабре 2024 года в create_cold_standby.sh внесли изменение (объединённый пул-реквест с формулировкой «prevent need for external: true»), которое запускает docker compose create на удалённом Docker сразу после копирования каталога mailcow-dockerized — ещё до синхронизации содержимого томов и до первого реального docker compose up (PR #6203 от codiflow, смёржен 10.12.2024). Смысл в том, что docker compose create — часть штатного жизненного цикла Compose, готовящая контейнеры к запуску, — создаёт сети, тома и контейнеры от имени этого Compose-проекта, а rsync потом заливает данные уже в «правильные» тома. Команда идемпотентна: при повторных прогонах скрипта она ничего не меняет. После этого шага Docker уже считает тома «своими», предупреждение не появляется, и никакого external: true в yml прописывать не нужно вообще.
Отсюда практический вывод: если у вас актуальная версия mailcow (собранная после этого исправления) и вы восстанавливаетесь строго по официальной инструкции — через сам скрипт и docker compose up -d сразу после синхронизации, без промежуточных ручных команд — проблема с external volume в принципе не должна возникать. Она проявляется в двух случаях: версия mailcow старше исправления (ветка 2024-06c, как в Issue #5970, вышла раньше декабря 2024), либо восстановление сделано не по сценарию — например, вручную через rsync без последующего docker compose create, или с уже отредактированным до этого docker-compose.yml.
Чек-лист: что проверить перед запуском резерва
Прежде чем разбираться с external volumes и compose-файлом, я всегда прохожу по короткому списку проверок — почти всегда причина в одном из этих пунктов, а не в повреждённых данных:
Во-первых, версия mailcow на источнике и на резервном сервере должна совпадать или быть достаточно свежей, чтобы включать исправление create_cold_standby.sh с docker compose create (по факту — любой релиз после 10 декабря 2024 года; версию видно в веб-интерфейсе mailcow, а дату последнего коммита — командой git -C /opt/mailcow-dockerized log -1 --format=%cd). Во-вторых, стоит явно проверить docker volume ls | grep <имя_проекта> на резервной машине и сравнить список с исходным сервером — если каких-то томов не хватает, значит проблема не в именовании, а в неполной синхронизации rsync, и лечить нужно её, а не compose-файл.
cd /opt/mailcow-dockerized
cat mailcow.conf | grep COMPOSE_PROJECT_NAME
docker volume ls -q --filter name=$(grep COMPOSE_PROJECT_NAME mailcow.conf | cut -d'=' -f2)
docker inspect <имя_тома> --format '{{ .Mountpoint }}'В-третьих, если восстановление делалось не строго по официальной инструкции (например, резерв поднимали вручную из бэкапа, а не через create_cold_standby.sh + docker compose up -d на чистой синхронизированной копии), выполните docker compose create на резервном сервере из каталога /opt/mailcow-dockerized до первого docker compose up -d — это тот самый шаг, которого не хватает, и он безопасен: он не запускает контейнеры, а только регистрирует сети, тома и сами контейнеры как принадлежащие текущему Compose-проекту. И только если после этого предупреждения остаются — а такое бывает при действительно нестандартных volume overrides, например томах на отдельном примонтированном диске, — стоит переходить к ручному external: true с явным name:, сверенным через docker inspect, а не наугад.
- Версия mailcow актуальна и на источнике, и на резерве (после исправления create_cold_standby.sh, конец 2024 года и новее)
- docker volume ls на резерве содержит все тома с исходного сервера — сверено построчно
- COMPOSE_PROJECT_NAME в mailcow.conf совпадает на источнике и резерве (иначе имена томов с префиксом не совпадут)
- Восстановление шло по официальному сценарию: create_cold_standby.sh (с docker compose create внутри) → docker compose up -d
- docker-compose.yml на резерве не редактировался вручную поверх штатного шаблона перед первым запуском
Как разобрали у «Курьер Плюс»: откат правок и повторный запуск
У «Курьер Плюс» версия mailcow оказалась из середины 2024 года — обновления откладывали почти год, потому что «и так работает», это классика для инфраструктуры в 30 рабочих мест без выделенного админа. Сначала я вернул docker-compose.yml на резервном сервере к оригинальному состоянию из репозитория (без ручных external: true, которые администратор клиента успел проставить), затем обновил саму установку mailcow на источнике до актуальной сборки через штатный update.sh — это заодно подтянуло исправленный create_cold_standby.sh. Заодно проверил, что update.sh не спотыкается о версию Docker Compose — с этим у клиента раньше уже была похожая история, поэтому сверка версий вошла в чек-лист отдельным пунктом.
После этого пересоздал резерв с нуля: удалил старые тома на резервном сервере командой docker volume rm (предварительно убедившись, что источник — основной сервер — не пострадает, резервная копия одноразовая и создаётся заново), прогнал create_cold_standby.sh повторно с исходного сервера, а на резервном сразу после синхронизации выполнил docker compose create вручную — не потому что скрипт этого не делал сам, а чтобы наглядно показать администратору клиента шаг, из-за пропуска которого возникла путаница в первый раз.
Тот же принцип холодного резерва я разбирал на примере сервера 1С в другом дата-центре: скопированные файлы — это ещё не резерв, резерв — это то, что реально поднялось и приняло нагрузку. docker compose up -d на резерве поднялся без единого предупреждения про volumes. Полная проверка — остановка контейнеров на основном сервере, переключение MX и A-записи на резервный IP, отправка тестового письма через диспетчерскую почту — заняла около 25 минут, что укладывается в целевой RTO, который мы для «Курьер Плюс» и согласовывали изначально. По итогу договорились об автообновлении mailcow не реже раза в квартал именно для того, чтобы подобные исправления в скриптах восстановления не залёживались на старых версиях.
Что делать, если проблема осталась после всех проверок
Если версия свежая, docker compose create выполнен, а предупреждения всё равно есть — почти наверняка у вас действительно нестандартный volume override: том вынесен на отдельный примонтированный диск, в сетевое хранилище или переопределён через docker-compose.override.yml, как это делается, например, при переносе почтового хранилища (vmail) на отдельный раздел по официальному руководству mailcow. В этом случае предупреждение Compose корректно — такой том действительно создан не текущим Compose-проектом, а руками администратора заранее, и external: true — правильный, штатный способ его подключить, при условии что имя указано точно.
В такой ситуации я всегда сверяю фактическое имя тома командой docker volume ls, а не полагаюсь на память или на то, как том называется в исходном docker-compose.yml, и прописываю его через связку external: true плюс отдельное поле name: с этим точным именем — так Compose ищет том по имени из name, а внутренний ключ секции volumes остаётся человекочитаемым и используется только для ссылок внутри самого yml. И важная оговорка: любая ручная правка docker-compose.yml в mailcow должна попадать не в основной файл (его перезаписывает update.sh при каждом обновлении), а в docker-compose.override.yml рядом с ним — Compose автоматически объединяет оба файла, а override переживает обновления, в отличие от правок в самом docker-compose.yml.
Частые вопросы
Почему Docker Compose вообще предупреждает про тома после холодного резерва mailcow?
Потому что тома физически появились на резервном сервере через rsync раньше, чем Compose успел зарегистрировать их как созданные именно этим проектом. Это стандартное поведение Compose для любых заранее существующих томов, а не специфика mailcow.
Обязательно ли прописывать external: true в docker-compose.yml mailcow?
Нет, если восстанавливаетесь по официальному сценарию create_cold_standby.sh на актуальной версии: скрипт сам выполняет docker compose create на резервной стороне, и Compose начинает считать тома своими без ручных правок yml.
Что означает ошибка external volume "postfix-vol-1" not found, если том виден в docker volume ls?
Обычно это расхождение имён: Compose при external:true ищет том с именем ровно как в ключе секции volumes, а реальный том в Docker называется с префиксом проекта (например, mailcowdockerized_postfix-vol-1). Решение — указать отдельным полем name: фактическое имя, сверенное через docker volume ls.
Где безопасно вносить правки в docker-compose.yml mailcow, чтобы они не терялись при обновлении?
Только в docker-compose.override.yml рядом с основным файлом. Update.sh перезаписывает docker-compose.yml из репозитория при каждом обновлении, а override.yml Compose подхватывает и объединяет с основным файлом автоматически, не трогая его.
Как узнать реальные пути и имена томов mailcow на сервере?
docker volume ls -q --filter name=<имя_проекта_из_COMPOSE_PROJECT_NAME> покажет список, а docker inspect <имя_тома> --format '{{ .Mountpoint }}' — фактический путь на диске. Тем же способом пользуется сам create_cold_standby.sh для определения точек монтирования.
Может ли проблема быть в неполной синхронизации rsync, а не в именовании томов?
Да, и это стоит исключить в первую очередь: сравните списки docker volume ls на источнике и на резерве построчно. Если тома вовсе нет на резервной машине — дело не в external volumes, а в том, что синхронизация не завершилась или упала с ошибкой.
Источники
- Cold standby cannot start due to external volumes — Issue #5970, mailcow-dockerized — Проверил версии окружения (mailcow 2024-06c, Docker 27.1.1, Docker Compose 2.27.3, Ubuntu 22.04 LTS), текст предупреждения по 14 томам, ошибку external volume "postfix-vol-1" not found после external:true, то, что запуск удался только после префикса mailcowdockerized_ во всех ссылках (правка не переживает обновления), и статус not-a-bug/support. https://github.com/mailcow/mailcow-dockerized/issues/5970
- Add create command to prevent external: true warnings — Pull Request #6203, mailcow-dockerized — Проверил, что в create_cold_standby.sh добавлен запуск docker compose create на удалённой стороне сразу после синхронизации, чтобы тома регистрировались как принадлежащие Compose-проекту без external:true; PR codiflow смёржен 10.12.2024; create выполняется сразу после синхронизации каталога mailcow-dockerized, команда идемпотентна. https://github.com/mailcow/mailcow-dockerized/pull/6203
- Cold-standby (rolling backup) — mailcow: dockerized documentation — Проверил формулировку предупреждения о нестандартных volume overrides («may break when you use unsupported volume overrides»), использование docker inspect для определения путей монтирования томов и требования к скрипту (rsync ≥3.1.0, Compose v2). https://docs.mailcow.email/backup_restore/b_n_r-coldstandby/
- Compose File Reference — Volumes (external, name) — Docker Docs — Проверил точный синтаксис external: true и отдельного поля name для внешних томов, а также что при external:true все атрибуты кроме name игнорируются и Compose не создаёт, а только ищет существующий том. https://docs.docker.com/reference/compose-file/volumes/
- docker compose create — CLI reference, Docker Docs — Проверил назначение команды docker compose create — подготовка контейнеров (и сопутствующих сетей/томов) без запуска — и синтаксис вызова docker compose create [OPTIONS] [SERVICE...]. https://docs.docker.com/reference/cli/docker/compose/create/
- Move Maildir (vmail volume) — mailcow: dockerized documentation — Проверил официальный способ переопределения тома vmail через docker-compose.override.yml как пример легитимного volume override, из-за которого предупреждение Compose о внешнем томе оправдано. https://docs.mailcow.email/manual-guides/Dovecot/u_e-dovecot-vmail-volume/



