Скрипты для бекапа и восстановления Docker образов

Зайчатки разума

Записная книжка айтишника

Скрипты для бекапа и восстановления Docker образов

2026-08-02 21:15:50 — Evgeniy Shumilov

Предпосылки к созданию

  Я уже давно и прочно подсел на идеологию self-hosted. То один, то другой сервис неожиданно отказывают в доступе с территории РФ. Конечно, есть способы получить этот самый доступ, но это вызывает некоторые неудобства. Да и  перефразируя классика, полбеды в том, что сервисы смертны - беда в том, что они могут быть внезапно смертны. Я помню, как накрылся звездою Google Reader, царство ему небесное, Springnote, ICQ, Skype, пачка сервисов Yahoo и другие, помню, как закончилось место на Google Photo, как были предпосылки к закрытию фигмы и Miro на территории РФ и т.п., поэтому стараюсь быть улиточкой и жить по принципу "Всё своё носи с собой". Если кто-то может что-то заблокировать, то нужно исходить из предположения, что это рано или поздно будет заблокировано. Сервисов уже много, в кубер пока не донёс, но стараюсь придерживаться принципа KISS - в /opt директории с сервисами, у каждого сервиса свой docker-compose и локальные директории, которые мапятся в качестве вольюмов. Т.е. переносится всё при необходимости не просто, а очень просто - сворачивается архив, копируется на другую машину, разворачивается, docker compose up -d и сервис жив. Я стараюсь в композах не использовать latest или использую при старте, а потом меняю на текущую версию. А старые версии образов иногда могут и перестать существовать в репозиториях или репозиторий вообще в целом может стать недоступен. Тут вариантов 2 - либо разворачивать свой репозиторий docker образов (и да, такой тоже имеется), либо делать бекапы образов и складывать рядом в поддиректорию backups. Два этих варианта никак не исключают друг-друга, но когда проект самодостаточен, мне спится спокойнее. Собственно, чтобы автоматизировать процесс локального резервного копирования я и написал данные скрипты. И да, виртуальные машины с сервисами тоже регулярно бекапятся.


Для чего ещё это может быть нужно?

Типичные ситуации, когда это может пригодиться:

  1. Офлайн-развертывание. Часто нужно перенести собранный стенд на сервер, у которого нет доступа в интернет. Раньше я иногда бодался с зависимостями, теперь просто бекаплю образы на машине со сборкой и разворачиваю их на целевом сервере.

  2. Бэкап перед обновлением. Перед обновлением docker-compose сервисов я создаю бэкап используемых образов. Если что-то идет не так — restore_images -f в течение минуты возвращает всё к исходному состоянию.

  3. Освобождение места. Бывает, что нужно удалить локальные образы, чтобы освободить место на диске, но при этом сохранить возможность быстро их восстановить. Скрипты делают эту задачу тривиальной.

Как это работает?

В основе работы лежат стандартные команды Docker: docker save для создания архива и docker load для восстановления. Однако скрипты обрастают удобной оберткой, которая берет на себя всю "рутину".

Скрипт бэкапа (backup_images)

Если запустить его без аргументов в директории с docker-compose.yml, он:

  1. Проверяет, какая команда доступна для работы с Compose: docker compose (новая версия) или docker-compose (устаревшая).

  2. Выполняет команду $COMPOSE_CMD config --images, чтобы получить список используемых образов.

  3. Для каждого образа создает архив с именем вида <image_name>_<tag>_<timestamp>.tar.gz, где все слэши в имени образа заменены на подчеркивания.

  4. Сохраняет список всех созданных архивов в файл backup_manifest.txt.

Скрипт восстановления (restore_images)

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

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

  2. Выполняет пре-чек (pre-flight check): перед тем как начать восстанавливать что-либо, скрипт проверяет все указанные для восстановления образы. Если хотя бы один из них используется запущенным контейнером, процесс восстановления прерывается, а пользователь видит список конфликтующих образов и контейнеров. Это полностью исключает ситуацию, когда вы случайно "поломаете" работающий сервис.

  3. Если образ не используется, скрипт проверяет, существует ли он локально. Если да — спрашивает, нужно ли его заменить (при запуске без ключа -f) или просто заменяет его без вопросов (с ключом -f).

  4. После успешного восстановления выводит список только что восстановленных образов.

Примеры из жизни

Давайте посмотрим, как это выглядит на практике. У меня есть проект espcan с двумя сервисами: platformio и espconnect. Вот как происходит бэкап и восстановление.

Создание бэкапа
 

evgeniy@ligthcoder:~/projects/espcan/backups$ ./backup_images
=== Docker Image Backup Tool ===

Auto-detecting images using 'docker compose config --images'...
Found images:
ruseler/espconnect:latest
shaguarger/platformio:latest

Output directory: ./backups_20260802_192015

Images to backup:
ruseler/espconnect:latest
shaguarger/platformio:latest

Backing up ruseler/espconnect:latest to ruseler_espconnect_latest_20260802_192015.tar.gz...
[OK] Successfully backed up (25M)
Backing up shaguarger/platformio:latest to shaguarger_platformio_latest_20260802_192021.tar.gz...
[OK] Successfully backed up (376M)

=========================================
Backup completed
Successful: 2 images
=========================================

Backup files saved in: ./backups_20260802_192015

Manifest:
./backups_20260802_192015/ruseler_espconnect_latest_20260802_192015.tar.gz
./backups_20260802_192015/shaguarger_platformio_latest_20260802_192021.tar.gz

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

Восстановление образа (интерактивный режим)

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

evgeniy@ligthcoder:~/projects/espcan/backups$ ./restore_images backups_20260802_193802/ruseler_espconnect_latest_20260802_193802.tar.gz
=== Docker Image Restore Tool ===

Checking for conflicts...
No conflicts found, proceeding with restore...

Restoring ruseler/espconnect:latest from backups_20260802_193802/ruseler_espconnect_latest_20260802_193802.tar.gz...
[WARN] Image ruseler/espconnect:latest already exists. Replace? (y/N): y

Removing existing image...
Untagged: ruseler/espconnect:latest
Deleted: sha256:fa2387995dc8988049fbdb29fa7aeb6c25b0d2c10051b0e35d924fc00fc82d13
Loading image (32M)...
Loaded image: ruseler/espconnect:latest
[OK] Successfully restored ruseler/espconnect:latest

=========================================
Restore completed
Restored: 1 images
=========================================

Restored images:
ruseler/espconnect:latest (33.7MB)

Восстановление с принудительной заменой (-f)

Если вы уверены в своих действиях, можно пропустить вопрос, добавив флаг -f:

evgeniy@ligthcoder:~/projects/espcan/backups$ ./restore_images -f backups_20260802_193802/ruseler_espconnect_latest_20260802_193802.tar.gz
=== Docker Image Restore Tool ===

Checking for conflicts...
No conflicts found, proceeding with restore...

Restoring ruseler/espconnect:latest from backups_20260802_193802/ruseler_espconnect_latest_20260802_193802.tar.gz...
[WARN] Image ruseler/espconnect:latest already exists, removing (forced)...
Untagged: ruseler/espconnect:latest
Deleted: sha256:fa2387995dc8988049fbdb29fa7aeb6c25b0d2c10051b0e35d924fc00fc82d13
Loading image (32M)...
Loaded image: ruseler/espconnect:latest
[OK] Successfully restored ruseler/espconnect:latest

=========================================
Restore completed
Restored: 1 images
=========================================

Restored images:
ruseler/espconnect:latest (33.7MB)

Ключевые особенности

Я постарался, чтобы скрипты обладали следующими важными качествами:

  1. POSIX-совместимость. Скрипты написаны на чистом sh и не используют bash-специфичных конструкций. Это гарантирует их работу в любом окружении, будь то Alpine, BusyBox или минимальная система, где нет bash. Для меня это было принципиально, так как я часто работаю с легковесными контейнерами (например, lxc c alpine в proxmox). Также я не любитель unicode смайликов, пиктограммочек и прочей мешуры, как и русского языка в комментариях и выводе, ибо не факт, что в вашей среде будут нужные шрифты, SHELL поддерживает подсветку и есть русская локаль. 

  2. Универсальность. Скрипты не привязаны к конкретному проекту. Вы можете запустить backup_images в любой директории, где есть docker-compose.yml, или просто передать список образов в качестве аргументов.

  3. Безопасность восстановления. Как я уже упоминал, встроенная проверка на использование образов запущенными контейнерами — это, на мой взгляд, одна из главных "фишек". Она спасает от необдуманных действий и предотвращает простои сервисов.

  4. Информативность. Скрипты выводят только нужную информацию. Они не используют ANSI-цвета, чтобы не засорять вывод "мусором" в системах без поддержки цветов. Вместо цветов используются простые маркеры [OK][WARN][FAIL], которые понятны в любом терминале.

Заключение

В итоге получился небольшой инструмент, который я теперь периодически использую. Он сделал процесс работы с Docker-образами проще и быстрее. Далее можно автоматизировать - например, поиск с помощью find на глубину 1 в /opt, вход в каждую директорию и бекап по крону, очистка старых бекапов и т.п.. И до этого тоже руки дойдут, я думаю.

Исходный код скриптов доступен в репозитории на GitHub: alive-corpse/docker_images_backups. Если у вас есть идеи по улучшению или вы нашли баг — буду рад вашим issue и pull request'ам. Всем добра.

Теги: docker, shell, instruments

comments powered by Disqus