MAATRIX / Блог / pgBackRest в Docker Compose: готовый файл

pgBackRest в Docker Compose: готовый файл

MAATRIX

Если ваш бэкап PostgreSQL — это pg_dump по cron раз в сутки, вы теряете данные за последние часы при любом сбое и тратите всё больше времени на сам дамп по мере роста базы. pgBackRest закрывает обе проблемы: инкрементальные и дифференциальные копии вместо полного дампа каждый раз, параллельное сжатие на несколько потоков и point-in-time recovery — восстановление на конкретную секунду, а не только на момент последнего бэкапа. Ниже — рабочий docker-compose.yml, который поднимает PostgreSQL с pgBackRest внутри одного образа, и пошаговая настройка от инициализации стенджа до восстановления.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Почему pgBackRest, а не pg_dump или физическая копия руками

pg_dump делает логический дамп: читает данные через SQL и пишет их заново при восстановлении. На маленькой базе это быстро и просто, но с ростом объёма время дампа и восстановления растёт почти линейно, а восстановление означает пересоздание индексов с нуля — на базе в сотни гигабайт это часы простоя.

pgBackRest работает на уровне файлов кластера (физический бэкап) и WAL-архива:

  • Инкрементальные и дифференциальные бэкапы. После первого full последующие копируют только изменившиеся файлы (incr) или изменения с момента последнего full (diff) — вместо копирования всей базы заново.
  • Параллелизм. Копирование и сжатие идут в несколько процессов (process-max), что на многоядерном сервере ощутимо сокращает время бэкапа по сравнению с однопоточным pg_basebackup.
  • Point-in-time recovery. Благодаря непрерывному архивированию WAL можно восстановить кластер на произвольный момент между бэкапами, а не только на момент снятия копии.
  • Проверка целостности. Каждый бэкап пишется с контрольными суммами файлов, а check проверяет согласованность архивации и репозитория заранее, а не при восстановлении.
  • Retention из коробки. Политика хранения настраивается в конфиге, без отдельного скрипта с find -mtime -delete.

Минус тоже есть: pgBackRest сложнее в настройке, чем pg_dump | gzip, и требует понимания, что такое стендж (stanza) и архивация WAL. Для базы на пару гигабайт это оверхед. Для продакшена, где важны время восстановления (RTO) и точка восстановления (RPO), — оправданная сложность.

Архитектура: один образ, PostgreSQL и pgBackRest вместе

В контейнерах у pgBackRest есть особенность: команда backup в классическом («posix») режиме требует прямого доступа к каталогу данных PostgreSQL (PGDATA), а archive-push для WAL-архивации вызывается изнутри самого PostgreSQL через archive_command. Проще всего это работает, когда PostgreSQL и pgBackRest находятся в одном контейнере — тогда archive_command обращается к локальному бинарнику, а бэкап снимается без сети и SSH.

Второй вариант — отдельный контейнер pgBackRest, обращающийся к PostgreSQL по сети (pg1-host), но тогда физический доступ к файлам всё равно нужен отдельно, и без SSH-моста между контейнерами это не собрать чисто в compose. Для одиночного сервера общий образ практичнее и надёжнее — так и делаем.

Собираем свой образ поверх официального postgres, добавляем pgBackRest и cron для расписания:

# Dockerfile
FROM postgres:16-bookworm

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

RUN mkdir -p /var/log/pgbackrest /var/lib/pgbackrest \
    && chown -R postgres:postgres /var/log/pgbackrest /var/lib/pgbackrest

COPY pgbackrest.conf /etc/pgbackrest/pgbackrest.conf
COPY crontab.txt /etc/cron.d/pgbackrest-cron
RUN chmod 0644 /etc/cron.d/pgbackrest-cron

COPY entrypoint-wrapper.sh /usr/local/bin/entrypoint-wrapper.sh
RUN chmod +x /usr/local/bin/entrypoint-wrapper.sh

ENTRYPOINT ["/usr/local/bin/entrypoint-wrapper.sh"]
CMD ["postgres"]

Обёртка над стандартным entrypoint запускает cron от root (сам pgBackRest внутри job переключается на пользователя postgres) и затем передаёт управление оригинальному entrypoint-скрипту образа postgres:

#!/usr/bin/env bash
# entrypoint-wrapper.sh
set -e
service cron start
exec docker-entrypoint.sh "$@"

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Готовый docker-compose.yml

services:
  db:
    build: .
    container_name: pg-backrest
    restart: unless-stopped
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD_FILE: /run/secrets/pg_password
    command:
      - "postgres"
      - "-c"
      - "archive_mode=on"
      - "-c"
      - "archive_command=pgbackrest --stanza=main archive-push %p"
      - "-c"
      - "wal_level=replica"
      - "-c"
      - "max_wal_senders=3"
    volumes:
      - pgdata:/var/lib/postgresql/data
      - pgbackrest_repo:/var/lib/pgbackrest
      - ./pgbackrest.conf:/etc/pgbackrest/pgbackrest.conf:ro
    secrets:
      - pg_password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 15s
      timeout: 5s
      retries: 5

secrets:
  pg_password:
    file: ./secrets/pg_password.txt

volumes:
  pgdata:
  pgbackrest_repo:

Репозиторий бэкапов (pgbackrest_repo) — отдельный volume, не подкаталог pgdata. Это важно: если репозиторий бэкапов физически лежит на том же диске, что и рабочие данные, вы теряете и то, и другое при отказе диска. На проде репозиторий стоит выносить на отдельный диск сервера или в S3-совместимое хранилище (ниже об этом отдельно) — сравнение вариантов под такую задачу разобрано в статье про лучший VPS для бэкапов.

Файл pgbackrest.conf рядом с compose-файлом:

[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=4
repo1-retention-diff=6
process-max=2
compress-type=zst
compress-level=3
start-fast=y

[main]
pg1-path=/var/lib/postgresql/data

process-max=2 — для сервера с 2 vCPU; на 4+ ядрах можно поднять до 3-4, но не выше числа ядер минус один, иначе бэкап начинает конкурировать за CPU с самим PostgreSQL. compress-type=zst (Zstandard) даёт лучшее соотношение скорости и степени сжатия, чем gzip — конкретный выигрыш зависит от данных, точных цифр без замера не дам.

Инициализация стенджа и первый бэкап

Стендж (stanza) — это именованная конфигурация одного кластера PostgreSQL внутри pgBackRest: связка «где данные, где репозиторий, какая политика хранения». Поднимаем контейнер и создаём стендж:

docker compose up -d db
docker compose exec db su postgres -c "pgbackrest --stanza=main stanza-create"

Перед первым бэкапом стоит проверить конфигурацию:

docker compose exec db su postgres -c "pgbackrest --stanza=main check"

check пытается переключить WAL-сегмент и проверяет, что архивация доходит до репозитория — если archive_command настроен неверно, ошибка вылезет здесь, а не при реальном восстановлении, когда это дороже. Если сервер новый, установка самого PostgreSQL описана в статье про установку PostgreSQL на Ubuntu 24.04.

Первый бэкап обязательно полный:

docker compose exec db su postgres -c "pgbackrest --stanza=main --type=full backup"

Результат смотрим через info:

docker compose exec db su postgres -c "pgbackrest --stanza=main info"

Команда выводит список бэкапов, их тип, размер (реальный и после сжатия) и диапазон WAL, который потребуется для восстановления каждого из них.

Инкрементальные бэкапы и расписание

После первого full последующие бэкапы можно снимать как diff или incr. Практическая схема для большинства случаев: full раз в неделю, diff раз в сутки, incr — по потребности между ними (или не использовать вовсе, если хватает full+diff):

# полный бэкап
pgbackrest --stanza=main --type=full backup

# дифференциальный (изменения с последнего full)
pgbackrest --stanza=main --type=diff backup

# инкрементальный (изменения с последнего бэкапа любого типа)
pgbackrest --stanza=main --type=incr backup

Расписание — через cron внутри контейнера (уже установлен в образе):

# crontab.txt
0 2 * * 0  postgres  pgbackrest --stanza=main --type=full backup >> /var/log/pgbackrest/cron.log 2>&1
0 2 * * 1-6 postgres pgbackrest --stanza=main --type=diff backup >> /var/log/pgbackrest/cron.log 2>&1
0 */6 * * * postgres pgbackrest --stanza=main --type=incr backup >> /var/log/pgbackrest/cron.log 2>&1

Retention из pgbackrest.conf (repo1-retention-full=4) означает, что хранятся последние 4 full-бэкапа и все diff/incr, которые от них зависят — старые цепочки удаляются автоматически при следующем full. Если репозиторий растёт быстрее, чем ожидалось, первым делом стоит проверить объём WAL — частая причина этого разобрана в статье про рост размера WAL в PostgreSQL.

Хранение в S3-совместимом хранилище

Локальный volume на том же сервере — не полноценный офсайт-бэкап. Для правила «3-2-1» pgBackRest умеет писать сразу во второй репозиторий, например в S3-совместимое хранилище (в том числе self-hosted MinIO):

[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=4

repo2-type=s3
repo2-path=/pgbackrest
repo2-s3-bucket=pg-backups
repo2-s3-endpoint=s3.example.com
repo2-s3-region=us-east-1
repo2-retention-full=8

Ключи доступа для S3 передаются через переменные окружения PGBACKREST_REPO2_S3_KEY и PGBACKREST_REPO2_S3_KEY_SECRET, а не хранятся в конфиге — их стоит прокидывать через secrets:, аналогично паролю базы. С двумя репозиториями backup без --repo пишет в оба сразу; если готового S3 под рукой нет, поднять MinIO можно по нашему готовому compose-файлу MinIO.

Восстановление: полное и на точку во времени

Восстановление — самая важная проверка бэкапа, и делать её нужно заранее, а не при первом реальном инциденте. Полное восстановление на последний доступный момент:

docker compose stop db
docker compose run --rm db su postgres -c \
  "pgbackrest --stanza=main --delta restore"
docker compose up -d db

Флаг --delta заставляет pgBackRest сравнивать файлы в PGDATA с содержимым бэкапа и копировать только отличающиеся — это быстрее полного восстановления с нуля, если каталог данных частично сохранился.

Point-in-time recovery — восстановление на конкретный момент, например прямо перед ошибочным DELETE:

docker compose run --rm db su postgres -c \
  "pgbackrest --stanza=main --type=time \
   --target='2026-08-30 14:22:00+03' \
   --delta restore"

После restore PostgreSQL при старте сам доигрывает WAL до указанной точки и переключается в обычный режим. Общая логика такого восстановления, независимо от конкретного инструмента бэкапа, разобрана в статье про восстановление базы данных из бэкапа на практике — стоит хотя бы раз пройти эту процедуру на тестовом сервере до того, как она понадобится на боевом.

Мониторинг и типичные грабли

Из того, что стабильно всплывает в эксплуатации:

  • archive_command не настроен или настроен неверно — WAL копится в pg_wal и не архивируется, check сразу это покажет. Симптом на проде — растущий каталог pg_wal и падающая производительность диска.
  • Репозиторий не вынесен на отдельный диск. Если pgbackrest_repo и pgdata на одном физическом томе, отказ диска убивает и данные, и бэкапы одновременно.
  • process-max выше числа ядер. Бэкап не ускоряется, а замедляет саму базу конкуренцией за CPU.
  • Не проверяют бэкапы восстановлением. info показывает, что бэкап «есть», но не что он рабочий — единственная проверка это тестовое восстановление.
  • Права на каталоги. pgBackRest работает от пользователя postgres; если репозиторий смонтирован с правами root без chown, stanza-create падает с ошибкой доступа.

Для алертинга проще всего парсить pgbackrest --stanza=main info --output=json в скрипте мониторинга и проверять поле backup.error и время последнего успешного бэкапа — если оно старше ожидаемого интервала, это повод для алерта раньше, чем для реального восстановления.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли использовать pgBackRest без Docker, напрямую на сервере?

Да, это его основной сценарий использования, Docker здесь просто фиксирует версию и упрощает перенос конфигурации между серверами.

Работает ли это с репликацией PostgreSQL?

Да, pgBackRest поддерживает бэкап со standby-реплики, чтобы не нагружать primary — но настройка стенджа для реплики отличается; она описана в статье про настройку репликации PostgreSQL отдельно.

Что произойдёт, если контейнер с базой перезапустится во время бэкапа?

Бэкап прервётся некорректно, но pgBackRest не оставит повреждённый файл в репозитории как «валидный» — он не попадёт в список для восстановления через info.

Нужен ли pg_dump вообще, если есть pgBackRest?

Да, как дополнение — логический дамп удобен для переноса отдельной таблицы или миграции между мажорными версиями PostgreSQL, чего физический бэкап не умеет в принципе.

Сколько места нужно под репозиторий бэкапов?

Ориентировочно — объём базы плюс запас под глубину retention с учётом инкрементов; точный расчёт зависит от интенсивности изменений, стоит следить за трендом через info, а не считать один раз при настройке.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →