Эксплуатация, резервные копии и восстановление
Оглавление · Далее: проверки
Примеры production-команд ниже выполняются на VPS в Bash пользователем
gheilt-deploy или уполномоченным администратором. Нельзя заменить их локальным
docker compose и ожидать, что команда затронет сервер.
Удобная оболочка для текущего релиза
release_dir=$(readlink -f /opt/gheilt/current)
test -n "$release_dir" && test -f "$release_dir/release.env"
compose=(docker compose --project-name gheilt \
--env-file /opt/gheilt/.env --env-file "$release_dir/release.env" \
-f "$release_dir/compose.yaml" -f "$release_dir/compose.production.yaml")
dc() { "${compose[@]}" "$@"; }
Дальнейшие команды dc предполагают эту оболочку. Если current ещё нет,
используйте конкретный release directory из неудачного первого запуска.
Статус и логи
dc ps
dc logs --tail=100 web functions
dc logs --tail=100 strapi hatchet postgres
docker stats --no-stream
free -m
df -h
Логи могут содержать payload CMS. Не отправляйте их целиком в публичные issue.
docker stats помогает увидеть рост памяти, но не заменяет длительные метрики:
Prometheus/Grafana/alerting в проекте не настроены.
curl --fail https://gheilt.mxsource.xyz/api/health
curl --fail https://gheilt.mxsource.xyz/api/trpc/news.list
Эти проверки ничего не меняют в данных. Webhook smoke сбрасывает редакционный кеш Next.js; задача Hatchet при этом не создаётся. Health Hatchet может оставаться зелёным при неисправной обработке задач: нужно наблюдать реальный результат в воркере.
Применение новых настроек
Для изменения только кода используйте GitHub Actions. После изменения environment
нужно пересоздать контейнеры: restart не перечитывает environment.
dc up -d --no-build --pull never --wait web functions
Команда использует уже загруженные образы. Наличие отсутствующего образа — повод восстановить доступ к registry через штатный pipeline, а не собирать всё на VPS. Перезапуск очереди/баз должен учитывать активные задачи и доступность приложений.
Что резервировать
| Объект | Зачем |
|---|---|
/opt/gheilt/.env | DB passwords, signing/encryption keys, token |
База strapi | Записи CMS и администраторы |
База hatchet | Определения, очередь, состояние и история задач |
gheilt_s3_metadata | Каталог Garage: buckets, keys, объектные ссылки |
gheilt_s3_data | Байты S3 объектов |
gheilt_strapi_uploads | Media-файлы, на которые ссылается CMS |
gheilt_hatchet_config | Ключи/конфигурация, связанные с токенами |
| PG globals | Роли; файл содержит чувствительные password hashes |
| Тома Caddy | TLS-ключи/конфигурация; можно перевыпустить, но сохранение полезно |
| Release metadata | Какая версия образов работала вместе с копией |
Репозиторий не содержит автоматического backup scheduler, offsite-хранилища или retention policy; одноразовый migration backup проверяет PostgreSQL/Garage restore. Копия на том же VPS не спасает от потери VPS. Храните шифрованную копию отдельно и проверяйте восстановление.
Пример согласованной копии в окно обслуживания
Это процедура с временной недоступностью CMS/очереди. Сначала отрепетируйте её
локально. Не выполняйте команды остановки автоматически ради чтения документации.
Далее предполагаются Bash, определённый dc и уже работающий production:
set -euo pipefail
umask 077
exec 9>/opt/gheilt/deploy.lock
flock -n 9
backup_dir="/opt/gheilt/backups/$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$backup_dir"
# EXIT пытается вернуть сервисы и при ошибке копирования.
trap 'dc up -d --no-build --pull never --wait --wait-timeout 240' EXIT
dc stop --timeout 60 caddy web strapi functions hatchet s3
cp /opt/gheilt/.env "$backup_dir/environment.env"
cp "$release_dir/release.env" "$backup_dir/release.env"
printf '%s\n' "$release_dir" > "$backup_dir/release-path.txt"
dc exec -T postgres pg_dump -U gheilt -d strapi -Fc > "$backup_dir/strapi.dump"
dc exec -T postgres pg_dump -U gheilt -d hatchet -Fc > "$backup_dir/hatchet.dump"
dc exec -T postgres pg_dumpall -U gheilt --globals-only > "$backup_dir/globals.sql"
for volume in s3_metadata s3_data strapi_uploads hatchet_config caddy_data caddy_config; do
docker run --rm -v "gheilt_$volume:/source:ro" -v "$backup_dir:/backup" \
alpine:3.23 tar -czf "/backup/$volume.tgz" -C /source .
done
Проверьте exit status и наличие архивов. pg_dump берёт согласованный snapshot
каждой базы, но два отдельных dump не являются одним межбазовым snapshot.
Остановка producers/worker уменьшает расхождения; таймаут остановки не гарантирует
завершение произвольно долгой задачи. Caddy и Garage остановлены на время копирования: metadata SQLite и object files
копируются согласованно. Изменяемые файлы работающего Garage архивировать нельзя.
EXIT trap поднимает сервисы. После завершения процесса/скрипта освобождается lock.
Если выполняли пример интерактивно, выйдите из этого shell, чтобы освободить fd 9.
Затем проверьте доступность приложений и доставьте зашифрованную копию вне хоста.
Нельзя копировать активную папку PostgreSQL обычным tar вместо pg_dump.
Учебное восстановление базы без изменения production
На локальном Docker-стеке создайте новую базу с отдельным именем:
docker compose exec -T postgres createdb -U gheilt -O strapi learning_restore
docker compose exec -T postgres pg_restore -U gheilt --no-owner --role=strapi \
--exit-on-error --single-transaction -d learning_restore < /path/to/strapi.dump
docker compose exec -T postgres psql -U gheilt -d learning_restore -c '\dt'
/path/to/strapi.dump — доступная локальная учебная копия, не приватная production-
база. Если база уже существует, выберите другое имя. Не применяйте --clean к
рабочей базе для обхода конфликта. Проверка \dt доказывает только наличие таблиц;
нужна проверка данных и запуск отдельного экземпляра CMS на восстановленной базе.
Восстановление всего стека на новом изолированном хосте
- Подготовьте хост и Docker; остановите поступление новых изменений на время переключения. Не меняйте DNS, пока восстановление не проверено.
- Безопасно восстановите
.envв/opt/gheilt/.envс правами 600 и release-файлы нужной версии. Получите соответствующие образы через авторизованный registry. - Поднимите только PostgreSQL. Его init script создаст нужные роли/пустые базы
с паролями из восстановленного
.env. Не запускайте Strapi/Hatchet до restore. - Восстановите custom-format dumps
strapiиhatchetчерезpg_restore --no-ownerс нужной ролью и--exit-on-error --single-transactionв пустые базы. - Восстановите uploads и hatchet_config в отдельные свежие тома с теми же именами. Пример ниже распаковывает только uploads на новом хосте.
- Запустите тот же release, проверьте CMS, файлы, токены, worker connection и задачу.
- После проверки переключите трафик и создайте новую резервную копию.
# На новом хосте, когда текущий release и dc уже подготовлены:
dc up -d --no-build --pull never --wait postgres
dc exec -T postgres pg_restore -U gheilt --no-owner --role=strapi \
--exit-on-error --single-transaction -d strapi < "$backup_dir/strapi.dump"
dc exec -T postgres pg_restore -U gheilt --no-owner --role=hatchet \
--exit-on-error --single-transaction -d hatchet < "$backup_dir/hatchet.dump"
docker volume create gheilt_strapi_uploads
docker run --rm -v gheilt_strapi_uploads:/restore -v "$backup_dir:/backup:ro" \
alpine:3.23 tar -xzf /backup/strapi_uploads.tgz -C /restore
По той же схеме восстановите hatchet_config и, если нужно, Caddy volumes.
Здесь не показан автоматический скрипт полного восстановления: процедура требует
проверки выбранной копии, версии PostgreSQL и совместимости релиза.
globals.sql полезен для дополнительных ролей, но не исполняется вслепую поверх
ролей, уже созданных init script. Восстановление очереди может вернуть старые задачи
и привести к повтору внешних побочных эффектов; нужна идемпотентность обработчиков.
Ручной откат образов
Для отката только web/functions/Caddy на VPS в Bash выберите существующий release. CMS и S3 сохраняются; их откат выполняется отдельно после проверки данных:
rollback_dir=/opt/gheilt/releases/REPLACE_WITH_COMMIT_SHA
old_compose=(docker compose --project-name gheilt \
--env-file /opt/gheilt/.env --env-file "$rollback_dir/release.env" \
-f "$rollback_dir/compose.yaml" -f "$rollback_dir/compose.production.yaml")
exec 9>/opt/gheilt/deploy.lock
flock -n 9
"${old_compose[@]}" config --quiet
mapfile -t images < <("${old_compose[@]}" config --images)
docker image inspect "${images[@]}" >/dev/null
"${old_compose[@]}" up -d --no-deps --no-build --pull never --wait --wait-timeout 240 web functions caddy
curl --fail https://gheilt.mxsource.xyz/api/health
ln -sfn "$rollback_dir" /opt/gheilt/current.next
mv -Tf /opt/gheilt/current.next /opt/gheilt/current
Сначала замените SHA, проверьте совместимость и наличие образов. Это не откат базы. Для постоянного исправления измените Git/branch: следующий push main снова применит содержимое main. Не воспринимайте удачный health как полную проверку старого релиза.
Обновление зависимостей и освобождение диска
Обновляйте небольшими группами: workspace через pnpm, CMS через её npm lockfile,
инфраструктуру через image tags. При major-обновлении PG нужен план миграции данных;
замена 17-alpine на новый major поверх старого volume не является таким планом.
Сначала df -h, docker system df и список релизов. Не запускайте глобальный prune
с volumes: можно потерять данные и образы для rollback. Retention/cleanup в проекте
пока не автоматизированы.
Дополнение для Garage
Прежний пример backup покрывает PostgreSQL и local uploads, но после добавления
S3 его недостаточно: отдельно нужны объекты gheilt_s3_data и metadata
gheilt_s3_metadata, а также ключи из .env. Для согласованной cold-копии
остановите Strapi и Garage; на это время media недоступны. Скопируйте оба тома,
затем поднимите Garage, дождитесь health и поднимите CMS. Проверьте восстановление
на отдельном экземпляре. Не снимайте работающую SQLite metadata обычным tar и
не считайте копию на том же VPS защитой от потери VPS.
Проверяемая предмиграционная копия
release.sh вызывает scripts/deploy/backup-content.sh один раз перед первой новой CMS под deploy flock. Он сохраняет SQL, оба Garage volumes, old uploads, env и release refs в каталог 0700, восстанавливает SQL в отдельную scratch DB и Garage в отдельную сеть с отдельными volumes. Cleanup удаляет только созданные scratch объекты и возвращает прежние приложения. Маркер /opt/gheilt/content-migration.backup указывает на копию. Это проверка миграции, не планировщик ежедневных backups и не offsite backup.
Ручной повтор выполняйте в согласованное окно обслуживания с deploy lock. Новую CMS/data не откатывайте запуском старого Strapi image: используйте проверенную копию на изолированном стенде и отдельный план восстановления. Rollback web использует --no-deps и сохраняет CMS/S3. Не удаляйте volumes исходного проекта ради restore drill.
current хранит релиз web, cms-current — фактически работающий релиз CMS. Во время
подготовки и частичного rollback они могут различаться; backup сохраняет обе ссылки
и возвращает каждую компоненту по её собственной версии. Garage restore drill
также читает контрольный объект и сравнивает байты, а не только проверяет bucket info.