Бэкап баз данных и файлов в S3 с помощью Restic + Healthchecks

Бэкап баз данных и файлов в S3 с помощью Restic + Healthchecks

В этой статье разберём практический и надёжный способ бэкапа:

  • баз данных (PostgreSQL / MariaDB)
  • конфигурационных и пользовательских файлов
  • с хранением в S3 / MinIO
  • с шифрованием
  • с уведомлениями о сбоях через Healthchecks

Решение подходит для:

  • одного сервера
  • Docker / Docker Compose
  • cron или systemd
  • продакшена

Почему Restic, а не rclone sync

rclone sync — это зеркалирование, а не бэкап.

Возможность

Restic

rclone sync

Версии файлов

Защита от удаления

Защита от ransomware

Шифрование

⚠️ вручную

Retention

Вывод:

Restic — это backup-система, rclone — транспорт.

Архитектура решения

PostgreSQL / MariaDB
        ↓ (dump)
   Файл дампа
        ↓
      Restic
        ↓
   S3 / MinIO
        ↓
   Healthchecks (OK / FAIL)

Что мы будем бэкапить

  • База данных (через dump)
  • Файлы и конфиги
  • Хранение в S3
  • Уведомления о статусе

1 Установка Restic

Ubuntu / Debian

apt update
apt install -y restic

Проверка:

restic version

2 Подготовка пароля Restic

Пароль не хранится в открытом виде — только в файле.

mkdir -p /root/.config/restic
openssl rand -base64 48 > /root/.restic-pass
chmod 600 /root/.restic-pass

Потеря пароля = потеря всех бэкапов.

3 Настройка доступа к S3 / MinIO

Создаём env-файл:

/root/.config/restic/backup.env

RESTIC_PASSWORD_FILE=/root/.restic-pass
RESTIC_REPOSITORY=s3:https://minio.example.com/backups/project-name

AWS_ACCESS_KEY_ID=XXXXXXXX
AWS_SECRET_ACCESS_KEY=YYYYYYYY
AWS_DEFAULT_REGION=us-east-1
chmod 600 /root/.config/restic/backup.env

4 Инициализация репозитория (1 раз)

set -a
source /root/.config/restic/backup.env
set +a

restic init

5 Healthchecks (опционально, но очень рекомендовано)

Создаём чек на https://healthchecks.io

Получаем URL вида:

https://healthchecks.example.com/ping/UUID
  • Успех → обычный URL
  • Ошибка → /ping/UUID/1

6 Полный скрипт бэкапа (PostgreSQL + Restic + Healthchecks)

Путь: /opt/project/backup.sh

#!/usr/bin/env bash
set -euo pipefail

### CONFIG
BASE_DIR="/opt/project"
RESTIC_ENV="/root/.config/restic/backup.env"

COMPOSE_FILE="${BASE_DIR}/docker-compose.yml"
COMPOSE_ENV="${BASE_DIR}/.env"
DB_SERVICE="db"

BACKUP_DIR="/var/backups/project"
KEEP_LOCAL_DAYS=7

HC_URL="https://healthchecks.example.com/ping/UUID"

### ERROR HANDLER
fail() {
  curl -fsS --retry 3 --max-time 10 "${HC_URL}/1" >/dev/null 2>&1 || true
}
trap fail ERR

### LOAD RESTIC ENV
set -a
source "$RESTIC_ENV"
set +a

### READ DB CREDS SAFELY
env_get() {
  grep -E "^$1=" "$2" | tail -n1 | cut -d= -f2- | tr -d '"'
}

POSTGRES_USER="$(env_get POSTGRES_USER "$COMPOSE_ENV")"
POSTGRES_PASSWORD="$(env_get POSTGRES_PASSWORD "$COMPOSE_ENV")"
POSTGRES_DB="$(env_get POSTGRES_DB "$COMPOSE_ENV")"

mkdir -p "$BACKUP_DIR"
chmod 700 "$BACKUP_DIR"

DATE="$(date +%F_%H%M%S)"
HOST="$(hostname -s)"

DUMP_FILE="${BACKUP_DIR}/postgres_${POSTGRES_DB}_${HOST}_${DATE}.dump"

### DATABASE DUMP
docker compose -f "$COMPOSE_FILE" --env-file "$COMPOSE_ENV" exec -T \
  -e PGPASSWORD="$POSTGRES_PASSWORD" \
  "$DB_SERVICE" \
  sh -lc "pg_dump -U '$POSTGRES_USER' -d '$POSTGRES_DB' -Fc --no-owner --no-acl" \
  > "$DUMP_FILE"

### RESTIC BACKUP (относительные пути)
(
  cd "$BACKUP_DIR"
  restic backup . \
    --tag project \
    --tag postgres \
    --tag prod \
    --tag "$HOST"
)

### RETENTION
restic forget \
  --keep-within 7d \
  --keep-daily 14 \
  --keep-weekly 8 \
  --keep-monthly 12 \
  --prune

### LOCAL CLEANUP
find "$BACKUP_DIR" -type f -mtime +"$KEEP_LOCAL_DAYS" -delete

### SUCCESS
curl -fsS --retry 3 --max-time 10 "$HC_URL" >/dev/null
echo "[OK] backup successful"
chmod 700 /opt/project/backup.sh

7 Cron: запуск каждые 6 часов

0 */6 * * * root cd /opt/project && ./backup.sh >> /var/log/project-backup.log 2>&1

7.1 Cron vs systemd timer: почему лучше timer и как настроить

Создаём файл:

/etc/systemd/system/backup.service

[Unit]
Description=Backup job via restic
Wants=network-online.target
After=network-online.target docker.service
Requires=docker.service

[Service]
Type=oneshot
WorkingDirectory=/opt/project
ExecStart=/opt/project/backup.sh

# не ограничиваем время выполнения
TimeoutStartSec=0

User=root
Group=root

[Install]
WantedBy=multi-user.target

Timer определяет расписание запуска.

Создаём файл:

/etc/systemd/system/backup.timer

[Unit]
Description=Run backup every 6 hours

[Timer]
# каждые 6 часов: 00:00, 06:00, 12:00, 18:00
OnCalendar=*-*-* 00,06,12,18:00

# если сервер был выключен — выполнить после старта
Persistent=true

# небольшая задержка после загрузки системы
OnBootSec=5min

[Install]
WantedBy=timers.target

Активируем timer:

systemctl daemon-reload
systemctl enable --now backup.timer

Проверяем расписание:

systemctl list-timers | grep backup

Запуск вручную (для теста):

systemctl start backup.service
systemctl status backup.service

Просмотр логов выполнения:

journalctl -u backup.service -n 100 --no-pager
Systemd timer — более надёжная и управляемая замена cron для бэкапов.

8 Проверка состояния

Список снапшотов

restic snapshots

Посмотреть содержимое

restic ls latest

9 Восстановление базы данных

restic restore latest --target /restore
pg_restore -d mydb /restore/postgres_mydb_*.dump

Политика хранения (retention)

--keep-within 7d     # все бэкапы за 7 дней (каждые 6 часов)
--keep-daily 14
--keep-weekly 8
--keep-monthly 12

Это идеальный баланс между:

  • детальностью
  • экономией места
  • возможностью отката

Итог

✔ шифрованные бэкапы

✔ S3 / MinIO

✔ дедупликация

✔ восстановление на любую дату

✔ мониторинг через healthchecks

✔ готово для продакшена

Что можно добавить дальше

  • второй off-site S3 (3-2-1+)
  • immutable bucket / object lock
  • systemd timer вместо cron
  • авто-тест восстановления
  • бэкап volume’ов и конфигов

Restic — один из самых надёжных способов бэкапа сегодня.

Если вы используете Docker и S3 — это практически идеальный вариант.