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
Что произошло:
docker exec pgзапускает процесс внутри контейнераpg.pg_dump -U de -d warehouse— стандартный вызов, читает БД и пишет SQL в stdout.> 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;
# ...
Без -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-операций — портит вывод и иногда буферизацию).
Что происходит:
cat backup.sqlпишет в stdout (= pipe).docker exec -iподхватывает stdin (= тот же pipe), пробрасывает в процесс внутри контейнера.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
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 копий.
В 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-курса.
Попробуй сам
- Подними Postgres с volume, создай таблицу
events, вставь 10000 строк (INSERT INTO events SELECT generate_series(1, 10000), 'data';). - Сделай дамп в custom-формате:
docker exec pg pg_dump -U de -d warehouse -Fc > warehouse.dump. Проверь размер. - Удали таблицу:
docker exec pg psql -U de -d warehouse -c "DROP TABLE events;". - Восстанови из дампа:
docker exec -i pg pg_restore -U de -d warehouse < warehouse.dump. Проверь количество строк. - Подними MinIO, создай bucket
data, залей несколько файлов. Сделайmc mirror local/data ./backup-minio. Проверь, что файлы скопировались на хост. - Бонус: собери backup-контейнер из примера. Запусти compose, дождись первого выполнения cron (можно поменять расписание на
* * * * *для теста — раз в минуту).