MinIO: объектное хранилище своими руками — от бакета до S3 API
Знакомая сцена. Приложению нужно хранить файлы пользователей: аватары, документы, фото. Первое решение — класть их на диск сервера, рядом с кодом. Работает ровно до первого деплоя: контейнер пересоздался, и все загруженные файлы исчезли. Добавили второй сервер — файл лежит на одном, а запрос пришёл на другой, и картинка не открывается.
Второе решение — облако, обычно AWS S3. Надёжно, но файлы уезжают во внешний сервис, появляется привязка к одному поставщику, набегает счёт за хранение и трафик, а для локальной разработки приходится либо платить, либо городить заглушки. Данные клиентов на чужих серверах — отдельная головная боль там, где это запрещено.
MinIO закрывает обе проблемы. Это объектное хранилище, которое вы запускаете сами — на своём сервере, в Docker, за одну команду. Снаружи оно говорит на языке AWS S3, поэтому любой инструмент и любая библиотека, умеющие работать с S3, работают с MinIO без переделок. Получаете надёжное хранилище файлов на своей инфраструктуре, без облачного счёта и без утечки данных наружу.
Разберём по шагам: что такое объектное хранилище, почему важна совместимость с S3, как запустить MinIO в Docker, как устроены бакеты и права и как хранить в нём файлы приложения.
MinIO запускают в Docker на Linux-сервере — основы того и другого разбирают в курсах «Docker» и по администрированию Linux на Хекслете.
Что такое MinIO
MinIO — объектное хранилище с открытым кодом. Его задача — хранить файлы и отдавать их по запросу, надёжно и в больших объёмах. От облачных хранилищ его отличает то, что вы разворачиваете его сами: на своём сервере, в своей сети, под своим контролем.
Главная особенность — совместимость с Amazon S3. S3 за годы стал негласным стандартом объектного хранения, и его протокол понимают сотни инструментов. MinIO повторяет этот протокол точь-в-точь. На практике это значит, что код, написанный под AWS S3, переключается на MinIO сменой одного адреса — и работает.
Где это применяют: хранение пользовательских загрузок в приложении, резервные копии, статика и медиа сайтов, наборы данных для машинного обучения, замена облачного S3 на своём железе и локальное хранилище для разработки. Везде, где нужно хранить много файлов и обращаться к ним по сети.
Объектное хранилище: чем оно отличается
Привычный способ хранить файлы — файловая система: папки внутри папок, путь до файла как адрес. Объектное хранилище устроено иначе, и разница принципиальна.
Файловая система | Объектное хранилище | |
|---|---|---|
Структура | Дерево папок | Плоский список объектов в бакетах |
Адрес файла | Путь: /home/user/avatar.png | Ключ объекта внутри бакета |
Доступ | Через файловую систему сервера | По сети, через HTTP-запрос |
Метаданные | Базовые: дата, размер | Произвольные: свои поля у объекта |
Масштаб | Ограничен диском | Растёт добавлением серверов |
Объект — это файл вместе с его метаданными и уникальным ключом. Бакет — контейнер для объектов, что-то вроде папки верхнего уровня. Внутри бакета структура плоская: настоящих вложенных папок нет, но ключ может выглядеть как путь — users/42/avatar.png. Это просто имя объекта, а не реальная иерархия, хотя выглядит привычно.

Зачем так сложнее. Плоская модель и доступ по сети дают то, чего не даёт диск: хранилище растёт добавлением серверов, файлы дублируются для надёжности, а приложение обращается к ним по одному и тому же адресу, сколько бы серверов за ним ни стояло. Файл больше не привязан к конкретной машине.
S3 API: почему совместимость решает всё
Это главный козырь MinIO, и его легко недооценить. S3 API — набор правил, по которым приложения общаются с хранилищем: как загрузить объект, как скачать, как перечислить содержимое бакета. AWS придумала его для своего облака, но он стал стандартом де-факто.

MinIO говорит на этом же языке. Последствия приятные:
Что вы используете с AWS S3 | С MinIO |
|---|---|
Библиотека для языка (boto3, aws-sdk) | Работает — меняется только адрес |
Консольная утилита (aws, s3cmd, rclone) | Работает с тем же адресом |
Инструменты резервного копирования | Видят MinIO как обычный S3 |
Код приложения под S3 | Переключается одной настройкой |
Практический вывод важен для архитектуры. Можно вести разработку на локальном MinIO, а в проде использовать облачный S3 — код один и тот же. Или начать с облака и переехать на своё хранилище, когда счёт станет заметным. Совместимость снимает привязку к поставщику: вы не заперты ни в облаке, ни в MinIO.
Запуск в Docker за одну команду
MinIO разворачивается как обычный контейнер. Одна команда поднимает и хранилище, и веб-консоль управления:
docker run -p 9000:9000 -p 9001:9001 \
-e MINIO_ROOT_USER=admin \
-e MINIO_ROOT_PASSWORD=password123 \
-v ~/minio-data:/data \
quay.io/minio/minio server /data --console-address ":9001"Разберём, что здесь происходит. Два порта: 9000 — это сам S3 API, к которому обращается код, а 9001 — веб-консоль для людей. Логин и пароль администратора задаются переменными. Том -v привязывает папку на сервере к хранилищу внутри контейнера — без него данные пропадут при пересоздании контейнера, что повторило бы исходную проблему.

После запуска открываете http://localhost:9001, входите под заданными логином и паролем и попадаете в консоль: создаёте бакеты, смотрите объекты, заводите ключи доступа. Всё то же самое делается и из командной строки, и из кода — консоль просто удобна для людей.
Бакеты, объекты и ключи доступа
Работа с MinIO строится на трёх вещах: бакеты, объекты и ключи.
Бакет — контейнер верхнего уровня. Под разные задачи заводят разные бакеты: avatars для аватаров, documents для документов, backups для копий. Имя бакета уникально в пределах хранилища.

Объект — файл внутри бакета со своим ключом. Ключ часто пишут как путь, чтобы группировать объекты по смыслу, хотя физически структура плоская:
бакет: uploads
объект: avatars/user_42.png
объект: avatars/user_77.png
объект: docs/contract_2026.pdfКлючи доступа — это пара «access key» и «secret key», аналог логина и пароля для программного доступа. Те же, что у AWS. Администраторский ключ задаётся при запуске, но в приложении используют не его, а отдельные ключи с ограниченными правами.
Права: что кому доступно
По умолчанию бакеты закрыты — без ключа к ним не обратиться. Доступ настраивают политиками. Бакет можно сделать публичным на чтение (тогда файлы открываются по прямой ссылке — удобно для статики сайта) или оставить приватным, и тогда к нему ходят только с ключом.
Режим бакета | Кто читает | Когда использовать |
|---|---|---|
Приватный | Только с ключом доступа | Документы, личные файлы — по умолчанию |
Публичный на чтение | Любой по прямой ссылке | Статика, картинки сайта, публичные медиа |
Не делайте бакет публичным «чтобы заработало». Самый быстрый способ показать файл — открыть бакет всему интернету, и так часто и делают сгоряча. Но если в бакете ��ежат документы пользователей, публичный доступ означает утечку всего разом. Личные файлы держат приватными, а отдают через временные подписанные ссылки — о них ниже.
Работа из кода
В приложении с MinIO работают через библиотеку. Поскольку хранилище S3-совместимое, годятся и родная библиотека MinIO, и любой клиент AWS S3 — достаточно указать адрес. Пример на JavaScript:
import { Client } from 'minio';
const minio = new Client({
endPoint: 'localhost',
port: 9000,
useSSL: false,
accessKey: 'admin',
secretKey: 'password123',
});
// создать бакет
await minio.makeBucket('uploads');
// загрузить файл
await minio.fPutObject('uploads', 'avatars/user_42.png', './avatar.png');
// скачать файл
await minio.fGetObject('uploads', 'avatars/user_42.png', './downloaded.png');Те же действия — создать бакет, положить объект, забрать объект, перечислить содержимое — есть в библиотеках для Python, Go, Java и других языков. Имена методов отличаются, идея одна.
Временные ссылки без раздачи ключей
Возникает вопрос: как дать пользователю скачать приватный файл, не отдавая ему секретный ключ. Ключ в браузере — это доступ ко всему хранилищу, его раздавать нельзя.

Ответ — presigned URL, временная подписанная ссылка. Приложение на своей стороне, с ключом, генерирует особую ссылку: она ведёт прямо к объекту в MinIO, действует ограниченное время и работает без ключа. Пользователь скачивает файл по ней напрямую, а ключ остаётся на сервере.
// ссылка на скачивание, живёт 5 минут
const url = await minio.presignedGetObject(
'uploads',
'avatars/user_42.png',
5 * 60
);
// эту ссылку безопасно отдать в браузерТот же приём работает и на загрузку: приложение выдаёт подписанную ссылку, и браузер заливает файл прямо в MinIO, минуя сервер приложения. Это снимает нагрузку — большие файлы не проходят через ваш бэкенд, а идут напрямую в хранилище. Ключ при этом ни разу не покидает сервер.
Надёжность и что MinIO умеет ещё
Хранилище файлов бессмысленно, если файлы из него пропадают. MinIO защищает данные на уровне самого хранилища. В распределённом режиме его запускают на нескольких серверах, и каждый объект хранится с избыточностью: часть дисков может выйти из строя, а файлы всё равно остаются целыми и доступными. Это называется erasure coding — данные раскладываются по дискам так, что восстанавливаются даже при потере нескольких из них. На одном контейнере для разработки это не нужно, но в проде именно так MinIO переживает отказ железа.
За пределами простого хранения есть набор возможностей, которые иначе пришлось бы собирать вручную.
Возможность | Зачем |
|---|---|
Версионирование | Хранить прошлые версии объекта — откатиться или восстановить удалённое |
Правила жизненного цикла | Автоматически удалять или архивировать старые объекты по сроку |
Репликация | Зеркалировать бакет на другой сервер или в другой регион |
Уведомления о событиях | Сообщать приложению о загрузке или удалении объекта |
Шифрование | Хранить объекты в зашифрованном виде |
Отдельно стоит упомянуть mc — консольный клиент MinIO. Он работает с хранилищем из командной строки: копирует файлы, настраивает политики, зеркалирует бакеты. Для администрирования и скриптов он удобнее, чем консоль в браузере.
Практика: хранилище файлов для приложения
Соберём хранение пользовательских загрузок с нуля.
Шаг 1. Запустить MinIO. Одна команда docker run из раздела выше — поднимутся хранилище и консоль.
Шаг 2. Создать бакет и ключ. В консоли на localhost:9001 создаёте бакет uploads и отдельный ключ доступа для приложения — не администраторский.
Шаг 3. Загружать файлы из кода. Приложение принимает файл от пользователя и кладёт в бакет под осмысленным ключом:
await minio.fPutObject(
'uploads',
`avatars/${userId}.png`, // ключ привязан к пользователю
uploadedFilePath
);Шаг 4. Отдавать файлы по временной ссылке. Когда нужно показать файл, приложение генерирует presigned URL и отдаёт его в браузер:
const url = await minio.presignedGetObject('uploads', `avatars/${userId}.png`, 300);
res.json({ avatarUrl: url });Готово. Файлы лежат в надёжном хранилище, переживают пересоздание контейнеров и не привязаны к конкретному серверу. Секретный ключ остался на бэкенде, а пользователь работает с файлами по временным ссылкам. Всё это — на вашей инфраструктуре, без облачного счёта, и тот же код позже переедет на облачный S3 без переписывания.
Антипаттерны
Хранить загрузки на диске сервера. С этого начиналась статья: файлы исчезают при деплое и не видны со второго сервера. Пользовательские файлы кладут в объектное хранилище, а не рядом с кодом приложения.
Использовать администраторский ключ в приложении. Корневой ключ даёт полный доступ ко всему хранилищу. Если он утечёт из приложения — потеряно всё. Для приложения заводят отдельный ключ с правами только на нужный бакет.
Делать приватный бакет публичным ради удобства. Публичный бакет с личными файлами — это утечка по прямой ссылке, без всякого взлома. Приватные данные отдают через временные подписанные ссылки, а не открытием бакета.
Класть файлы в базу данных как blob. База раздувается, копии замедляются, а отдача файлов нагружает СУБД. В базе хранят ключ объекта и метаданные, а сам файл — в объектном хранилище.
Запускать без привязки тома. Контейнер без -v хранит данные внутри себя — и теряет их при пересоздании. Данные хранилища всегда выносят на постоянный том на сервере.
FAQ
MinIO бесплатный?
Базовая версия с открытым кодом бесплатна и для разработки, и для коммерческих проектов. Платить нужно за сервер, на котором она работает, и за время на настройку и сопровождение. У компании есть платные тарифы с поддержкой и дополнительными возможностями для крупных установок.
Чем MinIO отличается от AWS S3?
S3 — облачный сервис Amazon: платите за хранение и трафик, данные на серверах Amazon. MinIO вы ставите сами на своём железе, платите только за инфраструктуру, данные остаются у вас. При этом протокол у них общий, поэтому код переносится между ними без переписывания. Облако проще на старте, MinIO выгоднее на масштабе и обязателен там, где данные нельзя отдавать наружу.
Нужно ли знать AWS, чтобы начать?
Нет. Достаточно понимать идею объектного хранилища — бакеты, объекты, ключи — и уметь запускать контейнеры в Docker. Знание AWS пригодится разве что для переноса кода между MinIO и облачным S3, но это та же самая библиотека.
Подходит ли MinIO для продакшена, а не только для разработки?
Да, MinIO рассчитан на боевую нагрузку. Для надёжности его разворачивают на нескольких серверах, где данные дублируются и переживают отказ дисков. Для разработки же хватает одного контейнера. Многие команды используют локальный MinIO в разработке и кластер MinIO в проде.
Что хранить в MinIO, а что в базе данных?
В MinIO — сами файлы: картинки, документы, видео, копии. В базе — структурированные данные и ссылка на объект: его бакет и ключ, плюс метаданные вроде имени и размера. Так база остаётся лёгкой, а файлы лежат там, где их удобно отдавать.
Можно ли использовать MinIO с любым языком?
Да. Раз хранилище совместимо с S3, подходят и официальные библиотеки MinIO, и клиенты AWS S3 для большинства языков: JavaScript, Python, Go, Java, C#. Принцип везде один: указать адрес и ключи, дальше работать с бакетами и объектами.






