В списке того, что на сервере действительно жалко потерять, контейнеров нет. Контейнер пересоздаётся за секунды, образ перекачивается из реестра, а вот содержимое тома не восстанавливается само никак: база, загруженные файлы, состояние приложений. При этом первая же попытка скопировать каталог тома на живом сервере часто даёт копию, которая при восстановлении не поднимается, и разбирательство случается в самый дорогой момент. Ниже три рабочие стратегии съёма, честный разбор рисков каждой, команды для tar и rsync, знакомство с restic и правило трёх копий, а в конце про репетицию восстановления, без которой любой бэкап остаётся предположением.
Почему копия живого тома с базой почти всегда битая
Файл базы не монолит, а набор страниц, которые движок пишет по собственному расписанию: часть уже на диске, часть в памяти, часть изменений доберётся при следующем цикле. Копия, снятая в произвольный момент, ловит состояние между записями: заголовок свежий, хвост старый, контрольные суммы не сходятся. Снаружи файл выглядит прилично, размер правильный, и вскрытие происходит только при попытке подняться из копии, когда время дороже всего. Типичный финал сцены: база стартует, первые таблицы отвечают, а самая нужная падает с ошибкой целостности, и вечер уходит на восстановление из обломков.
С файловыми томами картина мягче: картинка или документ либо скопирован целиком, либо нет, и архив на живом сервисе с фотографиями пользователей почти всегда соберётся рабочим. Почти: файл, который пишется в момент копирования, может попасть в архив недописанным, поэтому и здесь знание характера содержимого решает выбор способа.
Отдельная ловушка прячется в правах: тома принадлежат служебным идентификаторам, и копия, снятая с хоста без sudo, окажется пустой, а снятая с sudo превратит восстановление в задачу о правах. Работа через одноразовый контейнер обходит обе проблемы сразу, и весь разбор ниже построен именно на этом приёме. Каталоги, подключённые в контейнеры напрямую с хоста, подчиняются тем же правилам: копируются они как обычные папки, а вопросы консистентности решает всё то же знание о том, что внутри.
Три стратегии съёма и выбор по типу содержимого
Способов снять том ровно столько, сколько у него типов содержимого, и выбор делается до первой команды. Стратегии сводятся к трём:
- Полная остановка приложения и цельный архив тома в коротком окне тишины;
- Копия на живую без остановки для томов с независимыми файлами;
- Консистентный дамп средствами самой базы или кеша.
Первая стратегия скучная и самая надёжная: минута недоступности ночью, за которую том копируется целиком, без вопросов о состоянии. Вторая годится для фотографий, документов и логов, где ценность каждого файла самостоятельна. Третья самая изящная: база сама пишет согласованный снимок средствами движка, и дамп разворачивается на чистом месте без загадок совместимости версий хранилища.
Смешанный стек обслуживается смешанно: типичный проект держит том с базой и том с файлами пользователей, и зрелая схема снимает первый дампом, второй архивом, одним скриптом в одно расписание. Инвентаризация перед настройкой занимает минуту: список томов из консоли показывает имена, и рядом с каждым полезно поставить пометку способа съёма.
Архив тома одноразовым контейнером с tar
Классический снимок тома делается альпийским контейнером с двумя монтированиями: сам том и каталог для архивов на хосте.
docker run --rm -v project_db_data:/data -v /opt/backup:/backup alpine \
tar czf /backup/project_db_data-2026-10-12.tgz -C /data .
Ключ --rm убирает контейнер сразу после завершения, флаг -C с точкой кладёт в архив относительные пути, и распаковка на новом месте не создаёт лишней вложенности. Дата в имени файла обязана быть: архивов становится много, и без даты в имени навигация превращается в угадывание.
Восстановление обратно симметрично, с одной принципиальной деталью: старое содержимое тома очищается перед распаковкой, иначе архив смешается с тем, что там лежало.
docker run --rm -v project_db_data:/data -v /opt/backup:/backup alpine \
sh -c "rm -rf /data/* && tar xzf /backup/project_db_data-2026-10-12.tgz -C /data"
Проверка архива без распаковки делается списком содержимого.
tar tzf /opt/backup/project_db_data-2026-10-12.tgz | head -20
Двадцать строк знакомых имён в ответе означают, что снимок собран и читается. Для регулярного съёма команда оборачивается скриптом с подстановкой даты через date, и такой скрипт уже готов к cron из последнего раздела.
Сжатие почти всегда оправдано: текстовые базы худеют в архиве в разы, а фотографии почти не теряют в размере, зато время на упаковку съедают. Быстрая проверка результата делается одной строкой.
ls -lh /opt/backup/project_db_data-2026-10-12.tgz
Размер в ответе должен быть похожим на сам том, и ноль байт здесь означает не экономию, а пустую неудачу.
Консистентный дамп силами самой базы и кеша
Для томов с базами честнее всего не копировать файлы вовсе, а просить движок о снимке. MariaDB и MySQL снимают дамп утилитой изнутри контейнера, и вывод уходит файлом прямо на хост.
docker compose exec -T mariadb sh -c 'exec mariadb-dump -u root -p"$DB_ROOT_PASSWORD" app' \
> /opt/backup/db-2026-10-12.sql
Дамп согласован по построению: движок пишет его с соблюдением собственных правил целостности, и разворачивается такой файл на любой совместимой версии, от свежего контейнера до новой железки. Проверка содержимого проста: поиском по файлу находятся строки создания таблиц, и их наличие говорит, что снимок не пустой.
PostgreSQL снимается той же логикой собственной утилитой.
docker compose exec -T postgres pg_dump -U app app > /opt/backup/app-2026-10-12.sql
У утилиты есть и параллельный режим для больших баз, но он работает с форматом каталога и разворачивается отдельной утилитой восстановления, поэтому для типичных проектов проще плоский снимок.
Redis умеет то же самое встроенной командой: BGSAVE просит процесс записать снимок в фоновом режиме, и после завершения в томе появляется свежий dump.rdb.
docker compose exec redis redis-cli BGSAVE
docker run --rm -v project_redis_data:/data -v /opt/backup:/backup alpine \
cp /data/dump.rdb /backup/redis-2026-10-12.rdb
Первая команда запускает снимок, вторая копирует готовый файл уже знакомым одноразовым контейнером. Между командами проходит секунда-другая, и для кеша, который переживает потерю без драмы, это самый дешёвый из честных способов.
rsync по SSH и инкрементальные копии без боли
Когда том разрастается за десятки гигабайт, полный архив каждую ночь начинает занимать неприлично долго. rsync решает задачу иначе: он сравнивает источник с прошлой копией и переносит только изменения, и для больших файловых томов это разница между часами и минутами.
sudo rsync -a --delete /var/lib/docker/volumes/project_uploads/_data/ \
Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. :/backups/uploads/
Флаг -a сохраняет права, времена и структуру, завершающий слеш означает копирование содержимого каталога, а ключ --delete превращает копию в зеркало источника, включая исчезнувшие файлы. Ключ требует уважения: он честно удалит из копии то, что удалили из источника, и ошибка в сторону лишнего удаления здесь обходится дороже, чем кажется. Перед первым зеркалированием полезен пробный прогон с ключом dry-run: утилита перечислит, что собралась переносить и удалять, не тронув ни одного файла.
Настоящая сила rsync раскрывается в паре с жёсткими ссылками: приём с ключом --link-dest создаёт снапшоты, которые выглядят полными копиями, а место занимают только под изменения.
sudo rsync -a --delete --link-dest=/backups/uploads/latest \
/var/lib/docker/volumes/project_uploads/_data/ /backups/uploads/2026-10-12/
Дюжина суточных снапшотов занимает место, соизмеримое с одной копией и горстью изменений, и любой день открывается целиком, без сборки из кусочков. Копия уезжает на соседнюю машину по SSH, и это второй носитель из правила, к которому вернёмся ниже.
restic с шифрованием и правило трёх копий
Ручные скрипты хорошую службу сослужили, но у специализированных инструментов три достоинства сразу: шифрование до записи, дедупликация на уровне блоков и готовая уборка старых снимков. restic на момент написания живёт в версии 0.19.1 и умеет всё перечисленное из коробки.
restic init --repo /backups/restic
sudo restic backup --repo /backups/restic /var/lib/docker/volumes/project_db_data
restic forget --repo /backups/restic --keep-daily 7 --keep-weekly 5 --prune
Первая команда создаёт хранилище, вторая делает снимок каталога тома, третья наводит порядок: хранятся последние семь суточных и пять недельных снимков, остальное забывается, и место подчисто освобождается. Каждый снимок хранит отличия от предыдущего, поэтому сотня копий небольшого тома занимает место, соизмеримое с несколькими полными. Список снимков и восстановление делаются парой команд.
restic snapshots --repo /backups/restic
restic restore latest --repo /backups/restic --target /tmp/check
Первая показывает историю с размерами, вторая разворачивает последний снимок в отдельный каталог для проверки глазами.
Репозиторий restic живёт и на удалённой машине: SSH-транспорт входит в комплект, и одна опция превращает локальную схему в разностороннюю. Шифрование до отправки означает, что на чужом хранилище лежат только зашифрованные блоки, и пароль репозитория становится единственным ключом: потерянный пароль означает потерянные копии, поэтому место ему в менеджере секретов, а не в переписке.
Соседний инструмент BorgBackup, на момент написания в версии 1.4.5, идеологически близок и особенно хорош на локальных носителях. Выбор между ними сводится к привычке команды, а правило хранения от выбора не зависит: три копии ценного, на двух разных носителях, одна вне сервера. Локальный архив, копия на соседней машине и зашифрованный снимок в объектном хранилище закрывают и потерю всей площадки, и человеческую ошибку одновременно.
cron с журналом и репетиция восстановления
Ручной запуск бэкапа хорош ровно до первой занятой ночи. Скрипт, который перебирает тома, снимает дампы и архивы, пишет всё в журнал и ротирует старое, превращает разовые акции в систему.
30 2 * * * root /opt/scripts/backup-volumes.sh >> /var/log/backup-volumes.log 2>&1
Строка в cron запускает скрипт каждую ночь в половине третьего, вывод дописывается в журнал, и утром в нём видны имена снятых томов и размеры. Ротация старых архивов делается рядом поиском по времени.
find /opt/backup -name "*.tgz" -mtime +14 -delete
Две недели локальных архивов и месяцы в зашифрованном хранилище закрывают почти все реальные сценарии, от неудачного деплоя до случайного удаления таблицы. Полезная привычка в конце скрипта: проверка, что свежий архив не пустой. Архив нулевого размера тоже результат, только плохой, и его место в журнале с пометкой. Ещё одна строка дисциплины: скрипт дописывает в журнал свободное место на диске, и тренд этой цифры за месяц подскажет, что копии растут быстрее, чем чистятся, и хранилище пора расширять до первой ошибки записи. Сам журнал не растёт вечно: той же командой find с большим сроком подчищаются и его старые куски.
Замыкает дисциплину репетиция. Раз в месяц свежий архив разворачивается в тестовый том, приложение поднимается рядом с боевой копией и делает один настоящий запрос. Десять минут процедуры отвечают на единственный вопрос, ради которого всё затевалось: восстановится ли сервер из копий, которые он делает. Бэкап, который ни разу не восстанавливали, остаётся предположением, сколько бы гигабайт он ни занимал, и эта фраза заслуживает места на стене рядом с мониторингом.
По сути, рабочая схема собирается из четырёх решений: тип содержимого определяет способ съёма, размер определяет инструмент, cron превращает разовое в расписание, а ежемесячная репетиция превращает расписания в страховку. Вечер настройки и десять минут в месяц, и содержимое томов перестаёт быть единственной копией себя.