Бекапи перестали бути теорією
Зібрав робочий backup pipeline для nginx, Let’s Encrypt, /var/www, MariaDB і Docker volumes, а також автоматизував latest symlink, журнал запусків і restore flow.
Бекапи перестали бути теорією
До цього моменту слово "backup" у мене більше означало намір, ніж процес. Сервер уже виглядав акуратно: nginx, сертифікати, Docker-сервіси, сайт, база. Але я в якийсь момент сам собі поставив просте питання: якщо VPS зламається сьогодні ввечері, чи зможу я завтра зранку підняти все назад без нервів і шаманства?
Після цього етапу відповідь уже не "мабуть так", а "так, ось команди, ось архіви, ось шлях відновлення".
Що саме я вирішив архівувати
Перший робочий набір вийшов таким:
/etc/nginx/etc/letsencrypt/var/www- дамп усіх баз MariaDB
- Docker volume
uptime-kuma - Docker volume
portainer_data
По-простому: я зібрав усе, без чого було б боляче відновлювати сервер. Це не якийсь enterprise monster, але для першого production baseline набір дуже правильний: конфіги, TLS, контент, база і дані контейнерів.
Де все лежить
Я зробив окрему структуру під серверні бекапи:
/home/ubuntu/backups/server/Кожен запуск створює директорію з таймстампом на кшталт:
/home/ubuntu/backups/server/2026-04-15-1139Всередині структура розбита по змісту:
system/web/db/docker-volumes/
Такий поділ дуже допомагає під час restore: не треба згадувати, де саме лежить nginx, а де mariadb-all.sql.gz.
Команди, якими я запускав і перевіряв перший backup
Після того як скрипт уже з'явився на сервері, я перевіряв його руками:
chmod +x /home/ubuntu/scripts/backup.sh
/home/ubuntu/scripts/backup.sh
find /home/ubuntu/backups/server -maxdepth 3 -type f | sortУ результаті отримав повний набір архівів:
/home/ubuntu/backups/server/2026-04-15-1139/db/mariadb-all.sql.gz
/home/ubuntu/backups/server/2026-04-15-1139/docker-volumes/portainer_data.tar.gz
/home/ubuntu/backups/server/2026-04-15-1139/docker-volumes/uptime-kuma.tar.gz
/home/ubuntu/backups/server/2026-04-15-1139/system/etc-letsencrypt.tar.gz
/home/ubuntu/backups/server/2026-04-15-1139/system/etc-nginx.tar.gz
/home/ubuntu/backups/server/2026-04-15-1139/web/var-www.tar.gzПісля цього я не зупинився на самому факті створення файлів і перевірив, що вони взагалі читаються:
tar -tzf /home/ubuntu/backups/server/2026-04-15-1139/system/etc-nginx.tar.gz | head
tar -tzf /home/ubuntu/backups/server/2026-04-15-1139/system/etc-letsencrypt.tar.gz | head
tar -tzf /home/ubuntu/backups/server/2026-04-15-1139/web/var-www.tar.gz | head
gzip -t /home/ubuntu/backups/server/2026-04-15-1139/db/mariadb-all.sql.gz && echo "MariaDB dump OK"
tar -tzf /home/ubuntu/backups/server/2026-04-15-1139/docker-volumes/uptime-kuma.tar.gz | head
tar -tzf /home/ubuntu/backups/server/2026-04-15-1139/docker-volumes/portainer_data.tar.gz | headОце і є той маленький, але дуже дорослий DevOps-момент: backup вважається успішним не тоді, коли архів просто з'явився, а тоді, коли ти його реально відкрив і перевірив.
Найсильніше покращення: latest і LAST_SUCCESSFUL_BACKUP
Одна з найкорисніших змін у скрипті була не в архівації як такій, а в тому, як я зафіксував "останній валідний запуск".
Я додав:
ln -sfn "$RUN_DIR" "$BACKUP_ROOT/latest"
printf '%s\n' "$RUN_DIR" > "$LATEST_FILE"Після цього з'явився стабільний маршрут:
/home/ubuntu/backups/server/latestі окремий файл:
/home/ubuntu/backups/server/LAST_SUCCESSFUL_BACKUPПрактична користь колосальна:
RESTORE_CHECKLIST.mdбільше не прив'язаний до конкретної дати- не треба вручну редагувати шлях після кожного бекапу
- легше автоматизувати перевірки
- менше шансів помилитися під час інциденту
Як виглядає restore checklist після автоматизації
Після доробки RESTORE_CHECKLIST.md вже не вказує на конкретний каталог, а працює через latest.
Ключові restore-команди стали такими:
sudo tar -xzf /home/ubuntu/backups/server/latest/system/etc-nginx.tar.gz -C /
sudo tar -xzf /home/ubuntu/backups/server/latest/system/etc-letsencrypt.tar.gz -C /
sudo tar -xzf /home/ubuntu/backups/server/latest/web/var-www.tar.gz -C /
gunzip -c /home/ubuntu/backups/server/latest/db/mariadb-all.sql.gz | sudo mysql
docker run --rm -v uptime-kuma:/target -v /home/ubuntu/backups/server/latest/docker-volumes:/backup alpine:3.20 sh -c "cd /target && tar -xzf /backup/uptime-kuma.tar.gz"
docker run --rm -v portainer_data:/target -v /home/ubuntu/backups/server/latest/docker-volumes:/backup alpine:3.20 sh -c "cd /target && tar -xzf /backup/portainer_data.tar.gz"І окремо команди швидкої перевірки:
readlink -f /home/ubuntu/backups/server/latest
cat /home/ubuntu/backups/server/LAST_SUCCESSFUL_BACKUP
sudo nginx -t
docker ps
curl -I https://dev.bombaworkflows.com
curl -I https://kuma.bombaworkflows.com
curl -I https://portainer.bombaworkflows.comCron і журнал запусків
Щоб backup не залежав від настрою або пам'яті, я поставив щоденний запуск через cron:
15 3 * * * /home/ubuntu/scripts/backup.sh >> /home/ubuntu/backups/server/backup.log 2>&1Перевірка виглядала так:
crontab -l
systemctl status cron --no-pager
/home/ubuntu/scripts/backup.sh >> /home/ubuntu/backups/server/backup.log 2>&1
tail -n 30 /home/ubuntu/backups/server/backup.logЦе маленька деталь, але вона додає прозорості:
- видно, що backup реально запускався
- видно, де він зупинився, якщо буде помилка
- легше зрозуміти, чи спрацювала ротація
Що я виніс з цього етапу
Цей крок навчив мене дуже простої думки: інфраструктура починає дорослішати в той момент, коли в неї з'являється шлях назад.
Не зелений статус у дашборді. Не красивий reverse proxy. Не навіть HTTPS.
А саме відтворюваний шлях відновлення.
Висновок
Після цього кроку сервер уже не просто "працює". У нього є:
- backup script
- ротація
latestsymlink- лог запусків
- документований restore flow
- перевірені архіви
Для першого DevOps-сервера це вже дуже сильний фундамент. І найприємніше тут те, що це не абстрактна стаття "як могло б бути", а наш реальний шлях з живими командами і живими перевірками на VPS.