Резервная копия Docker Compose: как восстановить весь проект на чистом VPS 列印

  • 0

Чтобы восстановить Docker Compose на чистом VPS, сохраните конфигурацию проекта, секреты, постоянные данные и сведения о версиях образов. Одного compose.yaml недостаточно: он описывает запуск контейнеров, но не содержит базу данных, загруженные файлы и содержимое Docker volumes.

Ниже — последовательность для Linux VPS с обычным Docker Engine: подготовить согласованную копию, вынести её с сервера, восстановить данные до первого запуска и проверить приложение до переключения домена.

Что должно входить в резервную копию Docker Compose

Что сохранить Где искать Что произойдёт без этого
Описание проекта compose.yaml или docker-compose.yml, дополнительные файлы -f, профили и команда запуска Не получится точно повторить состав сервисов, сети и подключения данных
Настройки и секреты .env, env_file, файлы secrets и configs, сертификаты, ключи шифрования приложения Приложение не подключится к БД или не сможет расшифровать сохранённые пароли и интеграции
Постоянные данные Именованные и анонимные volumes, каталоги и файлы, подключённые через bind mounts Контейнеры запустятся с пустой базой или без пользовательских файлов
Версии приложения и СУБД Точные ссылки на образы; для собственных сборок — образы, Dockerfile и исходники Восстановление может неожиданно превратиться в обновление с несовместимым форматом данных
Внешние зависимости Внешняя БД, S3, общая сеть другого проекта, настройки reverse proxy, cron/systemd, DNS и списки разрешённых IP Локальные контейнеры будут исправны, а часть функций останется недоступной

Volume — постоянный том, которым управляет Docker. Bind mount — файл или каталог самого VPS, подключённый внутрь контейнера. Оба варианта требуют резервного копирования. Подключённый Docker socket, например /var/run/docker.sock, архивировать как данные приложения не нужно.

docker image save сохраняет образы. docker export не включает содержимое подключённых volumes. Эти команды не заменяют копию постоянных данных. Перенос всего /var/lib/docker тоже не используйте как основной способ восстановления отдельного проекта: он связывает копию с внутренним устройством исходного Docker-хоста.

Когда подходит эта инструкция

Основной сценарий — один VPS, локальные данные и допустимый перерыв на копирование. Сначала останавливаем приложение и фоновые задания, затем СУБД; запускаем их только после завершения архивирования. Так база и связанные с ней файлы перестают изменяться во время копирования.

Команды рассчитаны на Debian или Ubuntu, GNU tar и выполнение от root. На новом VPS используйте ту же архитектуру процессора и сначала те же образы приложения и СУБД. Rootless Docker, userns-remap, сетевые тома, кластеры и переход между архитектурами требуют отдельной проверки прав и процедуры восстановления данных.

Команды на исходном VPS выполняйте в одной shell-сессии: далее используется переменная BACKUP_DIR с путём копии. Команды для нового VPS отмечены отдельно.

В условном примере проект называется myapp, находится в /srv/myapp, содержит сервисы app и db. Данные БД находятся в томе myapp_db_data, а файлы пользователей — в /srv/myapp/uploads. Подставьте свои значения. Если запускаете Compose с дополнительными -f, --env-file или профилями, повторяйте эти параметры во всех командах.

1. Найдите все данные и зафиксируйте рабочую конфигурацию

На исходном VPS выполните:

cd /srv/myapp
docker compose ls
docker compose -p myapp ps -a
docker compose -p myapp config --quiet

Первая команда Compose показывает имена проектов. Вторая должна вывести именно ваши контейнеры. Пустой список означает, что выбраны другое имя проекта, Docker context или пользователь. Сначала исправьте это. Успешный config --quiet завершается без сообщений; ошибки переменных и YAML нужно устранить до резервного копирования.

Для каждого контейнера из списка посмотрите подключения, заменив CONTAINER_NAME его настоящим именем:

docker inspect CONTAINER_NAME --format '{{json .Mounts}}'

У подключения типа volume запишите Name и Destination; у bind — Source и Destination. Получится карта наподобие этой:

Источник Назначение Куда попадёт в копии
myapp_db_data Каталог данных БД внутри контейнера Отдельный архив тома
/srv/myapp/uploads Загрузки пользователей Архив каталога проекта
/etc/myapp/secret.key Ключ приложения Отдельная копия внешнего файла

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

Символическая ссылка на каталог вне /srv/myapp не означает, что его содержимое попадёт в архив проекта. Внешние каталоги, файлы и подключённые хранилища учитывайте отдельно. Важные изменения, сделанные прямо внутри контейнера вне постоянных подключений, также не попадут в архив volumes: перенесите их в конфигурацию, образ или отдельную копию.

Создайте закрытый каталог копии вне каталога проекта:

umask 077
BACKUP_DIR="/srv/backups/myapp-$(date -u +%Y%m%dT%H%M%SZ)"
install -d -m 700 "$BACKUP_DIR"

docker compose -p myapp config > "$BACKUP_DIR/compose.resolved.yaml"
docker compose -p myapp ps -a > "$BACKUP_DIR/containers.txt"
docker compose -p myapp ps -aq | xargs -r docker inspect \
  > "$BACKUP_DIR/containers.inspect.json"
docker compose -p myapp images > "$BACKUP_DIR/images.txt"
docker version > "$BACKUP_DIR/docker-version.txt"
docker compose version > "$BACKUP_DIR/compose-version.txt"
uname -m > "$BACKUP_DIR/architecture.txt"

В compose.resolved.yaml Compose подставляет переменные и объединяет конфигурацию. Файл полезен для сверки, но может содержать пароли и абсолютные пути. В containers.inspect.json останутся фактические подключения и переменные окружения созданных контейнеров. Оба файла храните закрыто; для восстановления сохраняйте также исходные Compose-файлы и .env.

В текстовом файле рядом запишите карту подключений, исходную команду запуска, внешние зависимости и дату копии. Не полагайтесь только на содержимое /srv/myapp: значения, передаваемые через окружение shell или систему развёртывания, тоже должны быть доступны после потери VPS.

2. Сохраните именно те образы, с которыми работали данные

Тег latest и даже номер версии могут указывать на другой образ к моменту восстановления. Для каждого работающего контейнера получите фактический ID образа, затем его digest — идентификатор содержимого:

docker inspect CONTAINER_NAME --format 'reference={{.Config.Image}} id={{.Image}}'
docker image inspect IMAGE_ID --format '{{json .RepoDigests}}'

Сохраните найденные ссылки вида repository@sha256:... и используйте их в image: на новом VPS. Берите digest из образа работающего контейнера: текущее значение тега в реестре уже могло измениться.

Если образ собран локально или его доступность в реестре не гарантирована, сохраните его. Условный пример для контейнера myapp-app-1:

docker image tag \
  "$(docker inspect myapp-app-1 --format '{{.Image}}')" \
  myapp-app:recovery-copy

docker image save -o "$BACKUP_DIR/app-image.tar" \
  myapp-app:recovery-copy

После загрузки этого архива на новом сервере укажите для сервиса image: myapp-app:recovery-copy. Так же можно сохранить остальные необходимые образы. Для восстановления без доступа к реестрам нужны образы всех сервисов, а не только собственного приложения. Файлы сборки сохраняйте вместе с проектом, но пересборку из изменившихся зависимостей не считайте точным воспроизведением прежнего образа.

Заранее загрузите вспомогательный образ ubuntu:24.04, чтобы его скачивание не увеличивало простой. На следующем этапе останавливаются сервисы проекта, а сам Docker Engine продолжает работать:

docker pull ubuntu:24.04

3. Остановите запись и создайте архивы

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

docker compose -p myapp stop -t 120 app
docker compose -p myapp stop -t 120
docker compose -p myapp ps -a
docker compose -p myapp logs --tail=100 db

Если есть отдельные worker или scheduler, остановите их вместе с app перед БД. После остановки не должно оставаться работающих писателей. В журнале СУБД проверьте штатное завершение. Принудительное завершение по тайм-ауту, код 137 или ошибка остановки требуют разбора: при необходимости верните сервис в работу и повторите корректную остановку с большим тайм-аутом.

Для файловой копии PostgreSQL требуется остановленный сервер и весь комплект данных, включая вынесенные WAL и табличные пространства. Это прямо описано в документации PostgreSQL. У SQLite нельзя во время записи копировать только основной .db, забыв журнал WAL; при остановленном приложении сохраняйте весь каталог его данных.

Если простой недопустим, используйте штатную процедуру конкретной СУБД: например, логическую копию PostgreSQL или Online Backup API SQLite. Такой способ имеет собственную процедуру импорта и не заменяет копию загруженных файлов. Снимок VPS также требует проверки согласованности всех дисков и внешних хранилищ. Одной универсальной команды для работающих приложений с разными СУБД нет.

Сохраните каталог проекта, включая скрытые файлы и bind mounts, расположенные внутри него:

tar --numeric-owner --acls --xattrs \
  -czpf "$BACKUP_DIR/project.tar.gz" \
  -C /srv/myapp .

Теперь проверьте существование настоящего тома:

docker volume inspect myapp_db_data

Продолжайте только при правильном имени. Приведённый ниже способ рассчитан на обычный локальный том: Driver: local, без Options, подключающих NFS, другой каталог или устройство. Ошибка в имени опасна тем, что вспомогательный контейнер может создать новый пустой том вместо чтения существующего.

docker run --rm \
  --mount type=volume,src=myapp_db_data,dst=/data,readonly \
  --mount type=bind,src="$BACKUP_DIR",dst=/backup \
  ubuntu:24.04 \
  tar --numeric-owner --acls --xattrs \
  -czpf /backup/myapp_db_data.tar.gz -C /data .

Повторите архивирование для каждого нужного volume из карты. Режим readonly защищает данные от записи этим контейнером, но не останавливает запись из других контейнеров. Поэтому он не заменяет предыдущий шаг.

Внешний каталог архивируйте отдельно по тому же принципу. Например, для настроек и ключа из /etc/myapp:

tar --numeric-owner --acls --xattrs \
  -czpf "$BACKUP_DIR/etc-myapp.tar.gz" \
  -C /etc/myapp .

Опции GNU tar сохраняют числовые идентификаторы владельцев и групп, права и поддерживаемые дополнительные атрибуты. Если команда сообщает об ошибке чтения, изменении файла во время копирования или нехватке места, копия не готова: устраните причину и повторите архивирование затронутых данных при остановленных писателях.

После успешного завершения всех архивов обычный рабочий сервер можно вернуть в работу:

docker compose -p myapp start
docker compose -p myapp ps -a

Если это окончательная копия для переезда, оставьте исходное приложение закрытым для записи до переключения. Иначе новые изменения не попадут на новый VPS. Не используйте для резервного копирования docker compose down -v: параметр -v удаляет управляемые Compose тома.

4. Проверьте комплект и вынесите копию с VPS

Проверьте читаемость каждого архива. Для нашего примера:

tar -tzf "$BACKUP_DIR/project.tar.gz" > /dev/null
tar -tzf "$BACKUP_DIR/myapp_db_data.tar.gz" > /dev/null
tar -tzf "$BACKUP_DIR/etc-myapp.tar.gz" > /dev/null

cd "$BACKUP_DIR"
sha256sum -- * > SHA256SUMS

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

Сопоставьте архивы с картой подключений: каждому нужному источнику должен соответствовать архив или отдельно описанная копия внешнего хранилища. Затем скопируйте весь комплект на другой сервер или независимое хранилище. Пример передачи по SSH на подготовленный VPS с разрешённым root-доступом:

scp -pr "$BACKUP_DIR" root@203.0.113.10:/root/

203.0.113.10 — условный адрес, замените его своим. SSH шифрует передачу, но сами tar-архивы остаются незашифрованными. Для долговременного хранения используйте зашифрованное хранилище резервных копий, например restic, а пароль восстановления держите отдельно от VPS. Копия на том же сервере не переживёт его полную потерю.

5. Подготовьте чистый VPS

Установите Docker Engine и Compose plugin по инструкции для своей ОС: Ubuntu или Debian. Проверьте доступ к Docker и архитектуру:

docker version
docker compose version
uname -m
df -h
df -i

docker version должен показать и клиент, и сервер. Архитектура должна соответствовать сохранённой, а свободного места и inode — хватать для распаковки. На одном диске одновременно могут находиться архивы, восстановленные данные, образы и временные файлы приложения. Например, архив на 30 ГБ с данными, занимающими после распаковки 100 ГБ, уже требует больше 130 ГБ свободного места с учётом образов и запаса на работу.

Сначала восстановите прежнюю версию проекта. Обновление приложения, основную версию СУБД и смену архитектуры выполняйте отдельной операцией после проверки копии. Для переноса БД между несовместимыми версиями или архитектурами может понадобиться логический экспорт и импорт вместо распаковки файлов.

До запуска ограничьте доступ к тестовому экземпляру. Проверьте опубликованные ports:: БД не нужно открывать в интернет, если к ней обращаются только контейнеры проекта. Не рассчитывайте только на UFW — публикация портов Docker влияет на обработку правил межсетевого экрана. Внешние сети с external: true, устройства и системные зависимости восстановите до запуска сервисов.

6. Восстановите файлы и подключите правильные volumes

На новом VPS задайте путь к полученному комплекту. Имя каталога ниже — пример; используйте имя своей копии:

BACKUP_DIR=/root/myapp-20261008T120000Z
cd "$BACKUP_DIR"
sha256sum -c SHA256SUMS

У каждого файла должен появиться результат OK. При FAILED или отсутствии файла проверьте источник и повторите передачу. Не восстанавливайте проект из повреждённого комплекта.

Распаковывайте в новые пустые каталоги. Если там уже есть данные, сначала разберите, откуда они появились; не накладывайте копию поверх работающего проекта:

install -d /srv/myapp /etc/myapp

tar --numeric-owner --same-owner --acls --xattrs \
  -xzpf "$BACKUP_DIR/project.tar.gz" -C /srv/myapp

tar --numeric-owner --same-owner --acls --xattrs \
  -xzpf "$BACKUP_DIR/etc-myapp.tar.gz" -C /etc/myapp

Если исходный проект использовал абсолютные пути, восстановите их либо измените все соответствующие подключения. Сверьте .env, файлы секретов и цели символических ссылок.

Для каждого сохранённого volume создайте отдельный пустой том. В примере сначала убедитесь, что имя свободно:

docker volume inspect myapp_db_data

На чистом VPS ожидается ошибка no such volume. Если том найден, не перезаписывайте его: проверьте, к какому проекту он относится. Для нового тома выполните:

docker volume create myapp_db_data
docker pull ubuntu:24.04

docker run --rm \
  --mount type=volume,src=myapp_db_data,dst=/data \
  --mount type=bind,src="$BACKUP_DIR",dst=/backup,readonly \
  ubuntu:24.04 \
  tar --numeric-owner --same-owner --acls --xattrs \
  -xzpf /backup/myapp_db_data.tar.gz -C /data

Теперь в восстановленном Compose-файле явно привяжите логическое имя тома к созданному. Если сервис прежде использовал db_data:, соответствующая запись верхнего уровня выглядит так:

volumes:
  db_data:
    external: true
    name: myapp_db_data

Сохраните прежний путь назначения тома в разделе services. Не переименовывайте логический ключ db_data, если на него уже ссылается сервис. При external: true оставьте в определении тома только external и name; остальные свойства такого определения Compose не допускает.

В этом варианте Compose подключает существующий том по точному имени; создание и удаление тома выполняются отдельно. Так мы избегаем предупреждения «volume already exists but was not created by Docker Compose» и зависимости от имени каталога проекта. При последующих резервных копиях продолжайте включать этот том в карту данных: его нельзя искать только по метке проекта Compose. Правила external и name описаны в документации Compose.

Повторите привязку для остальных восстановленных томов. Для анонимного тома добавьте именованный том в Compose и подключите его к прежнему Destination. Не запускайте сервисы, пока все нужные данные не восстановлены.

7. Запустите проект и проверьте реальные данные

Если сохраняли собственные образы, загрузите каждый из архивов:

docker image load -i "$BACKUP_DIR/app-image.tar"

В image: укажите сохранённый локальный тег либо зафиксированный digest из реестра. Для образов из реестра выполните docker pull с этой точной ссылкой. При необходимости предварительно авторизуйтесь через docker login. До следующего шага все образы проекта должны быть доступны локально.

При учебном восстановлении отключите отправку писем, платежные операции, webhooks, ботов и задания по расписанию средствами приложения. Не подключайте тестовый экземпляр к рабочей внешней БД или очереди. Пока это не обеспечено, запускайте только необходимые для проверки безопасные сервисы.

cd /srv/myapp
docker compose -p myapp config --quiet

docker compose -p myapp up -d --no-build --pull never db
docker compose -p myapp logs --tail=100 db

СУБД должна открыть существующие данные без ошибок совместимости и доступа. Если вместо этого она создаёт новую базу, остановите её и проверьте имя тома, путь назначения и переменную каталога данных. Продолжать запуск приложения с пустой БД не нужно.

После успешного старта БД запустите остальные разрешённые сервисы:

docker compose -p myapp up -d --no-build --pull never
docker compose -p myapp ps -a
docker compose -p myapp logs --tail=200

Отсутствие перезапусков и статус healthy, если настроена проверка здоровья, — только первый этап. Проверьте вход существующего пользователя, последние записи на момент копии, открытие нескольких загруженных файлов и операции чтения и записи. Для тестовой записи используйте безопасный объект; убедитесь, что он сохраняется после перезапуска соответствующего сервиса.

Если HTTPS обслуживается на новом VPS, проверить домен до изменения DNS можно так:

curl --resolve app.example.com:443:203.0.113.10 \
  https://app.example.com/

Замените домен и IP. Запрос пойдёт на новый адрес с правильным именем сайта и проверкой сертификата. Для полноценной проверки в браузере временно задайте такое же соответствие в hosts-файле своего компьютера. После проверки удалите его. Успешная выдача главной страницы сама по себе не подтверждает восстановление БД и файлов.

Что проверить, если восстановленный проект не работает

Симптом Что проверить Следующее действие
Пустой сайт или мастер первоначальной настройки docker inspect: фактическое имя тома и Destination; для bind mount — наличие файлов в Source Остановить приложение и подключить восстановленные данные. Пустой новый том не означает, что архив пропал
Permission denied Числовые UID/GID в архиве и после распаковки; user: в Compose; владельца процесса в образе Вернуть прежнего пользователя или корректно восстановить владельца нужного каталога. Не применять chmod -R 777
Несовместимая версия базы Фактический образ СУБД и версию, на которой сделана копия Вернуться к исходному образу; обновление провести позже по инструкции СУБД
SQL-файл лежит в initdb, но импорт не происходит Не была ли БД уже инициализирована Для официального PostgreSQL /docker-entrypoint-initdb.d выполняется только при пустом каталоге данных. Использовать штатную процедуру импорта в подготовленную БД; не удалять единственный том ради повторной инициализации
Не расшифровываются пароли или интеграции Исходный ключ шифрования, .env, файлы secrets Восстановить прежний ключ. Новый случайный ключ не расшифрует старые значения
Connection refused, приложение не видит БД Логи БД, общую сеть, имя сервиса и порт подключения Использовать имя сервиса Compose, например db, вместо прежнего IP контейнера. localhost внутри приложения указывает на сам контейнер
Контейнер завершается с кодом 137 docker inspect CONTAINER_NAME --format '{{.State.OOMKilled}}', журналы ядра и лимиты памяти При true разобраться с нехваткой RAM/лимитом. При false искать другую причину принудительного завершения; код сам по себе не доказывает повреждение копии
Локально всё работает, снаружи сайт недоступен A и AAAA, опубликованные порты, reverse proxy, сертификаты и внешние правила доступа Исправить сетевую часть и повторить внешний запрос

Чтобы сравнить UID/GID с исходной копией, выполните tar --numeric-owner -tvzf "$BACKUP_DIR/myapp_db_data.tar.gz". Для просмотра владельцев файлов в восстановленном томе без запуска приложения:

docker run --rm \
  --mount type=volume,src=myapp_db_data,dst=/data,readonly \
  ubuntu:24.04 ls -lan /data

Сравнивайте числовые UID/GID: одно и то же число на двух VPS может отображаться под разными именами пользователей. При rootless Docker или переназначении пользовательских пространств простого совпадения чисел на хостах недостаточно — нужна проверка отображения идентификаторов.

Как переключить проект и сохранить возможность отката

Для планового переезда учебную копию сначала проверьте отдельно. Затем снова остановите запись на старом VPS, создайте окончательную копию и восстановите её в новом пустом окружении. Не распаковывайте окончательный архив поверх базы, с которой уже работали во время теста.

После проверки переключите A-запись и при наличии AAAA-запись, настройте reverse proxy и разрешённые IP внешних сервисов. На время обновления DNS старый экземпляр должен оставаться закрытым для записи. Фоновые задания и обработчики очередей включайте только на выбранном рабочем сервере.

Пока новый экземпляр не принял реальные изменения, можно вернуть трафик на прежний VPS. После появления новых заказов, файлов или других записей простой возврат DNS приведёт к работе со старым состоянием: сначала остановите запись и перенесите актуальные изменения обратно. Старый VPS и исходные копии сохраняйте до подтверждения восстановления и появления проверенной копии уже нового сервера.

Как сделать резервирование регулярным

Частоту определяйте допустимой потерей данных. Если полная копия выполняется раз в сутки и между копиями нет других механизмов защиты, при аварии можно потерять почти сутки изменений. Для более строгих требований нужны более частые согласованные копии или восстановление СУБД на момент времени по её журналам.

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

Храните несколько поколений копий, включая копию вне рабочего VPS. Политика удаления старых архивов не должна оставлять вас без последней проверенной версии после неудачного задания. Повторяйте восстановление в отдельной среде после существенных изменений; измеренное время такой проверки даст реалистичную оценку простоя при аварии.

Если VPS уже потерян, а сохранился только Compose-файл, можно повторить инфраструктуру и установку приложения, но восстановить отсутствующие пользовательские данные из YAML нельзя. Нужен сохранившийся диск, отдельный volume, копия БД, пользовательских файлов или внешнего хранилища.

Для восстановления и его предварительной проверки можно подготовить отдельный VPS HSTQ. Выбирайте диск с учётом архивов и распакованных данных, а RAM — по требованиям всего проекта. Если нужна помощь инженера с переносом Docker и проверкой приложения, объём и стоимость работ согласуйте отдельно: администрирование приложений не входит в базовую услугу VPS.


這篇文章有幫助嗎?

相關文章

Какие есть боты/сервисы, которые стоит добавить в исключения? Практический гайд для защиты сайта и бизнеса В современных условиях кибербезопасности настройка блокировок и фильтров — обязательная мера для... Что делать, если сертификаты Let’s Encrypt не обновляются? Простое решение за 5 минут Сертификаты от Let’s Encrypt стали стандартом для бесплатной автоматической защиты сайтов по... Какие сервисы и решения реально помогают? Топ-10 инструментов Почему взламывают сайты и что самое опасное? Современный сайт на WordPress, Битрикс, Joomla,... Лучшие версии PHP и MySQL сейчас для WordPress: что выбрать для максимальной стабильности и скорости? WordPress — самая популярная CMS в мире, и именно поэтому вопрос о правильной версии PHP и... Где сейчас захостить видео, чтобы его просто вставлять на свой сайт без рекламы? Лучшие альтернативы YouTube В 2025 году все чаще сталкиваемся с ситуацией: YouTube работает с перебоями, вставки грузятся...
« 返回

知識庫