Что нужно бэкапить в Docker Compose инфраструктуре
Когда новички думают о бэкапе Docker контейнеров, они часто забывают о критичных компонентах. Вот полный список того, что должно попасть в резервную копию:
- Docker volumes — здесь живут ваши базы данных, загруженные файлы, конфигурации
- Файлы docker-compose.yml и .env — конфигурация всей инфраструктуры
- Bind mounts — примонтированные директории с хоста
- Образы контейнеров (опционально) — если используете кастомные образы
- Сетевые настройки — для сложных конфигураций
Забудьте про бэкап самих контейнеров — это бессмысленно. Контейнеры эфемерны, важны только данные и конфигурация.
Пример 1: Базовый скрипт бэкапа Docker Compose
Начнём с простого, но функционального скрипта, который покрывает 80% задач. Этот скрипт я использую для своих домашних проектов уже 3 года — ни одного сбоя.
#!/bin/bash
# Файл: /opt/scripts/docker-backup-basic.sh
# Базовый скрипт бэкапа Docker Compose
set -euo pipefail # Прерывать при ошибках
# ============= КОНФИГУРАЦИЯ =============
COMPOSE_DIR="/opt/myapp"
BACKUP_ROOT="/backups/docker"
BACKUP_RETENTION_DAYS=30
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="${BACKUP_ROOT}/${DATE}"
# ============= СОЗДАНИЕ ДИРЕКТОРИИ =============
mkdir -p "$BACKUP_DIR"
echo "$(date) - Начало бэкапа в $BACKUP_DIR"
# ============= БЭКАП КОНФИГУРАЦИИ =============
echo "Сохранение docker-compose.yml и .env файлов..."
cd "$COMPOSE_DIR" || exit 1
cp docker-compose.yml "$BACKUP_DIR/"
[ -f .env ] && cp .env "$BACKUP_DIR/"
# Сохранение списка запущенных контейнеров
docker-compose ps > "$BACKUP_DIR/containers-state.txt"
# ============= БЭКАП VOLUMES =============
echo "Бэкап Docker volumes..."
# Получаем список всех volumes проекта
VOLUMES=$(docker volume ls --format '{{.Name}}' | grep "$(basename $COMPOSE_DIR)")
for volume in $VOLUMES; do
echo "Бэкапим volume: $volume"
docker run --rm \
-v "${volume}:/source:ro" \
-v "${BACKUP_DIR}:/backup" \
alpine \
tar czf "/backup/${volume}.tar.gz" -C /source .
done
# ============= БЭКАП BIND MOUNTS =============
echo "Бэкап bind mounts..."
if [ -d "${COMPOSE_DIR}/data" ]; then
tar czf "${BACKUP_DIR}/bind-mounts.tar.gz" -C "${COMPOSE_DIR}" data
fi
# ============= СОЗДАНИЕ АРХИВА =============
echo "Создание итогового архива..."
cd "$BACKUP_ROOT" || exit 1
tar czf "${DATE}.tar.gz" "${DATE}"
rm -rf "${DATE}" # Удаляем временную директорию
# ============= ОЧИСТКА СТАРЫХ БЭКАПОВ =============
echo "Удаление бэкапов старше ${BACKUP_RETENTION_DAYS} дней..."
find "$BACKUP_ROOT" -name "*.tar.gz" -type f -mtime +${BACKUP_RETENTION_DAYS} -delete
echo "$(date) - Бэкап завершён: ${BACKUP_ROOT}/${DATE}.tar.gz"
echo "Размер: $(du -h ${BACKUP_ROOT}/${DATE}.tar.gz | cut -f1)"
Установка и запуск базового скрипта
# Создаём директории mkdir -p /opt/scripts /backups/docker # Сохраняем скрипт nano /opt/scripts/docker-backup-basic.sh # Вставляем код выше # Делаем исполняемым chmod +x /opt/scripts/docker-backup-basic.sh # Тестовый запуск /opt/scripts/docker-backup-basic.sh # Проверяем результат ls -lh /backups/docker/
Пример 2: Продвинутый скрипт с остановкой контейнеров
Для баз данных (PostgreSQL, MySQL, MongoDB) простого копирования volumes недостаточно — можно получить повреждённые данные. Правильный способ — остановить контейнеры или использовать нативные инструменты дампа.
#!/bin/bash
# Файл: /opt/scripts/docker-backup-advanced.sh
# Продвинутый <a href="https://it-apteka.com/ustanovka-n8n-v-lxc-kontejner-proxmox-polnaja-instrukcija-ot-a-do-ja/" data-wpil-monitor-id="353">скрипт с остановкой контейнеров</a> и дампом БД
set -euo pipefail
# ============= КОНФИГУРАЦИЯ =============
COMPOSE_DIR="/opt/myapp"
BACKUP_ROOT="/backups/docker"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="${BACKUP_ROOT}/${DATE}"
LOG_FILE="/var/log/docker-backup.log"
# Функция логирования
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# ============= СОЗДАНИЕ СТРУКТУРЫ =============
mkdir -p "$BACKUP_DIR"/{volumes,configs,dumps}
log "=== Начало бэкапа ==="
# ============= ПРОВЕРКА DOCKER =============
if ! docker ps > /dev/null 2>&1; then
log "ERROR: Docker недоступен!"
exit 1
fi
cd "$COMPOSE_DIR" || exit 1
# ============= БЭКАП КОНФИГУРАЦИЙ =============
log "Сохранение конфигураций..."
cp docker-compose.yml "$BACKUP_DIR/configs/"
[ -f .env ] && cp .env "$BACKUP_DIR/configs/"
docker-compose config > "$BACKUP_DIR/configs/docker-compose-resolved.yml"
# ============= ДАМП POSTGRESQL =============
log "Создание дампа PostgreSQL..."
docker-compose exec -T postgres pg_dumpall -U postgres | \
gzip > "$BACKUP_DIR/dumps/postgres-dump.sql.gz"
# Или для конкретной БД
# docker-compose exec -T postgres pg_dump -U postgres mydb | \
# gzip > "$BACKUP_DIR/dumps/mydb.sql.gz"
# ============= ДАМП MYSQL =============
log "Создание дампа MySQL..."
docker-compose exec -T mysql mysqldump -u root -p"${MYSQL_ROOT_PASSWORD}" --all-databases | \
gzip > "$BACKUP_DIR/dumps/mysql-dump.sql.gz"
# ============= ДАМП MONGODB =============
log "Создание дампа MongoDB..."
docker-compose exec -T mongodb mongodump --archive --gzip | \
cat > "$BACKUP_DIR/dumps/mongodb-dump.archive.gz"
# ============= ОСТАНОВКА КОНТЕЙНЕРОВ =============
log "Остановка контейнеров для консистентного бэкапа..."
docker-compose stop
# ============= БЭКАП VOLUMES =============
log "Бэкап всех volumes..."
VOLUMES=$(docker volume ls --format '{{.Name}}' | grep "$(basename $COMPOSE_DIR)")
for volume in $VOLUMES; do
log "Бэкап volume: $volume"
docker run --rm \
-v "${volume}:/source:ro" \
-v "${BACKUP_DIR}/volumes:/backup" \
alpine \
tar czf "/backup/${volume}.tar.gz" -C /source .
done
# ============= ЗАПУСК КОНТЕЙНЕРОВ =============
log "Запуск контейнеров обратно..."
docker-compose start
# Ждём готовности
sleep 10
# Проверяем, что всё запустилось
if docker-compose ps | grep -q "Exit"; then
log "WARNING: Некоторые контейнеры не запустились!"
fi
# ============= СОЗДАНИЕ ФИНАЛЬНОГО АРХИВА =============
log "Создание итогового архива..."
cd "$BACKUP_ROOT" || exit 1
tar czf "${DATE}.tar.gz" "${DATE}"
BACKUP_SIZE=$(du -h "${DATE}.tar.gz" | cut -f1)
rm -rf "${DATE}"
# ============= ОЧИСТКА СТАРЫХ БЭКАПОВ =============
log "Очистка старых бэкапов (>30 дней)..."
find "$BACKUP_ROOT" -name "*.tar.gz" -type f -mtime +30 -delete
log "=== Бэкап завершён успешно ==="
log "Файл: ${BACKUP_ROOT}/${DATE}.tar.gz"
log "Размер: ${BACKUP_SIZE}"
Пример 3: Скрипт бэкапа без остановки контейнеров
Для production систем, которые нельзя останавливать, используем «горячий» бэкап через нативные инструменты баз данных без downtime.
#!/bin/bash
# Файл: /opt/scripts/docker-backup-hot.sh
# Горячий бэкап без остановки контейнеров
set -euo pipefail
COMPOSE_DIR="/opt/myapp"
BACKUP_ROOT="/backups/docker"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="${BACKUP_ROOT}/${DATE}"
mkdir -p "$BACKUP_DIR"/{dumps,configs}
echo "$(date) - Горячий бэкап без остановки сервисов"
cd "$COMPOSE_DIR" || exit 1
# ============= КОНФИГУРАЦИИ =============
cp docker-compose.yml .env "$BACKUP_DIR/configs/" 2>/dev/null || true
# ============= POSTGRESQL HOT <a class="wpil_keyword_link" href="https://it-apteka.com/category/rezervnoe-kopirovanie/" title="Резервное копирование" data-wpil-keyword-link="linked" data-wpil-monitor-id="47">BACKUP</a> =============
# Используем pg_dump вместо копирования файлов
docker-compose exec -T postgres pg_dump -Fc -U postgres mydb \
> "$BACKUP_DIR/dumps/postgres-mydb.dump"
# Или все базы
docker-compose exec -T postgres pg_dumpall -U postgres \
| gzip > "$BACKUP_DIR/dumps/postgres-all.sql.gz"
# ============= MYSQL HOT BACKUP =============
docker-compose exec -T mysql sh -c \
'mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --single-transaction --quick --all-databases' \
| gzip > "$BACKUP_DIR/dumps/mysql-all.sql.gz"
# ============= MONGODB HOT BACKUP =============
docker-compose exec -T mongodb mongodump --archive --gzip \
> "$BACKUP_DIR/dumps/mongodb.archive.gz"
# ============= REDIS SNAPSHOT =============
# Форсируем сохранение и копируем RDB файл
docker-compose exec -T redis redis-cli BGSAVE
sleep 5
docker-compose exec -T redis sh -c 'cat /data/dump.rdb' \
> "$BACKUP_DIR/dumps/redis-dump.rdb"
# ============= БЭКАП UPLOADED FILES =============
# Если есть volume с файлами пользователей
docker run --rm \
-v myapp_uploads:/source:ro \
-v "${BACKUP_DIR}:/backup" \
alpine \
tar czf /backup/uploads.tar.gz -C /source .
# ============= АРХИВИРОВАНИЕ =============
cd "$BACKUP_ROOT"
tar czf "${DATE}.tar.gz" "${DATE}"
rm -rf "${DATE}"
echo "$(date) - Бэкап завершён: ${BACKUP_ROOT}/${DATE}.tar.gz"
Пример 4: Скрипт с отправкой бэкапа в облако
Локальные бэкапы — это хорошо, но что если сгорит сервер? Правильный подход — дублировать бэкапы в облако. Покажу интеграцию с популярными сервисами.
#!/bin/bash
# Файл: /opt/scripts/docker-backup-cloud.sh
# Бэкап с отправкой в облако (AWS S3, Backblaze B2, rsync)
set -euo pipefail
# ============= КОНФИГУРАЦИЯ =============
COMPOSE_DIR="/opt/myapp"
BACKUP_ROOT="/backups/docker"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_ROOT}/${DATE}.tar.gz"
# Настройки облачных хранилищ
S3_BUCKET="s3://my-backups-bucket/docker/"
B2_BUCKET="b2://my-backup-bucket/docker/"
REMOTE_SERVER="backup@backup-server.com:/backups/docker/"
# <a class="wpil_keyword_link" href="https://t.me/it_apteka_com/34" title="Telegram" data-wpil-keyword-link="linked" data-wpil-monitor-id="268">Telegram</a> уведомления
TELEGRAM_BOT_TOKEN="your-bot-token"
TELEGRAM_CHAT_ID="your-chat-id"
# ============= ФУНКЦИИ =============
send_telegram() {
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d chat_id="${TELEGRAM_CHAT_ID}" \
-d text="$1" > /dev/null
}
# ============= СОЗДАНИЕ БЭКАПА =============
echo "$(date) - Создание локального бэкапа..."
send_telegram "🔄 Начало бэкапа Docker на $(hostname)"
mkdir -p "${BACKUP_ROOT}/tmp"
TEMP_DIR="${BACKUP_ROOT}/tmp/${DATE}"
mkdir -p "$TEMP_DIR"
cd "$COMPOSE_DIR" || exit 1
# Конфигурации
cp docker-compose.yml .env "$TEMP_DIR/" 2>/dev/null || true
# Дампы БД
docker-compose exec -T postgres pg_dumpall -U postgres | \
gzip > "$TEMP_DIR/postgres.sql.gz"
# Volumes
VOLUMES=$(docker volume ls --format '{{.Name}}' | grep "$(basename $COMPOSE_DIR)")
for volume in $VOLUMES; do
docker run --rm \
-v "${volume}:/source:ro" \
-v "${TEMP_DIR}:/backup" \
alpine \
tar czf "/backup/${volume}.tar.gz" -C /source .
done
# Создание архива
cd "${BACKUP_ROOT}/tmp"
tar czf "$BACKUP_FILE" "${DATE}"
rm -rf "${TEMP_DIR}"
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
echo "Локальный бэкап создан: $BACKUP_FILE ($BACKUP_SIZE)"
# ============= ОТПРАВКА В AWS S3 =============
if command -v aws &> /dev/null; then
echo "Отправка в AWS S3..."
if aws s3 cp "$BACKUP_FILE" "${S3_BUCKET}" --storage-class STANDARD_IA; then
echo "✓ Успешно загружено в S3"
send_telegram "✅ Бэкап загружен в S3: ${BACKUP_SIZE}"
else
echo "✗ Ошибка загрузки в S3"
send_telegram "❌ Ошибка загрузки в S3"
fi
fi
# ============= ОТПРАВКА В BACKBLAZE B2 =============
if command -v b2 &> /dev/null; then
echo "Отправка в Backblaze B2..."
if b2 upload-file my-backup-bucket "$BACKUP_FILE" "docker/${DATE}.tar.gz"; then
echo "✓ Успешно загружено в B2"
else
echo "✗ Ошибка загрузки в B2"
fi
fi
# ============= ОТПРАВКА НА УДАЛЁННЫЙ СЕРВЕР =============
if command -v rsync &> /dev/null; then
echo "Отправка на удалённый сервер через rsync..."
if rsync -avz --progress "$BACKUP_FILE" "$REMOTE_SERVER"; then
echo "✓ Успешно скопировано на удалённый сервер"
send_telegram "✅ Бэкап скопирован на backup-server"
else
echo "✗ Ошибка копирования на удалённый сервер"
send_telegram "❌ Ошибка копирования на backup-server"
fi
fi
# ============= ОЧИСТКА ЛОКАЛЬНЫХ БЭКАПОВ =============
echo "Очистка локальных бэкапов старше 7 дней..."
find "$BACKUP_ROOT" -maxdepth 1 -name "*.tar.gz" -type f -mtime +7 -delete
# Очистка в S3 (оставляем последние 30 дней)
if command -v aws &> /dev/null; then
aws s3 ls "${S3_BUCKET}" | while read -r line; do
FILE_DATE=$(echo "$line" | awk '{print $1}')
FILE_NAME=$(echo "$line" | awk '{print $4}')
if [ -n "$FILE_DATE" ] && [ -n "$FILE_NAME" ]; then
AGE_DAYS=$(( ($(date +%s) - $(date -d "$FILE_DATE" +%s)) / 86400 ))
if [ $AGE_DAYS -gt 30 ]; then
echo "Удаление старого бэкапа из S3: $FILE_NAME"
aws s3 rm "${S3_BUCKET}${FILE_NAME}"
fi
fi
done
fi
echo "$(date) - Бэкап завершён успешно"
send_telegram "✅ Бэкап Docker завершён успешно
Размер: ${BACKUP_SIZE}
Сервер: $(hostname)"
Установка AWS CLI для работы с S3
# Установка AWS CLI curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" unzip awscliv2.zip sudo ./aws/install # Настройка credentials aws configure # AWS Access Key ID: ваш_ключ # AWS Secret Access Key: ваш_секрет # Default region name: eu-central-1 # Default output format: json # Проверка aws s3 ls
Пример 5: Настройка Crontab для автоматического бэкапа
Скрипты готовы, теперь автоматизируем их запуск через cron. Покажу несколько стратегий бэкапа для разных сценариев.
# Открываем crontab для редактирования crontab -e # ============= ВАРИАНТ 1: ЕЖЕДНЕВНЫЙ БЭКАП В 3 НОЧИ ============= 0 3 * * * /opt/scripts/docker-backup-basic.sh >> /var/log/docker-backup-cron.log 2>&1 # ============= ВАРИАНТ 2: КАЖДЫЕ 6 ЧАСОВ ============= 0 */6 * * * /opt/scripts/docker-backup-hot.sh >> /var/log/docker-backup-cron.log 2>&1 # ============= ВАРИАНТ 3: ЕЖЕДНЕВНО + ОБЛАКО В ВОСКРЕСЕНЬЕ ============= # Ежедневный локальный бэкап в 02:00 0 2 * * * /opt/scripts/docker-backup-basic.sh >> /var/log/docker-backup-cron.log 2>&1 # Недельный бэкап в облако каждое воскресенье в 04:00 0 4 * * 0 /opt/scripts/docker-backup-cloud.sh >> /var/log/docker-backup-cron.log 2>&1 # ============= ВАРИАНТ 4: СТРАТЕГИЯ 3-2-1 ============= # 3 копии данных, 2 разных носителя, 1 offsite # Ежедневно в 01:00 - локальный бэкап 0 1 * * * /opt/scripts/docker-backup-basic.sh >> /var/log/docker-backup-cron.log 2>&1 # Ежедневно в 02:00 - на второй диск 0 2 * * * rsync -a /backups/docker/ /mnt/backup-disk/docker/ >> /var/log/docker-backup-cron.log 2>&1 # Еженедельно в воскресенье 03:00 - в облако 0 3 * * 0 /opt/scripts/docker-backup-cloud.sh >> /var/log/docker-backup-cron.log 2>&1 # ============= ПРОВЕРКА CRONTAB ============= # Список всех задач crontab -l # Просмотр логов cron tail -f /var/log/docker-backup-cron.log # Или системные логи cron grep CRON /var/log/syslog | tail -20
Расписание cron: шпаргалка по синтаксису
# Формат: минута час день_месяца месяц день_недели команда # * * * * * команда # │ │ │ │ │ # │ │ │ │ └─── день недели (0-7, 0 и 7 = воскресенье) # │ │ │ └───── месяц (1-12) # │ │ └─────── день месяца (1-31) # │ └───────── час (0-23) # └─────────── минута (0-59) # Примеры популярных расписаний: # Каждый день в 02:30 30 2 * * * # Каждый понедельник в 05:00 0 5 * * 1 # Первого числа каждого месяца в 00:00 0 0 1 * * # Каждые 12 часов 0 */12 * * * # Каждые 30 минут */30 * * * * # Рабочие дни (пн-пт) в 18:00 0 18 * * 1-5 # Выходные (сб-вс) в 10:00 0 10 * * 6,7
Продвинутый скрипт восстановления из бэкапа
Бэкап без возможности восстановления — это не бэкап. Вот скрипт для быстрого recovery после сбоя.
#!/bin/bash
# Файл: /opt/scripts/docker-restore.sh
# Восстановление из бэкапа
set -euo pipefail
# ============= ПАРАМЕТРЫ =============
if [ $# -lt 1 ]; then
echo "Использование: $0 <путь_к_бэкапу.tar.gz>"
echo "Пример: $0 /backups/docker/20240120_030000.tar.gz"
exit 1
fi
BACKUP_FILE="$1"
RESTORE_DIR="/opt/restore-$(date +%Y%m%d_%H%M%S)"
COMPOSE_DIR="/opt/myapp"
# ============= ПРОВЕРКИ =============
if [ ! -f "$BACKUP_FILE" ]; then
echo "ERROR: Файл бэкапа не найден: $BACKUP_FILE"
exit 1
fi
echo "Восстановление из: $BACKUP_FILE"
echo "Директория восстановления: $RESTORE_DIR"
read -p "Продолжить? (yes/no): " CONFIRM
if [ "$CONFIRM" != "yes" ]; then
echo "Отменено пользователем"
exit 0
fi
# ============= РАСПАКОВКА БЭКАПА =============
echo "Распаковка бэкапа..."
mkdir -p "$RESTORE_DIR"
tar xzf "$BACKUP_FILE" -C "$RESTORE_DIR" --strip-components=1
# ============= ОСТАНОВКА ТЕКУЩИХ КОНТЕЙНЕРОВ =============
echo "Остановка текущих контейнеров..."
cd "$COMPOSE_DIR" || exit 1
docker-compose down -v # -v удалит volumes
# ============= ВОССТАНОВЛЕНИЕ КОНФИГУРАЦИЙ =============
echo "Восстановление конфигураций..."
cp "$RESTORE_DIR"/configs/* "$COMPOSE_DIR/" 2>/dev/null || \
cp "$RESTORE_DIR"/*.yml "$COMPOSE_DIR/" 2>/dev/null || true
# ============= СОЗДАНИЕ VOLUMES =============
echo "Создание volumes из docker-compose.yml..."
cd "$COMPOSE_DIR"
docker-compose up --no-start
# ============= ВОССТАНОВЛЕНИЕ VOLUMES =============
echo "Восстановление данных volumes..."
for volume_backup in "$RESTORE_DIR"/volumes/*.tar.gz "$RESTORE_DIR"/*.tar.gz; do
if [ -f "$volume_backup" ]; then
volume_name=$(basename "$volume_backup" .tar.gz)
echo "Восстановление volume: $volume_name"
docker run --rm \
-v "${volume_name}:/target" \
-v "$RESTORE_DIR":/backup \
alpine \
sh -c "cd /target && tar xzf /backup/$(basename $volume_backup)"
fi
done
# ============= ВОССТАНОВЛЕНИЕ ДАМПОВ БД =============
if [ -d "$RESTORE_DIR/dumps" ]; then
echo "Запуск контейнеров для восстановления БД..."
docker-compose up -d
# Ждём готовности БД
echo "Ожидание готовности баз данных..."
sleep 15
# PostgreSQL
if [ -f "$RESTORE_DIR/dumps/postgres-dump.sql.gz" ]; then
echo "Восстановление PostgreSQL..."
gunzip < "$RESTORE_DIR/dumps/postgres-dump.sql.gz" | \
docker-compose exec -T postgres psql -U postgres
fi
# MySQL
if [ -f "$RESTORE_DIR/dumps/mysql-dump.sql.gz" ]; then
echo "Восстановление MySQL..."
gunzip < "$RESTORE_DIR/dumps/mysql-dump.sql.gz" | \
docker-compose exec -T mysql mysql -u root -p"${MYSQL_ROOT_PASSWORD}"
fi
# MongoDB
if [ -f "$RESTORE_DIR/dumps/mongodb-dump.archive.gz" ]; then
echo "Восстановление MongoDB..."
gunzip < "$RESTORE_DIR/dumps/mongodb-dump.archive.gz" | \
docker-compose exec -T mongodb mongorestore --archive --drop
fi
else
# Если дампов нет, просто запускаем контейнеры
echo "Запуск контейнеров..."
docker-compose up -d
fi
# ============= ПРОВЕРКА =============
echo ""
echo "============= СТАТУС ВОССТАНОВЛЕНИЯ ============="
docker-compose ps
echo ""
echo "Восстановление завершено!"
echo "Проверьте работоспособность приложения"
echo "Временные файлы в: $RESTORE_DIR"
echo ""
read -p "Удалить временные файлы? (yes/no): " CLEANUP
if [ "$CLEANUP" = "yes" ]; then
rm -rf "$RESTORE_DIR"
echo "Временные файлы удалены"
fi


