Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

Как поднять свой S3-совместимый объектный склад на MinIO для staging

Дата публикации: 25-06-2026 15:23:00

Разворачиваем MinIO на VPS, настраиваем HTTPS через Traefik и presigned URL для загрузки файлов. Экономим на облачном S3 на этапе разработки.— Читать дальше «Как поднять свой S3-совместимый объектный склад на MinIO для staging»

Основное содержимое страницы с новостью.

Если в приложении есть загрузка файлов — аватары, документы, отчёты, записи звонков — каждый тестовый файл на staging утекает в облачный счёт. AWS S3, Cloudflare R2 и Yandex Object Storage берут деньги за хранение и трафик, а в staging это чистый перерасход: тут нет SLA, зато полно битых загрузок, ночных сбросов базы и файлов, которые никто не удаляет.

Выход — поднять собственное S3-совместимое хранилище на том же VPS, где крутится staging. MinIO реализует API Amazon S3, понимает те же SDK и presigned URL, но стоит ровно столько, сколько стоит диск сервера. Один и тот же код приложения работает и с MinIO на dev, и с R2 в проде — меняются только переменные окружения.

MinIO — это open-source сервер объектного хранилища, написанный на Go. Он поддерживает основные операции S3: бакеты, объекты, multipart upload, versioning, lifecycle, шифрование SSE-S3, CORS и IAM-политики. Для приложения он выглядит как обычный S3-эндпоинт, поэтому подходит почти любой SDK: AWS SDK, boto3, minio-js, aws-sdk-go.

На staging важно не столько масштабирование, сколько идентичность поведения продакшена. Если в проде R2 или S3, а в staging — локальная файловая система, вы тестируете не тот код. MinIO закрывает этот разрыв: тот же PutObjectCommand, те же presigned URL, те же ошибки SignatureDoesNotMatch.

  • MinIO — полноценный S3-совместимый сервер, который можно развернуть в Docker на VPS за 10–15 минут.
  • Для staging это экономия на хранении тестовых файлов и единый код с продакшеном.
  • HTTPS и домен лучше отдавать reverse proxy — Traefik или NGINX — с Let’s Encrypt.
  • Presigned URL позволяют загружать и скачивать файлы напрямую из браузера, не проксируя байты через бэкенд.
  • Root-ключи MinIO нельзя отдавать приложению: создавайте отдельного пользователя с IAM-политикой только на нужный бакет.

Архитектура: прод против staging

В продакшене обычно используется управляемое хранилище: AWS S3, Cloudflare R2, Yandex Object Storage, Hetzner Object Storage. Там работают репликация, резервное копирование и чужой дежурный. В staging достаточно одного MinIO-контейнера на сервере с регулярным зеркалированием в дешёвое холодное хранилище.

Приложение не знает, с кем оно говорит: код инициализации клиента одинаков. Разница только в переменных окружения: эндпоинте, регионе, ключах и флаге forcePathStyle, который нужен большинству S3-совместимых сервисов, включая MinIO и R2.

Важно: виртуальный хостинг bucket.example.com в MinIO работает, но на staging проще включить path-style и не мучиться с DNS-записями под каждый бакет.

Что понадобится

  • VPS с Linux, публичным IP и открытыми портами 80/443.
  • Два A-записи: minio-staging.example.com и minio-console.example.com.
  • Docker и Docker Compose v2.
  • Reverse proxy с TLS — в примере Traefik v2 + Let’s Encrypt.
  • Около 10 ГБ свободного места под тестовые данные.

Разворачиваем MinIO в Docker Compose

Минимальный docker-compose.staging.yml заводит один контейнер, внешнюю сеть для Traefik и именованный том для данных.

			# docker-compose.staging.yml
networks:
  proxy:
    external: true
    name: proxy
  internal:
    name: internal

volumes:
  minio-data:

services:
  minio:
    image: minio/minio:latest
    container_name: minio-staging
    restart: unless-stopped
    environment:
      - MINIO_ROOT_USER=${MINIO_ROOT_USER:-admin}
      - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:-change-me-please}
      - MINIO_SERVER_URL=https://minio-staging.example.com
      - MINIO_BROWSER_REDIRECT_URL=https://minio-console.example.com
    command: server /data --console-address ":9001"
    volumes:
      - minio-data:/data
    networks:
      - proxy
      - internal
    ports:
      - "9000:9000"
      - "9001:9001"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 30s
		

Два параметра критичны для presigned URL. MINIO_SERVER_URL говорит серверу, на каком публичном домене подписывать ссылки. Без него ссылка будет подписана для http://minio:9000 и браузер отклонит подпись. MINIO_BROWSER_REDIRECT_URL нужен для корректных редиректов веб-консоли.

Прокидываем HTTPS через Traefik

Добавляем лейблы к сервису minio, чтобы Traefik маршрутизировал API и консоль на разные порты и автоматически выпускал сертификаты.

			labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"

      # S3 API
      - "traefik.http.routers.minio-staging.rule=Host(`minio-staging.example.com`)"
      - "traefik.http.routers.minio-staging.entrypoints=websecure"
      - "traefik.http.routers.minio-staging.tls.certresolver=letsencrypt"
      - "traefik.http.routers.minio-staging.service=minio-staging"
      - "traefik.http.services.minio-staging.loadbalancer.server.port=9000"

      # Web Console
      - "traefik.http.routers.minio-console.rule=Host(`minio-console.example.com`)"
      - "traefik.http.routers.minio-console.entrypoints=websecure"
      - "traefik.http.routers.minio-console.tls.certresolver=letsencrypt"
      - "traefik.http.routers.minio-console.service=minio-console"
      - "traefik.http.services.minio-console.loadbalancer.server.port=9001"
		

Проверяем здоровье сервера с локальной машины: curl -I https://minio-staging.example.com/minio/health/live должен вернуть HTTP 200.

Cloudflare: для поддомена с API лучше выключить оранжевое облако. Бесплатный тариф Cloudflare обрезает тело запроса на 100 МБ и может убирать S3-заголовки, из-за чего ломается подпись.

Бакеты, политики и отдельный пользователь для приложения

После запуска создаём бакет и отдельного IAM-пользователя. Делать это root-ключами приложения — плохая идея: root может удалить всё.

			# Подключаем mc к локальному MinIO
docker exec -it minio-staging \
  mc alias set local http://localhost:9000 admin change-me-please

# Создаём бакет
docker exec -it minio-staging mc mb local/staging-files

# Делаем его приватным
docker exec -it minio-staging mc anonymous set none local/staging-files
		

Теперь создаём пользователя staging-app и IAM-политику, ограничивающую права только этим бакетом.

			cat > /tmp/policy.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:*"],
      "Resource": [
        "arn:aws:s3:::staging-files",
        "arn:aws:s3:::staging-files/*"
      ]
    }
  ]
}
EOF

docker cp /tmp/policy.json minio-staging:/tmp/policy.json
docker exec -it minio-staging mc admin policy create local staging-rw /tmp/policy.json
docker exec -it minio-staging mc admin user add local staging-app a-long-random-secret-key
docker exec -it minio-staging mc admin policy attach local staging-rw --user staging-app
		

Логин staging-app и его секрет — это и есть S3_ACCESS_KEY и S3_SECRET_KEY для приложения.

Код приложения не меняется

Пример на AWS SDK v3 для Node.js. Обратите внимание на forcePathStyle: true: без него SDK попытается обратиться к bucket.minio-staging.example.com, и запрос уйдёт в никуда.

			// src/lib/s3.js
import { S3Client } from "@aws-sdk/client-s3";

export const s3 = new S3Client({
  endpoint: process.env.S3_ENDPOINT,
  region: process.env.S3_REGION,
  credentials: {
    accessKeyId: process.env.S3_ACCESS_KEY,
    secretAccessKey: process.env.S3_SECRET_KEY,
  },
  forcePathStyle: process.env.S3_FORCE_PATH_STYLE === "true",
});

export const BUCKET = process.env.S3_BUCKET;
		

Переменные для staging:

			S3_ENDPOINT=https://minio-staging.example.com
S3_PUBLIC_ENDPOINT=https://minio-staging.example.com
S3_BUCKET=staging-files
S3_ACCESS_KEY=staging-app
S3_SECRET_KEY=a-long-random-secret-key
S3_REGION=us-east-1
S3_FORCE_PATH_STYLE=true
		

Для продакшена — только другой набор значений, код идентичен.

Presigned URL: загрузка и скачивание без проксирования

Presigned URL — это обычный HTTPS URL с короткой подписью в query string. Кто угодно может выполнить ровно то действие, на которое выдана подпись: PUT для загрузки или GET для скачивания. Бэкенд проверяет права, подписывает URL и отдаёт клиенту — сам файл идёт напрямую в MinIO.

Загрузка из браузера

			// src/lib/presign.js
import { PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { s3, BUCKET } from "./s3.js";
import { randomUUID } from "node:crypto";

export async function presignUpload({ filename, contentType, userId }) {
  const key = `users/${userId}/${randomUUID()}-${filename}`;
  const cmd = new PutObjectCommand({
    Bucket: BUCKET,
    Key: key,
    ContentType: contentType,
  });
  const uploadUrl = await getSignedUrl(s3, cmd, { expiresIn: 60 * 5 });
  return { uploadUrl, key };
}
		

Важный подводный камень: Content-Type, который браузер отправляет при PUT, должен точно совпадать с тем, что было передано в PutObjectCommand. Иначе MinIO вернёт SignatureDoesNotMatch.

Скачивание приватных файлов

			export async function presignDownload(key, expiresIn = 60 * 10) {
  const cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key });
  return getSignedUrl(s3, cmd, { expiresIn });
}

// Эндпоинт в приложении
app.get("/api/documents/:id/url", requireAuth, async (req, res) => {
  const doc = await db.documents.findByPk(req.params.id);
  if (!doc || !canUserSee(req.user, doc)) return res.sendStatus(403);
  const url = await presignDownload(doc.key, 600);
  res.json({ url });
});
		
Почему это лучше проксирования: при прямой загрузке через ваше API все байты проходят через приложение, съедая CPU, RAM и пропускную способность. С presigned URL трафик идёт между клиентом и MinIO — бэкенд только подписывает ссылку.

CORS, lifecycle и безопасность

Несколько команд, которые стоит выполнить сразу после создания бакета.

CORS для браузерных загрузок

			cat > /tmp/cors.json <<'EOF'
{
  "CORSRules": [
    {
      "AllowedOrigins": ["https://app-staging.example.com", "http://localhost:3000"],
      "AllowedMethods": ["GET", "PUT", "POST", "HEAD"],
      "AllowedHeaders": ["*"],
      "ExposeHeaders": ["ETag"],
      "MaxAgeSeconds": 3000
    }
  ]
}
EOF

docker cp /tmp/cors.json minio-staging:/tmp/cors.json
docker exec -it minio-staging mc cors set local/staging-files /tmp/cors.json
		

Автоудаление старых тестовых файлов

			docker exec -it minio-staging mc ilm rule add --expire-days 30 local/staging-files
		

Шифрование данных в покое

			docker exec -it minio-staging mc encrypt set sse-s3 local/staging-files
		

И ещё раз: root-ключи храните в менеджере секретов и используйте только для mc admin. Консоль MinIO, если она доступна из интернета, закрывайте IP-allowlist или базовой авторизацией на уровне Traefik.

Бэкапы и мониторинг

Staging не должен хранить что-то ценное, но периодическое зеркалирование в дешёвое холодное хранилище спасает от случайного удаления. Команда mc mirror синхронизирует бакет в Backblaze B2, Yandex Object Storage или другой S3-совместимый бэкенд.

			mc alias set cold https://s3.us-east-005.backblazeb2.com $KEY $SECRET
mc mirror --overwrite --remove staging/staging-files cold/staging-backup
		

Для метрик MinIO отдаёт Prometheus-экспортёр по пути /minio/v2/metrics/cluster. В Grafana можно импортировать дашборд ID 13502 и сразу видеть занятое место, RPS, задержки и ошибки.

Типичные проблемы

  • SignatureDoesNotMatch при PUT — браузер отправил Content-Type, отличный от подписанного. Проверьте заголовок PUT.
  • Подписанная ссылка работает локально, но не в браузере — не задан MINIO_SERVER_URL. Ссылка подписана для внутреннего http://minio:9000.
  • 403 после Cloudflare — бесплатный тариф Cloudflare модифицирует заголовки. Переведите A-запись в режим DNS-only.
  • CORS preflight failed — на бакете не настроены CORS-правила.
  • Консоль редиректит на http://minio:9001 — не задан MINIO_BROWSER_REDIRECT_URL.

Часто задаваемые вопросы

1

Можно ли использовать MinIO в продакшене?

Можно, если есть экспертиза и ресурсы на поддержку кластера. Но управляемые S3/R2/Selectel Object Storage проще в эксплуатации: репликация, резервирование и дежурства уже на стороне провайдера. На staging MinIO часто выигрывает по цене и скорости развёртывания.

2

Что такое path-style и почему его включают?

Path-style означает, что бакет идёт в пути URL: example.com/bucket/key. Виртуальный хостинг — bucket.example.com/key. MinIO поддерживает оба варианта, но path-style не требует дополнительных DNS-записей, поэтому удобнее на staging.

3

Какие SDK совместимы с MinIO?

Практически все S3-SDK: AWS SDK для JavaScript, Python boto3, Go aws-sdk-go, Java aws-sdk-java, а также официальный minio-js/minio-py. Главное — указать правильный endpoint, region и forcePathStyle.

4

Нужен ли отдельный бакет под каждое окружение?

Не обязательно, но удобно. Можно завести бакеты staging-files, review-files, demo-files и политики с разным доступом. Это упрощает lifecycle-правила и изоляцию данных.

5

Как ограничить размер загружаемых файлов?

MinIO не ограничивает размер объекта, но обычно перед ним стоит reverse proxy. В Traefik лимита по умолчанию нет, а в NGINX используйте client_max_body_size. Если стоит Cloudflare, помните о лимите 100 МБ на бесплатном тарифе.

Выводы

Self-hosted MinIO на staging — это не попытка заменить облако, а способ сделать тестовую среду дешевле и ближе к продакшену. Тот же API, те же SDK, те же presigned URL, но без счетов за хранение битых файлов и ночных сбросов базы.

Ключевые моменты, которые стоит запомнить: всегда указывайте MINIO_SERVER_URL для корректных подписей, не используйте root-ключи в приложении, включайте path-style на staging и настраивайте lifecycle, чтобы мусор не копился.

Самое дорогое в staging — не железо, а различия в кодовых путях между dev и prod. MinIO помогает убрать одну из этих разниц почти бесплатно.

Команда tprogerРедакция

Источник: freeCodeCamp — How to Self-Host an S3-Compatible Object Store with MinIO on Your Staging Server.

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Как выбрать VPS/VDS под свой проект: гайд по параметрам и 6 провайдеров0830-06-2026
2Зарубежный хостинг для сайта: ТОП-25 иностранных хостинг-провайдеров0526-06-2026
3VPS vs VDS vs виртуальный хостинг: что выбрать в 2026 году0730-06-2026
4Кроссплатформенный VPS: 6 провайдеров с Linux и Windows в 2026 году0830-06-2026
5Deckhouse Conf 2026: зачем инженерам самописный SDN и виртуалки в Kubernetes0502-07-2026
6Как объединить инфраструктуру 35 продуктов в единую платформу данных0726-06-2026
7 Рег.облако открыл корпоративное объектное хранилище S3 с полной изоляцией данных 5725-11-2025
8 Рег.ру привлек в S3 собственной разработки более тысячи новых клиентов за полгода 5731-07-2025
9 Рег.облако увеличил облачное хранилище в 40 раз 5706-03-2026

Классификация: Мнения. Схожих патентов: 0. Схожих новостей: 9. Тональность: 5. Информативность: 7. Источник: tproger.ru.