Learning Platform
Глоссарий Troubleshooting
Урок 14.05 · 26 мин
Средний
dockerpostgresminiobackupdata-engineering

Backup и restore

«У нас же volume, данные не пропадут» — самая дорогая иллюзия Junior DE. Volume защищает от рестарта контейнера, не от:

  • Случайного docker volume rm pg-data рукой.
  • Развалившегося диска на хосте.
  • DROP TABLE users в неправильной консоли.
  • Поломки самого PG-кластера (например, после неудачного pg_resetwal).

Backup — это отдельный файл в другом месте. Restore — навык, который проверяется тогда, когда уже поздно. В этом уроке: pg_dump из контейнера, mc mirror для MinIO, паттерн backup-контейнера с cron.


pg_dump из контейнера

pg_dump — стандартная утилита Postgres для логического бэкапа. Внутри образа postgres:16 она уже лежит — её можно запустить через docker exec.

Базовая команда:

docker exec pg pg_dump -U de -d warehouse > backup.sql

Что произошло:

  1. docker exec pg запускает процесс внутри контейнера pg.
  2. pg_dump -U de -d warehouse — стандартный вызов, читает БД и пишет SQL в stdout.
  3. > backup.sql на хосте перехватывает stdout и сохраняет в файл backup.sql в текущей директории на хосте.

Это работает, потому что Docker прокидывает stdout процесса в контейнере наружу. Файл backup.sql появится на хосте, не внутри контейнера.

Проверка:

ls -lh backup.sql
head -20 backup.sql

# --
# -- PostgreSQL database dump
# --
# -- Dumped from database version 16.3
# -- Dumped by pg_dump version 16.3
# SET statement_timeout = 0;
# ...
NOTE

Без -it docker exec всё равно работает, но не открывает PTY. Это важно для бэкапа — PTY ломает бинарный поток. Поэтому никогда не используй -it для pg_dump > file — поставит \r\n в неожиданные места и файл будет битый. Только docker exec pg pg_dump ....


Custom-формат vs plain SQL

pg_dump поддерживает несколько форматов:

  • plain (default, -Fp) — текстовый SQL, читается глазами. Минусы: огромный, медленный restore, нельзя выборочно восстанавливать.
  • custom (-Fc) — бинарный, сжатый (gzip-level 6 по умолчанию). Быстрее, меньше, поддерживает pg_restore с фильтрами (только эта таблица, без индексов, и т.д.).
  • directory (-Fd) — каждая таблица в отдельный файл. Поддерживает параллельный dump (-j 4).
  • tar (-Ft) — tar-архив с структурой directory. Редко используется.

Для скриптов backup — всегда custom. Удобнее, меньше, быстрее.

docker exec pg pg_dump -U de -d warehouse -Fc -f /tmp/warehouse.dump
docker cp pg:/tmp/warehouse.dump ./warehouse.dump
docker exec pg rm /tmp/warehouse.dump

Здесь мы пишем во временный файл внутри контейнера и копируем на хост через docker cp. Это нужно, потому что для custom-формата pg_dump хочет файл, а не stdout (хотя -f - тоже работает, но через stdout сжатие иногда косячит).

Альтернатива через pipe (работает в большинстве случаев):

docker exec pg pg_dump -U de -d warehouse -Fc > warehouse.dump
ls -lh warehouse.dump
file warehouse.dump
# warehouse.dump: PostgreSQL custom database dump - v1.14-0

Restore

Для plain SQL dump’а используется psql:

cat backup.sql | docker exec -i pg psql -U de -d warehouse

Ключевой момент — -i, без -t. -i keep STDIN open (нужно для pipe), -t allocate TTY (НЕ нужно для bulk-операций — портит вывод и иногда буферизацию).

Что происходит:

  1. cat backup.sql пишет в stdout (= pipe).
  2. docker exec -i подхватывает stdin (= тот же pipe), пробрасывает в процесс внутри контейнера.
  3. psql читает stdin, выполняет команды одна за одной.

Для custom-форматаpg_restore:

docker exec -i pg pg_restore -U de -d warehouse --clean --if-exists < warehouse.dump

Флаги:

  • --clean — перед restore сделать DROP существующих объектов (таблиц, индексов).
  • --if-exists — DROP только если объект существует (избегаем «object does not exist» ошибок).

Альтернатива через docker cp + exec без stdin:

docker cp warehouse.dump pg:/tmp/warehouse.dump
docker exec pg pg_restore -U de -d warehouse --clean --if-exists /tmp/warehouse.dump
docker exec pg rm /tmp/warehouse.dump
Backup/restore паттерн
docker exec pg_dumpЗапускает pg_dump внутри контейнера pg, читает базу и пишет в stdout
stdout pipe
файл на хостеStdout перенаправлен в локальный файл — backup.dump лежит на хост-машине, вне Docker
файл на хостеТот же файл backup.dump, который мы создали в шаге backup
stdin pipe
docker exec -i pg_restoredocker exec -i принимает stdin через pipe и пробрасывает в pg_restore внутри контейнера

Selective restore через pg_restore

Если в дампе много таблиц, а восстановить нужно одну:

# Список объектов в дампе
docker exec -i pg pg_restore -l < warehouse.dump | head

# Restore только конкретной таблицы
docker exec -i pg pg_restore -U de -d warehouse -t users < warehouse.dump

-l выводит TOC дампа. -t users — restore только таблицы users.


Backup MinIO bucket

С MinIO бэкап организуется через mc mirror — синхронизация bucket’а с локальной директорией или другим bucket’ом.

# mirror local/datalake -> host directory
docker run --rm -it \
  --network host \
  -v $(pwd)/backup:/backup \
  minio/mc \
  sh -c "mc alias set local http://localhost:9000 admin admin12345 && \
         mc mirror local/datalake /backup/datalake"

Что делает mc mirror:

  • Рекурсивно копирует все объекты bucket’а в директорию.
  • При повторном запуске копирует только новые/изменённые объекты (сравнение по mtime и size).
  • Удаляет на destination то, чего нет на source — если включён --remove (по умолчанию ВЫКЛ).

Альтернатива — копировать bucket в другой bucket:

mc mirror local/datalake backup-s3/datalake-snapshot-2026-05-15

Если backup-s3 — это удалённый AWS S3 или другой MinIO, это полноценный off-site backup.


Cron-контейнер для регулярных бэкапов

Bash-скрипты для автоматизации задач

Боли с ручным pg_dump > file.sql — забываешь, опаздываешь, теряешь по 12 часов данных. Стандартный паттерн — отдельный контейнер, который раз в сутки делает дамп.

Простейший вариант — образ с cron и pg_dump:

FROM postgres:16

RUN apt-get update && apt-get install -y cron && rm -rf /var/lib/apt/lists/*

COPY backup.sh /backup.sh
RUN chmod +x /backup.sh

COPY crontab /etc/cron.d/backup
RUN chmod 0644 /etc/cron.d/backup && crontab /etc/cron.d/backup

CMD ["cron", "-f"]

crontab:

0 3 * * * /backup.sh >> /var/log/backup.log 2>&1

backup.sh:

#!/bin/bash
set -e

TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_FILE=/backups/warehouse-${TIMESTAMP}.dump

echo "[$(date)] Starting backup to ${BACKUP_FILE}"

PGPASSWORD=$POSTGRES_PASSWORD pg_dump \
  -h postgres \
  -U $POSTGRES_USER \
  -d $POSTGRES_DB \
  -Fc \
  -f $BACKUP_FILE

echo "[$(date)] Backup done. Size: $(du -h $BACKUP_FILE | cut -f1)"

# rotation: оставляем последние 7 бэкапов
ls -t /backups/warehouse-*.dump | tail -n +8 | xargs rm -f
echo "[$(date)] Rotation done. Kept: $(ls -1 /backups/warehouse-*.dump | wc -l) files"

Compose:

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: de
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: warehouse
    volumes:
      - pg-data:/var/lib/postgresql/data

  backup:
    build: ./backup
    environment:
      POSTGRES_USER: de
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: warehouse
    volumes:
      - backups:/backups
    depends_on:
      - postgres

volumes:
  pg-data:
  backups:

Каждые сутки в 3:00 контейнер backup запускает pg_dump через сеть compose (-h postgres) и кладёт результат в volume backups. Rotation удаляет файлы старше 7 копий.

Cron-backup container в compose
cron tick 03:00В контейнере backup крутится cron в foreground (CMD cron -f), который раз в сутки запускает /backup.sh
pg_dump через сетьbackup.sh вызывает pg_dump -h postgres — обращается к контейнеру postgres по DNS-имени compose-сети
файл в /backupsVolume backups: смонтирован в контейнер. Файлы переживают рестарт backup-контейнера и Postgres'а
rotation -keep 7ls -t | tail -n +8 | xargs rm — удаляем все файлы кроме последних 7. Предотвращает раздувание volume

В production добавь:

  • Загрузку дампов в S3/MinIO (mc cp /backups/$BACKUP_FILE local/backups/).
  • Алёрты при падении бэкапа (cron на Sentry, или Healthchecks.io).
  • Проверку, что dump читается: pg_restore -l backup.dump > /dev/null && echo OK.

Тест восстановления

Это самый важный раздел. Backup, который не проверен на restore — не backup. Раз в неделю прогоняй restore на отдельной базе:

docker run -d \
  --name pg-restore-test \
  -e POSTGRES_USER=de \
  -e POSTGRES_PASSWORD=secret \
  -e POSTGRES_DB=warehouse \
  postgres:16

docker exec -i pg-restore-test pg_restore -U de -d warehouse --clean --if-exists < warehouse.dump

docker exec pg-restore-test psql -U de -d warehouse -c "SELECT COUNT(*) FROM users;"

Если SELECT возвращает ожидаемое число строк — restore рабочий. Если ошибки — это шанс починить до того, как они тебе понадобятся в проде.


Что не покрывает pg_dump

  • Большие базы (>100 GB) — pg_dump будет идти часами. Используй pg_basebackup для physical backup + WAL archiving.
  • Точку во времени (PITR) — pg_dump = snapshot на момент запуска. Для восстановления на любую секунду — pg_basebackup + WAL archiving + recovery_target_time.
  • Базы с активной нагрузкой — pg_dump сам не блокирует, но если за время дампа произошёл DROP TABLE, дамп может быть inconsistent.

Для production-Postgres > 50 GB смотри в сторону pgBackRest, barman — это уже за рамками junior-курса.


Попробуй сам

  1. Подними Postgres с volume, создай таблицу events, вставь 10000 строк (INSERT INTO events SELECT generate_series(1, 10000), 'data';).
  2. Сделай дамп в custom-формате: docker exec pg pg_dump -U de -d warehouse -Fc > warehouse.dump. Проверь размер.
  3. Удали таблицу: docker exec pg psql -U de -d warehouse -c "DROP TABLE events;".
  4. Восстанови из дампа: docker exec -i pg pg_restore -U de -d warehouse < warehouse.dump. Проверь количество строк.
  5. Подними MinIO, создай bucket data, залей несколько файлов. Сделай mc mirror local/data ./backup-minio. Проверь, что файлы скопировались на хост.
  6. Бонус: собери backup-контейнер из примера. Запусти compose, дождись первого выполнения cron (можно поменять расписание на * * * * * для теста — раз в минуту).

Проверка знанийKnowledge check
Как сделать backup Postgres из контейнера и восстановить его, и почему docker exec -i важно использовать БЕЗ флага -t при работе с дампами?
ОтветAnswer
Backup: docker exec pg pg_dump -U de -d warehouse -Fc > backup.dump — pg_dump запускается внутри контейнера, его stdout перенаправляется в файл на хосте. Флаг -Fc делает custom-формат (бинарный, сжатый, поддерживает pg_restore с фильтрами). Restore: cat backup.dump | docker exec -i pg pg_restore -U de -d warehouse --clean --if-exists — docker exec -i подхватывает stdin через pipe и пробрасывает в pg_restore. Флаг -t (allocate TTY) ВРЕДЕН для бэкапов: TTY превращает stdin/stdout в режим терминала, что ломает бинарный поток. Например, \n в дампе может стать \r\n, или часть данных потеряется в буферизации PTY. Файл получится битый, а ошибка проявится только при restore. Правило: для интерактивной работы с контейнером docker exec -it; для bulk-операций с pipe (бэкап, restore, копирование) — только docker exec -i БЕЗ t. Backup без проверки restore — не backup. Регулярно прогоняй pg_restore на тестовом контейнере и проверяй COUNT(*) на ключевых таблицах.

Проверьте понимание

Результат: 0 из 0
Аналитический
Вопрос 1 из 4. Почему для backup Postgres через docker exec ... pg_dump > backup.dump нужно НЕ использовать флаг -t (allocate TTY)?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5