Своё S3-хранилище на VPS: чем заменить MinIO
MinIO больше не развивают: репозиторий закрыт, свежие критические уязвимости остались без исправлений. Разбираемся, что ставить вместо него, и поднимаем рабочее S3-хранилище на одном сервере за полчаса.
Что случилось с MinIO
Несколько лет MinIO был ответом по умолчанию на вопрос «как поднять своё S3-хранилище». Один бинарник, веб-консоль, совместимость с Amazon S3 — его ставили под бэкапы, под файлы приложений, под Nextcloud. Дальше события шли так.
| Когда | Что произошло |
|---|---|
| Май 2025 | Из бесплатной версии вырезали панель управления. Осталась только страница со списком файлов: ни пользователей, ни политик, ни настроек. Всё это уехало в платный AIStor. |
| Октябрь 2025 | Последний выпуск сообщества — заплатка к уязвимости. Docker-образ для него так и не собрали: в инструкции написано «клонируйте исходники и соберите образ сами». |
| Февраль 2026 | В README появилась строчка заглавными буквами: этот репозиторий больше не поддерживается. |
| Апрель 2026 | Репозиторий заархивировали. Теперь он доступен только для чтения: ни коммитов, ни правок, ни новых выпусков. |
Само по себе «проект закрыли» ещё не повод бежать. Повод — то, что случилось дальше. Уязвимости в MinIO продолжают находить, а закрывать их некому.
Почему это срочно, а не «когда-нибудь»
За 2026 год в базе уязвимостей GitHub появилось семь записей по MinIO, две из них с уровнем «критический». У части записей в графе «исправлено в версии» стоит прочерк — исправления не выйдет никогда. Самая опасная, CVE-2026-41145, позволяет записать произвольный файл в любой бакет, зная только идентификатор ключа: секретный ключ и корректная подпись не нужны. В описании прямо сказано, что затронуты все установки, а в качестве примера ключа приводится стандартный minioadmin.
Есть и официальный путь: MinIO предлагает перейти на AIStor — их коммерческий продукт, у которого существует бесплатная редакция для одного сервера. Это рабочий вариант, если вы готовы жить в чужой экосистеме с закрытым исходным кодом и лицензионным ключом. Дальше в статье — про второй путь: уйти на живой открытый проект.
Зачем вообще держать S3 у себя
Если коротко: S3 — это способ хранить файлы, обращаясь к ним по сети как к обычному веб-адресу, а не через SSH и не через сетевую папку. Файл называется «объектом», папка верхнего уровня — «бакетом», а вместо логина и пароля используется пара «идентификатор ключа + секрет». Придумал схему Amazon, но её протокол повторили десятки других хранилищ, поэтому «S3-совместимый» сегодня значит «к нему подойдёт любая привычная программа».
Свой сервер вместо облачного берут обычно ради трёх вещей:
- Бэкапы. restic, Duplicati, Proxmox Backup — все умеют S3. Держать приёмник бэкапов у себя дешевле и быстрее, чем гонять их в облако.
- Файлы приложения. Загруженные пользователями картинки и документы лучше держать отдельно от кода: тогда приложение можно переставить на другой сервер, ничего не перенося руками.
- Раздача статики. Картинки, дистрибутивы, записи — то, что нужно отдавать наружу по ссылке и не считать каждый запрос.
Честно про деньги
Расхожее «своё дешевле в разы» — миф, если считать только гигабайты. Российские облака берут за стандартное объектное хранилище порядка 2 ₽ за гигабайт в месяц. Тариф-хранилище у VPS-провайдера — это примерно 300 ГБ диска и 2 ГБ памяти за 700 ₽ в месяц, то есть те же 2 ₽ за гигабайт. По чистой цене за объём паритет.
Разница появляется в трёх других строчках счёта. Исходящий трафик в облаке платный после небольшого бесплатного лимита, а у VPS он либо безлимитный, либо измеряется терабайтами. Запросы в облаке тарифицируются штуками — а restic при проверке репозитория делает их тысячами. Предсказуемость: у себя счёт фиксированный, и неудачный скрипт в цикле не превратится в неприятный сюрприз в конце месяца.
Простое правило
Файлы лежат и почти не читаются — облако удобнее и надёжнее. Файлы активно скачиваются, или запросов к ним очень много, или данные не должны покидать ваш периметр — своё хранилище выигрывает.
Чем заменить MinIO: три кандидата
Живых открытых проектов, которые действительно можно поставить вместо MinIO, сейчас три. Все три активно развиваются, но подходят для разного.
| Проект | Сильная сторона | Где не подойдёт |
|---|---|---|
| Garage 2.3AGPL-3.0 | Стабильный, крошечный по ресурсам, один бинарник без внешней базы. Сделан ровно под сценарий «пара своих серверов». | Нет версионирования объектов, нет Object Lock, нет привычных по AWS bucket policy — права выдаются ключам целиком на бакет. |
| RustFS 1.0-rcApache-2.0 | Самая близкая замена MinIO: похожая раскладка на диске, своя веб-консоль, версионирование. Разрешительная лицензия. | До сих пор release candidate, а распределённый режим и правила жизненного цикла помечены авторами как «в тестировании». В прод рано. |
| SeaweedFSApache-2.0 | Проверенный временем зверь для миллиардов мелких файлов, богатые возможности. | Три отдельных компонента плюс хранилище метаданных. Для одного сервера это заметно больше работы, чем пользы. |
Дальше в статье — Garage. Почему он
Для одного-двух серверов он объективно удобнее остальных: стабильный релиз, никакой внешней базы данных, меньше 20 МБ памяти в простое и — с версии 2.3, вышедшей 16 апреля 2026, — режим одного узла, в котором больше не нужно вручную раскладывать кластер командами. Проект делает французское объединение Deuxfleurs, которое само на нём и живёт. Если вам принципиально нужны версионирование или Object Lock — Garage не ваш вариант, возвращайтесь к таблице выше.
Что понадобится
- Сервер с Ubuntu 22.04 или 24.04. Программе хватит 1 ГБ памяти, но с учётом системы и Docker комфортнее 2 ГБ. Процессор роли не играет.
- Диск под данные. Сколько нужно хранить — столько и берите, плюс запас. Метаданные — это ещё примерно 2–3 КБ на объект: под миллион файлов заложите несколько гигабайт сверх самих данных.
- Поддомен, а лучше два.
s3.example.com— сюда будут стучаться программы; он нужен обязательно.files.example.com— отсюда файлы будут раздаваться наружу по обычной ссылке; если такой раздачи не планируется, второй поддомен можно не заводить и пропустить соответствующие шаги. Записи типа A ведут на IP сервера, wildcard не нужен. - Открытые порты 80 и 443. Больше ничего открывать не придётся: все порты Garage мы привяжем к
127.0.0.1, наружу их не видно.
Про диск есть тонкость. Garage хранит данные кусками по мегабайту в обычных файлах, а метаданные — в базе, к которой обращается на каждый запрос. Поэтому идеальная схема — быстрый NVMe под систему и метаданные, а под сами данные можно взять что подешевле. Если диск один, берите NVMe: на большом количестве мелких объектов разница ощутима.
Установка Garage
Ставим через Docker Compose: так проще обновляться и ничего не остаётся в системе, кроме одного каталога. Все команды выполняются от root или через sudo.
Шаг 1. Docker и каталог
curl -fsSL https://get.docker.com | sh
mkdir -p /opt/garage/meta /opt/garage/data
cd /opt/garagemeta — база с описанием объектов, data — сами файлы. Держите их рядом: так весь каталог /opt/garage целиком переносится на другой сервер.
Шаг 2. Файл настроек
Команда ниже создаёт конфиг и сама подставляет в него два случайных секрета — руками ничего править не надо. Замените только files.example.com на свой домен для раздачи файлов; если раздача не нужна, оставьте как есть — поле обязательное, но ни на что больше не влияет.
tee /opt/garage/garage.toml >/dev/null <<EOF
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "sqlite"
metadata_fsync = true
metadata_auto_snapshot_interval = "6h"
replication_factor = 1
rpc_bind_addr = "[::]:3901"
rpc_secret = "$(openssl rand -hex 32)"
[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
[s3_web]
bind_addr = "[::]:3902"
root_domain = ".files.example.com"
index = "index.html"
[admin]
api_bind_addr = "[::]:3903"
admin_token = "$(openssl rand -base64 32)"
EOF
chmod 600 /opt/garage/garage.tomlЧто здесь важно и почему именно так:
db_engine = "sqlite"— по умолчанию Garage использует другой движок, LMDB. Он быстрее, но авторы прямо предупреждают: после жёсткого выключения его база легко повреждается. На одном сервере второй копии нет, поэтому надёжность важнее скорости.metadata_fsync = true— по той же причине: заставляем базу честно сбрасывать записи на диск. На NVMe разницы в скорости мы не заметили, на медленном диске она будет.metadata_auto_snapshot_interval— раз в шесть часов Garage сам делает снимок базы метаданных и хранит два последних. Если база всё-таки повредится, будет откуда восстановиться.replication_factor = 1— одна копия, потому что сервер один. Это не опечатка, но и не защита от смерти диска: см. раздел про бэкапы ниже.root_domainв секции[s3_web]— поле обязательное, без него Garage просто не запустится. Как оно работает, разберём в разделе про раздачу файлов.
Шаг 3. Docker Compose
Вместе с хранилищем сразу поднимем веб-интерфейс — тот самый, которого MinIO лишился. Ему нужен пароль в зашифрованном виде, поэтому команда состоит из двух частей: сначала считаем хеш пароля, потом пишем файл. Замените ПРИДУМАЙТЕ-ПАРОЛЬ на свой пароль и выполните всё одним куском. Одинарную кавычку в пароле не используйте — она разорвёт команду.
HASH=$(docker run --rm httpd:alpine htpasswd -nbBC 10 admin 'ПРИДУМАЙТЕ-ПАРОЛЬ' | sed 's/[$]/$$/g')
tee /opt/garage/docker-compose.yml >/dev/null <<EOF
services:
garage:
image: dxflrs/garage:v2.3.0
container_name: garage
restart: unless-stopped
command: /garage server --single-node
volumes:
- ./garage.toml:/etc/garage.toml:ro
- ./meta:/var/lib/garage/meta
- ./data:/var/lib/garage/data
ports:
- "127.0.0.1:3900:3900"
- "127.0.0.1:3902:3902"
webui:
image: khairul169/garage-webui:1.1.0
container_name: garage-webui
restart: unless-stopped
depends_on:
- garage
environment:
API_BASE_URL: "http://garage:3903"
S3_ENDPOINT_URL: "http://garage:3900"
AUTH_USER_PASS: "$HASH"
volumes:
- ./garage.toml:/etc/garage.toml:ro
ports:
- "127.0.0.1:3909:3909"
EOFСразу проверьте, что пароль записался — строка должна выглядеть как admin:$$2y$$10$$…, а не быть пустой:
grep AUTH_USER_PASS /opt/garage/docker-compose.ymlЗачем в команде sed
Хеш пароля состоит из кусков, разделённых знаком доллара, а Docker Compose считает доллар началом переменной и молча съедает часть строки. Чтобы этого не случилось, каждый доллар удваивается — этим и занимается sed. В итоговом файле вы увидите $$2y$$10$$…, и это правильно: внутрь контейнера попадёт нормальный хеш с одинарными долларами. По этой же причине не пытайтесь вынести пароль в файл .env — Compose испортит его точно так же.
Шаг 4. Запуск
cd /opt/garage
docker compose up -d
docker compose exec garage /garage statusЧерез несколько секунд должна появиться таблица с одной строкой:
==== HEALTHY NODES ====
ID Hostname Address Tags Zone Capacity Version
94200e32f868eb51 bfd5952df2b1 172.22.0.2:3901 [default] dc1 457.4 GiB v2.3.0Если вы читали старые руководства по Garage и ждёте команд layout assign и layout apply — их больше не нужно. Флаг --single-node, появившийся в версии 2.3, сам создаёт раскладку и берёт под данные весь доступный объём диска.
Первый бакет и ключ доступа
Бакет — это папка верхнего уровня. Ключ — пара «идентификатор + секрет», которую вы дадите программе. Права выдаются отдельно: создали ключ, создали бакет, разрешили одному работать с другим.
cd /opt/garage
docker compose exec garage /garage bucket create backups
docker compose exec garage /garage key create main-key
docker compose exec garage /garage bucket allow \
--read --write --owner backups --key main-keyВторая команда напечатает то, что нужно сохранить:
Key ID: GK89ff9894698336a68ea8f080
Secret key: 43886f91d7c2727ace90129018540e47e62d6e84818d5143f382cbc6903555e7Если секрет потерялся — не страшно, его можно посмотреть снова командой docker compose exec garage /garage key info main-key --show-secret. Дальше в статье мы везде используем этот один ключ, чтобы не запутаться. В реальной работе заведите отдельный ключ на каждое приложение — команды те же, меняются только имена: тогда скомпрометированный сервер с бэкапами не даст доступ к бакету с картинками сайта.
Если приложение хочет создавать бакеты само
По умолчанию ключ этого не умеет — и это правильно. Но некоторые программы создают бакет сами при первом запуске и без такого права падают с ошибкой. Разрешить: docker compose exec garage /garage key allow --create-bucket main-key.
Полезно сразу ограничить размер бакета, чтобы одно приложение не заняло весь диск:
docker compose exec garage /garage bucket set-quotas backups --max-size 200GИ сразу про автоудаление: Garage понимает стандартное правило жизненного цикла S3 — например, «чистить папку logs/ раз в 30 дней». Команда выполняется уже с клиентской машины, которую мы настроим ниже, так что просто вернитесь к ней позже.
tee lifecycle.json >/dev/null <<'EOF'
{
"Rules": [
{
"ID": "delete-old-logs",
"Filter": { "Prefix": "logs/" },
"Status": "Enabled",
"Expiration": { "Days": 30 }
}
]
}
EOF
aws --profile garage s3api put-bucket-lifecycle-configuration \
--bucket backups --lifecycle-configuration file://lifecycle.jsonДомен, Nginx и HTTPS
Сейчас хранилище слушает только на самом сервере. Чтобы к нему могли обращаться другие машины, поставим перед ним Nginx. Заодно он выдаст HTTPS — без него подключаться к хранилищу через интернет нельзя, ключи уйдут открытым текстом.
apt update
apt install -y nginx certbot python3-certbot-nginx dnsutilsПрежде чем идти дальше, убедитесь, что обе A-записи уже ведут на сервер — иначе certbot не сможет выдать сертификат:
dig +short s3.example.com @8.8.8.8
dig +short files.example.com @8.8.8.8server {
listen 80;
server_name s3.example.com;
# Файлы в хранилище бывают большими — снимаем лимит Nginx
client_max_body_size 0;
proxy_request_buffering off;
proxy_buffering off;
location / {
proxy_pass http://127.0.0.1:3900;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}
server {
listen 80;
server_name files.example.com;
location / {
proxy_pass http://127.0.0.1:3902;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}ln -s /etc/nginx/sites-available/garage /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
certbot --nginx -d s3.example.com
certbot --nginx -d files.example.comCertbot сам допишет блоки для HTTPS и настроит обновление сертификатов.
Две строчки, из-за которых чаще всего ничего не работает
proxy_set_header Host $http_host; — а не привычный $host. Клиент подписывает свой запрос вместе с заголовком Host, и если Nginx передаст его хоть немного изменённым, хранилище ответит Invalid signature. Мы это проверили: с $host запрос на нестандартном порту отваливается, с $http_host проходит.
client_max_body_size 0; — по умолчанию Nginx не пропускает тела запросов больше мегабайта. Без этой строки мелкие файлы зальются, а на большом вы получите ошибку 413 Content Too Large — тоже проверено.
Подключаем клиентов
Всё дальнейшее делается уже не на сервере с хранилищем, а на той машине, откуда вы будете класть файлы.
aws-cli — для ручной работы
Ставим официальную вторую версию: она умеет запоминать адрес хранилища в профиле, и тогда не придётся писать --endpoint-url в каждой команде.
sudo apt install -y unzip curl
curl -fsSL https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -o /tmp/awscliv2.zip
unzip -q /tmp/awscliv2.zip -d /tmp
sudo /tmp/aws/install
aws --versionmkdir -p ~/.aws
tee ~/.aws/credentials >/dev/null <<'EOF'
[garage]
aws_access_key_id = GK89ff9894698336a68ea8f080
aws_secret_access_key = 43886f91d7c2727ace90129018540e47e62d6e84818d5143f382cbc6903555e7
EOF
tee ~/.aws/config >/dev/null <<'EOF'
[profile garage]
region = garage
endpoint_url = https://s3.example.com
EOF
chmod 600 ~/.aws/credentialsaws --profile garage s3 ls
aws --profile garage s3 cp report.pdf s3://backups/
aws --profile garage s3 ls s3://backups/Пригодится ещё одна команда: aws --profile garage s3 presign s3://backups/report.pdf --expires-in 3600 выдаёт ссылку, по которой файл можно скачать без всяких ключей в течение часа. Удобно, чтобы разово отправить кому-то большой файл.
rclone — для синхронизации папок
curl https://rclone.org/install.sh | sudo bash
mkdir -p ~/.config/rclone
tee ~/.config/rclone/rclone.conf >/dev/null <<'EOF'
[garage]
type = s3
provider = Other
access_key_id = GK89ff9894698336a68ea8f080
secret_access_key = 43886f91d7c2727ace90129018540e47e62d6e84818d5143f382cbc6903555e7
endpoint = https://s3.example.com
region = garage
EOF
chmod 600 ~/.config/rclone/rclone.confВ списке провайдеров rclone отдельного пункта для Garage нет — provider = Other это правильный выбор, проверено на rclone 1.75.
rclone copy /var/www/uploads garage:backups/uploads --progress
rclone ls garage:backupsrestic — бэкапы
Самый частый сценарий для своего S3. Как ставить и настраивать restic, подробно разобрано в нашей статье про бэкапы; здесь — только то, что касается подключения к Garage.
export AWS_ACCESS_KEY_ID=GK89ff9894698336a68ea8f080
export AWS_SECRET_ACCESS_KEY=43886f91d7c2727ace90129018540e47e62d6e84818d5143f382cbc6903555e7
export RESTIC_REPOSITORY="s3:https://s3.example.com/backups/restic"
export RESTIC_PASSWORD="длинный-пароль-от-репозитория"
restic init
restic backup /var/www /etc
restic snapshots
restic checkОбратите внимание на адрес репозитория: s3:, затем полный адрес с https://, затем имя бакета и папка внутри него. Мы прогнали весь цикл — от init до check — на Garage 2.3 и restic 0.19: ошибок нет.
Про защиту бэкапов от шифровальщика
Схема «ключ может только дописывать, но не удалять» в Garage невозможна: права бывают только на чтение, на запись и владение, а запись включает удаление. Если сервер, который делает бэкапы, взломают, его ключом можно стереть и сам архив. Единственная надёжная защита здесь — вторая копия репозитория в другом месте, куда у этого сервера доступа нет.
Веб-интерфейс
Контейнер webui мы уже подняли — он показывает состояние узла, список бакетов и ключей и умеет ходить по файлам, загружать и скачивать их. Ровно то, чего лишили пользователей MinIO.
Наружу мы его не выставляем: панель управления хранилищем в открытом интернете — лишний риск, а нужна она изредка. Открываем через SSH-туннель со своего компьютера:
ssh -L 3909:127.0.0.1:3909 root@s3.example.comПока это подключение открыто, панель доступна по адресу http://127.0.0.1:3909 в браузере. Логин — admin, пароль — тот, что вы задали при создании docker-compose.yml.
Интерфейс делает не команда Garage, а отдельный разработчик, и обновляется он редко. Мы проверили: версия 1.1.0 корректно работает с Garage 2.3 — статус узла, бакеты, ключи и файловый браузер на месте. Но относитесь к нему как к удобному дополнению: всё то же самое всегда доступно командами.
Раздача файлов наружу по обычной ссылке
До сих пор всё требовало ключей. Но часто нужно противоположное: чтобы картинка с сайта или дистрибутив открывались у любого по прямой ссылке. Для этого у Garage есть отдельный режим и отдельный порт — тот самый 3902, который мы уже прокинули на files.example.com.
Ключевой трюк: бакет должен называться ровно так же, как домен. Тогда Garage сам понимает, что отдавать, и никакие wildcard-записи в DNS не нужны.
cd /opt/garage
docker compose exec garage /garage bucket create files.example.com
docker compose exec garage /garage bucket allow \
--read --write --owner files.example.com --key main-key
docker compose exec garage /garage bucket website --allow files.example.comaws --profile garage s3 cp logo.png s3://files.example.com/logo.png
curl -I https://files.example.com/logo.pngГотово: файл доступен по адресу https://files.example.com/logo.png всем без ключей. Если положить в бакет index.html, он откроется на корне домена — так на своём хранилище можно разместить и небольшой статический сайт.
Публичный — значит публичный
После команды bucket website --allow любой файл в этом бакете может скачать кто угодно, зная имя. Не смешивайте в одном бакете публичные картинки и рабочие документы — заведите под раздачу отдельный бакет, как мы и сделали.
Переезд с MinIO
Данные перевозит rclone. Опишите оба хранилища в его конфиге и синхронизируйте бакет в бакет — со стороны MinIO нужен только ключ с правом чтения.
[minio]
type = s3
provider = Minio
access_key_id = minioadmin
secret_access_key = СТАРЫЙ-СЕКРЕТ
endpoint = https://old-s3.example.com
region = us-east-1cd /opt/garage
docker compose exec garage /garage bucket create photos
docker compose exec garage /garage bucket allow \
--read --write --owner photos --key main-key
rclone sync minio:photos garage:photos --progress --transfers 8 --checkers 16
rclone check minio:photos garage:photosrclone check в конце сверяет, что всё доехало. Переключайте приложения на новый адрес только после того, как проверка прошла без расхождений, а старое хранилище погасите не сразу — пусть постоит выключенным пару недель.
Что при переезде поменяется в настройках приложений: адрес хранилища, ключи и регион — у Garage в нашем конфиге он называется garage, а MinIO по умолчанию отдаёт us-east-1. Регион Garage проверяет строго: со старым значением запрос отвалится с ошибкой AuthorizationHeaderMalformed … unexpected scope.
Бэкап самого хранилища
Повторим главное: сервер один, копия данных одна. Сгорел диск — сгорело всё. Поэтому хранилище само нуждается в бэкапе, и делать его правильнее не копированием каталога, а через S3 — второй командой rclone в другое место.
rclone sync garage:photos remote2:photos --progress
rclone sync garage:backups remote2:backups --progressremote2 здесь — это любое второе место, описанное в том же rclone.conf: второй сервер с Garage, российское облачное хранилище, Backblaze или даже внешний диск дома. Главное, чтобы это была другая машина: копия на том же сервере от смерти диска не спасёт.
Если всё же хотите копировать каталог целиком — обязательно останавливайте контейнер, иначе рискуете снять базу метаданных в момент записи. Либо снимайте её штатной командой, которая делает согласованный снимок на работающем сервере:
cd /opt/garage
docker compose exec garage /garage meta snapshot
ls meta/snapshotsАвтоматически такой снимок делается каждые шесть часов — это включено в нашем конфиге. Garage хранит два последних и старые удаляет сам. Учтите, что снимок — это полная копия файла базы, так что место под каталог meta закладывайте с запасом.
Как обновляться
Версия зафиксирована в docker-compose.yml намеренно: так обновление происходит тогда, когда вы этого захотели, а не когда контейнер сам перезапустился. Порядок такой: сначала снимок метаданных, потом смена версии.
cd /opt/garage
docker compose exec garage /garage meta snapshot
# заменить v2.3.0 на нужную версию в docker-compose.yml
nano docker-compose.yml
docker compose pull
docker compose up -d
docker compose exec garage /garage statusПеред сменой первой цифры в версии (скажем, с 2.x на 3.0) обязательно загляните в заметки к выпуску: у Garage такие переходы иногда требуют отдельной процедуры для метаданных. Внутри одной ветки обновление проходит простой заменой образа.
Когда своё S3 — плохая идея
Это единственная копия чего-то важного
Облачные хранилища держат три копии в разных стойках и дают гарантии сохранности. Один VPS не даёт ничего. Если данные существуют в одном экземпляре и их потеря недопустима — либо облако, либо второй сервер, третьего варианта нет.
Нужны версии файлов или неудаляемые объекты
Версионирования и Object Lock в Garage нет. Если ваш сценарий — «откатиться на вчерашнюю версию файла» или «архив нельзя стереть 30 дней», ищите другое решение.
Файлы должны быстро открываться по всему миру
Ваше хранилище стоит в одной точке, и канал сервера — потолок для всех сразу. Раздавать тяжёлое видео тысяче человек с одного VPS не выйдет: это задача для CDN.
Некому следить за сервером
Своё хранилище — это ещё один сервер, который надо обновлять, мониторить и бэкапить. Если этим никто не будет заниматься, оно превратится в такую же заброшенную установку MinIO, с которой всё и началось.
Частые проблемы
Контейнер не стартует, в логах «TOML parse error»
Garage строго проверяет конфиг и падает, если чего-то не хватает. Самый частый случай — секция [s3_web] без строки root_domain: без неё сервер не запустится, даже если раздача файлов вам не нужна. Смотрите точное место ошибки: docker compose logs garage | tail -20 — Garage печатает номер строки. И учтите, что лишние параметры он не ругает, а молча игнорирует: опечатка в имени параметра ошибки не вызовет, просто настройка не применится.
Клиент отвечает «Forbidden: Invalid signature»
Подпись запроса не сошлась, и причин ровно две. Первая — опечатка в секретном ключе; сверьте его командой docker compose exec garage /garage key info main-key --show-secret. Вторая — в Nginx стоит proxy_set_header Host $host; вместо $http_host. Соседние ошибки читаются буквально и лечатся сами собой: No such key: GK… — не тот идентификатор ключа, а AuthorizationHeaderMalformed … unexpected scope — не совпал регион, в клиенте должно стоять то же значение, что в s3_region вашего конфига (у нас это garage).
Мелкие файлы заливаются, большие обрываются с ошибкой 413
Это Nginx, а не хранилище. Большой файл клиент режет на куски, и первый же кусок упирается в стандартный лимит в один мегабайт. Лечится строкой client_max_body_size 0; внутри блока server и командой systemctl reload nginx.
Приложение не видит бакет и требует адрес вида bucket.s3.example.com
Есть два способа адресации: s3.example.com/backups и backups.s3.example.com. Первый работает в нашей конфигурации сразу, и aws-cli, rclone и restic используют именно его. Если приложение умеет только второй, у него обычно есть галочка вроде «path-style addressing» — включите её. Если такой галочки нет, придётся добавить в DNS wildcard-запись *.s3.example.com и строку root_domain = ".s3.example.com" в секцию [s3_api].
Панель не пускает с правильным паролем
Почти наверняка потерялась часть хеша. Посмотрите файл: в docker-compose.yml строка должна выглядеть как AUTH_USER_PASS: "admin:$$2y$$10$$…" — с удвоенными долларами. Проверить, что дошло до контейнера, можно так: docker inspect garage-webui | grep AUTH_USER_PASS — здесь долларов должно быть уже по одному, и строка не должна быть обрезана. Если обрезана — перегенерируйте её командой из шага 3 и выполните docker compose up -d.
Коротко: порядок действий
- Сервер с 1–2 ГБ памяти и нужным объёмом диска, две A-записи на его IP
- Docker из
get.docker.com, каталоги/opt/garage/metaи/opt/garage/data garage.tomlодной командой — секреты подставятся самиdocker-compose.ymlс Garage и веб-панелью, пароль черезhtpasswd | seddocker compose up -d, проверитьgarage status- Бакет, ключ, права: три команды
- Nginx с
$http_hostиclient_max_body_size 0, затемcertbot --nginx - Профиль в aws-cli или rclone, первая загрузка файла
- Публичный бакет с именем, равным домену, и
bucket website --allow - Настроить выгрузку копии во второе место — сервер один
Не подошло — docker compose down в /opt/garage всё останавливает, а данные остаются в каталогах meta и data. Удалить совсем — стереть /opt/garage целиком.
Хранилищу важны две вещи: объём диска и его скорость. Метаданные Garage любят NVMe
VPS с NVMe-дисками →