restic в Docker Compose: готовый файл
Если бэкап сервера сейчас держится на самописном bash-скрипте с закодированным паролем внутри и cron-строкой, которую страшно трогать, — знакомая ситуация. Restic решает эту задачу иначе: дедупликация «из коробки», шифрование на клиенте и один бинарник, который одинаково работает с локальным диском, SFTP и S3. Ниже — рабочий docker-compose.yml для всех трёх бэкендов и то, как заставить контейнерный restic бэкапиться по расписанию, а не только руками.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Почему restic и почему в контейнере
Restic — это дедуплицирующий бэкап-инструмент: файлы режутся на чанки переменного размера по содержимому (content-defined chunking), и одинаковые куски данных между снимками хранятся один раз. На практике это значит, что после первого полного бэкапа последующие снимки занимают в хранилище заметно меньше места, чем сами данные — точный процент экономии зависит от того, насколько данные между снимками похожи, конкретных цифр без замера на своих данных давать не буду.
Шифрование в restic — на стороне клиента, AES-256, и происходит до отправки данных в хранилище. Это значит, что бэкенду (диску, SFTP-серверу, S3-бакету) не обязательно доверять: он видит только зашифрованные блобы. Ключ шифрования отдельный от бэкенда, и без пароля репозиторий не открывается никаким штатным способом — это разобрано подробнее в статье про частые ошибки бэкапа с шифрованием.
Зачем заворачивать это в Docker Compose, если restic и так один бинарник:
- Версия фиксирована.
image: restic/restic:0.16.4— и на новом сервере поднимается ровно та же версия, а не то, что подтянулaptна момент установки. - Конфигурация лежит рядом с приложением. Один
docker-compose.ymlдля сервиса и его бэкапа, без отдельного скрипта где-то в/opt. - Секреты не разбросаны по shell-истории. Пароль репозитория и ключи бэкенда идут через
secrets:, а не черезexportв скрипте, который кто-то потом закоммитит в git по ошибке.
Минус тоже есть: restic — не демон, он завершает работу после каждой команды. Контейнер, который просто «крутится», тут не подойдёт по умолчанию — про это ниже, в разделе про расписание.
Готовый docker-compose.yml: локальный диск
Самый простой вариант — бэкап на отдельный смонтированный диск или раздел этого же сервера. Он не заменяет полноценный офсайт-бэкап (диск может умереть вместе с сервером), но годится как первый шаг или как одна из копий по правилу «3-2-1».
services:
restic:
image: restic/restic:latest
container_name: restic-backup
environment:
RESTIC_REPOSITORY: /repo
RESTIC_PASSWORD_FILE: /run/secrets/restic_password
volumes:
- /srv/app/data:/data:ro
- /mnt/backup-disk/restic-repo:/repo
- restic-cache:/root/.cache/restic
secrets:
- restic_password
command: backup /data
restart: "no"
secrets:
restic_password:
file: ./secrets/restic_password.txt
volumes:
restic-cache:
Данные монтируются в /data только на чтение — контейнеру-бэкаперу незачем иметь право что-то там менять. Каталог /root/.cache/restic вынесен в отдельный volume: без него restic будет заново скачивать индексы репозитория при каждом запуске контейнера, что заметно замедляет и локальный, и особенно удалённый бэкенд.
Репозиторий нужно инициализировать один раз до первого бэкапа:
docker compose run --rm restic init
После этого docker compose run --rm restic (с командой из файла или переопределённой) выполняет бэкап. restart: "no" явный, хотя это и значение по умолчанию — так понятнее, что сервис не должен «висеть», это одноразовая задача, а не долгоживущий процесс.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверБэкенд S3 и S3-совместимые хранилища
Меняется только RESTIC_REPOSITORY и способ передать доступ к объектному хранилищу. Restic понимает как AWS S3, так и совместимые с S3 API хранилища (MinIO, Backblaze B2 через совместимый эндпоинт и другие) — для этого в адрес репозитория подставляется свой хост.
services:
restic:
image: restic/restic:latest
container_name: restic-backup
environment:
RESTIC_REPOSITORY: s3:https://s3.eu-west-1.amazonaws.com/my-backup-bucket/app-data
RESTIC_PASSWORD_FILE: /run/secrets/restic_password
AWS_SHARED_CREDENTIALS_FILE: /run/secrets/aws_credentials
volumes:
- /srv/app/data:/data:ro
- restic-cache:/root/.cache/restic
secrets:
- restic_password
- aws_credentials
command: backup /data
restart: "no"
secrets:
restic_password:
file: ./secrets/restic_password.txt
aws_credentials:
file: ./secrets/aws_credentials.ini
volumes:
restic-cache:
Файл aws_credentials.ini — обычный формат credentials-файла AWS SDK:
[default]
aws_access_key_id = ВАШ_КЛЮЧ
aws_secret_access_key = ВАШ_СЕКРЕТ
Для S3-совместимого хранилища (например, MinIO на своём сервере) репозиторий выглядит так же, только с адресом своего эндпоинта: s3:https://minio.example.com/backup-bucket/app-data. Если MinIO поднят без валидного TLS-сертификата, потребуется дополнительно указать RESTIC_CACERT с путём до CA-сертификата — иначе restic откажется соединяться по HTTPS с самоподписанным сертификатом.
Про то, зачем вообще прятать такие ключи через secrets:, а не через environment: в открытом виде, подробно — в статье Docker secrets: управление паролями.
Бэкенд SFTP: свой сервер как хранилище
Если под бэкапы арендован отдельный сервер в другой локации — это, пожалуй, самый прозрачный вариант: данные лежат в обычных файлах на диске у вас же, только на другой машине, и разносятся физически от оригинала.
services:
restic:
image: restic/restic:latest
container_name: restic-backup
environment:
RESTIC_REPOSITORY: sftp:backup@203.0.113.10:/mnt/restic-repo
RESTIC_PASSWORD_FILE: /run/secrets/restic_password
volumes:
- /srv/app/data:/data:ro
- restic-cache:/root/.cache/restic
- ~/.ssh/id_ed25519_backup:/root/.ssh/id_ed25519:ro
- ~/.ssh/known_hosts:/root/.ssh/known_hosts:ro
secrets:
- restic_password
command: backup /data
restart: "no"
secrets:
restic_password:
file: ./secrets/restic_password.txt
volumes:
restic-cache:
Бэкенд SFTP у restic работает через реальный sftp-клиент (часть openssh-client), поэтому важны две вещи: приватный ключ должен быть смонтирован с правильными правами (если на хосте у файла ключа права шире, чем 600, ssh может отказаться его использовать), а known_hosts — уже содержать отпечаток удалённого сервера, иначе первое подключение из неинтерактивного контейнера зависнет на вопросе о доверии хосту. Отпечаток проще всего добавить заранее командой ssh-keyscan 203.0.113.10 >> ~/.ssh/known_hosts на хосте.
Ключ SSH стоит генерировать отдельный, только для бэкапа, с ограничением на нужный каталог через command= в authorized_keys — тогда даже утечка ключа не даст доступа ко всему серверу-хранилищу.
Расписание: backup и forget без демона в контейнере
Restic не остаётся жить после команды, поэтому restart: unless-stopped на сервисе выше ничего не даст — контейнер просто будет постоянно перезапускаться и сразу завершаться. Рабочих вариантов два.
Проще и надёжнее — cron на хосте, который дёргает docker compose run:
0 3 * * * cd /srv/app && docker compose run --rm restic backup /data >> /var/log/restic-backup.log 2>&1
30 3 * * 0 cd /srv/app && docker compose run --rm restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune >> /var/log/restic-forget.log 2>&1
Ротация через forget с флагом --prune обязательна отдельной задачей: без неё репозиторий растёт бесконечно, даже с дедупликацией — снимки просто помечаются старыми, но место физически не освобождается, пока не пройдёт prune.
Если хочется, чтобы расписание тоже жило в Docker — добавьте ofelia, планировщик задач для контейнеров. Держите restic живым как idle-контейнер (entrypoint: sleep infinity вместо команды бэкапа) и запускайте в нём команды по docker exec через ofelia:
services:
restic:
image: restic/restic:latest
container_name: restic
entrypoint: sleep infinity
environment:
RESTIC_REPOSITORY: /repo
RESTIC_PASSWORD_FILE: /run/secrets/restic_password
volumes:
- /srv/app/data:/data:ro
- /mnt/backup-disk/restic-repo:/repo
- restic-cache:/root/.cache/restic
secrets:
- restic_password
restart: unless-stopped
ofelia:
image: mcuadros/ofelia:latest
depends_on:
- restic
command: daemon --docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
labels:
ofelia.job-exec.restic-backup.schedule: "0 3 * * *"
ofelia.job-exec.restic-backup.container: restic
ofelia.job-exec.restic-backup.command: "restic backup /data"
ofelia.job-exec.restic-forget.schedule: "30 3 * * 0"
ofelia.job-exec.restic-forget.container: restic
ofelia.job-exec.restic-forget.command: "restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune"
restart: unless-stopped
secrets:
restic_password:
file: ./secrets/restic_password.txt
volumes:
restic-cache:
sleep infinity — не элегантно, но честно: это способ держать контейнер живым как цель для docker exec, сам restic по-прежнему не демон. Перед использованием сверьтесь с документацией ofelia — параметры job-exec могут отличаться между версиями образа.
Сравнение бэкендов по тому, что нужно контейнеру:
| Бэкенд | RESTIC_REPOSITORY | Что нужно контейнеру | Когда уместен |
|---|---|---|---|
| Локальный диск | /repo | volume с точкой монтирования отдельного диска | Первый шаг или одна из копий правила 3-2-1, не единственная |
| SFTP | sftp:user@host:/path | приватный ключ, готовый known_hosts | Есть свой второй сервер в другой локации, нужен полный контроль над хранилищем |
| S3 / совместимое | s3:https://endpoint/bucket/path | credentials-файл AWS SDK | Managed-хранилище без администрирования ещё одного сервера |
Восстановление и проверка бэкапа
Бэкап, который никогда не проверялся на восстановление, — это не бэкап, а надежда. Три команды, которые стоит гонять регулярно, а не только в момент катастрофы:
docker compose run --rm restic snapshots
docker compose run --rm restic check
docker compose run --rm -v /tmp/restore-test:/restore restic restore latest --target /restore
snapshots показывает, что копии вообще создаются и с ожидаемой периодичностью. check проверяет целостность репозитория на уровне метаданных и (с флагом --read-data-subset время от времени) целостность самих данных. Тестовое восстановление в отдельный каталог — единственный способ по-настоящему убедиться, что из репозитория можно достать рабочие файлы, а не повреждённые фрагменты. Общий разбор типовых проблем с восстановлением и потерянными паролями — в статье частые ошибки бэкапа с шифрованием, там же — почему нельзя держать репозиторий на том же диске, что и данные.
Если бэкапится не файловая система приложения, а том Docker с базой данных, восстановление устроено чуть иначе — сначала останавливается сервис, потом заменяется содержимое volume, и здесь есть свои грабли с правами и активными подключениями к базе, они разобраны в статье бэкап Docker volume: частые ошибки и решения.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Restic сам создаёт репозиторий при первом бэкапе?
Нет, нужно инициализировать явно командой restic init (в контейнере — docker compose run --rm restic init) до первого backup. Иначе backup завершится ошибкой, что репозиторий не существует.
Можно ли использовать один контейнер для нескольких источников данных?
Да, проще всего — отдельные сервисы в одном compose-файле с разными volumes: и разными путями в RESTIC_REPOSITORY, чтобы снимки разных приложений не перемешивались.
Что будет, если контейнер с бэкапом упадёт посреди работы?
Restic оставит блокировку на репозитории — следующий запуск откажется работать. Если вы уверены, что активной операции нет, блокировка снимается командой restic unlock.
Нужен ли восстановлению тот же образ restic, что делал бэкап?
Формат репозитория обратно совместим между минорными версиями, но безопаснее фиксировать тег (restic/restic:0.16.4, а не latest) — тогда версия предсказуема.
Кеш-том обязателен?
Нет, но без него каждый запуск заново тянет индексы репозитория с бэкенда — для S3 и SFTP это заметно увеличивает время и трафик, особенно на репозитории с большим числом снимков.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →