Locale
Наконец завершил миграцию файлов блога с локального диска в облачное хранилище.
Артур Хайбуллин
Артур Хайбуллин 22 фев 2026, 23:21

Привет! Наконец-то я завершил перенос хранения файлов, загруженных в мой блог, с локального серверного диска на сервис типа S3, который я развернул в своей лаборатории с помощью решения RustFS (совместимого с Amazon S3, как MinIO / Spaces / R2 и т. д.).

rustfs.png

Почему я решил мигрировать с локального диска?

Локальное хранилище в Laravel (через storage/app/public + storage:link) работало нормально, но начали появляться некоторые ограничения. Ниже я постарался перечислить основные из них:

  • Масштабирование: несколько экземпляров приложения требуют одновременного использования общего хранилища, а совместное использование файловой системы между ними приводит к потере производительности и требует нетривиальных решений. Если ваш сайт размещен на географически распределенных узлах, то эта задача становится практически невыполнимой и потребует значительных усилий и затрат для реализации.

  • Резервное копирование: резервное копирование растущего объема файлов вместе с кодом и базой данных также не является правильным решением.

  • CDN и географическое распределение: контейнер, подобный S3, можно легко разместить за CDN без изменения кода.

Поэтому логичным шагом было переместить все загрузки в отдельное хранилище объектов, которое бы позволяло получить к ним доступ с помощью API, подобного S3.

Подготовка Laravel для S3

В проекте уже был подключён драйвер league/flysystem-aws-s3-v3, так что ничего ставить не пришлось, но для установки в принципе достаточно выполнить команду:

composer require league/flysystem-aws-s3-v3 "^3.0"

Дальше я просто переключил диск по умолчанию в .env:

FILESYSTEM_DISK=s3

AWS_ACCESS_KEY_ID=your_access_key_id
AWS_SECRET_ACCESS_KEY=your_secret_access_key
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=your_bucket_name
AWS_URL=https://your-bucket-url.com
# для кастомного S3
# AWS_ENDPOINT=https://your-custom-endpoint.com
# AWS_USE_PATH_STYLE_ENDPOINT=true

Laravel уже умеет подхватывать это из config/filesystems.php, так что весь код, который работал через Storage::disk() и Storage::url(), сразу начал смотреть в мой бакет.​

Отдельный момент — Livewire: временные загрузки тоже нужно отправлять в S3, иначе часть файлов будет оставаться на локальном диске.

LIVEWIRE_TEMPORARY_FILE_UPLOAD_DISK=s3

Миграция уже загруженных файлов

Так как в опубликованных мной ранее постах блога уже были размещены файлы изображений storage/app/public , их нужно было аккуратно перенести, не ломая существующие ссылки.​

Самый простой путь — воспользоваться aws-cli (или аналогами rclone, s3cmd) и просто Если у провайдера свой endpoint, можно добавить --endpoint-url https://your-endpoint или использовать rclone с нужным backend’ом.​
Важно сохранить ту же структуру каталогов и имена файлов — тогда Laravel продолжит строить те же относительные пути, уже поверх S3.​

Критичный момент: если Вы храните ссылки в БД.​

  • Если хранятся относительные пути (posts/123/image.jpg), то после переключения FILESYSTEM_DISK приложение просто начнёт генерировать S3‑URL поверх тех же путей.​

  • Если хранились полные локальные пути, может понадобиться миграция, которая превратит их в относительные пути или сразу в S3‑URL.​

Я ориентировался на первый вариант и убедился, что везде в коде использую Storage::url($path):

Storage::disk(config('filesystems.default'))->url(
    'path/to/file.jpg'
);

Чистка, кеши и символические ссылки

После переключения диска я почистил кеши Laravel, чтобы приложение гарантированно использовало обновленную конифгурацию:

php artisan config:clear
php artisan cache:clear
php artisan route:clear

Символическую ссылку public/storage, которую создаёт php artisan storage:link, можно не трогать, если вы больше не обслуживаете файлы с локального диска.​
В моём случае часть файлов ещё жила локально, поэтому ссылку я оставил — переход получился плавным: новый контент уже летит в S3, старый постепенно мигрирует.​

Нюансы S3‑совместимого сервиса

Когда используешь не чистый AWS, а свой S3‑подобный сторедж, всплывают дополнительные детали.​

  • CORS: если фронтенд обращается к бакету напрямую, нужно правильно прописать CORS‑правила под домен блога.​

  • CDN: S3‑совместимый бакет удобно прятать за CloudFront или другим CDN, увеличивая скорость раздачи статики во всём мире.​

  • Права доступа: нельзя раздавать бакету публичную запись; для приватных загрузок — pre‑signed URL или серверный upload‑поток.​

  • Path‑style vs virtual host: многие кастомные S3‑сервисы требуют AWS_USE_PATH_STYLE_ENDPOINT=true и своего AWS_ENDPOINT.​

Ниже я привел для удобства небольшой чек‑лист, чтобы не упустить какой-либо шаг изменения конфигурации:

  • Проверить наличие league/flysystem-aws-s3-v3 в composer.json.​

  • Прописать S3‑учётки и FILESYSTEM_DISK=s3 в .env.​

  • Настроить LIVEWIRE_TEMPORARY_FILE_UPLOAD_DISK.​

  • Синхронизировать storage/app/* в бакет.​

  • Очистить кеши и вручную проверить несколько ссылок на файлы в интерфейсе.​

В итоге блог продолжил работать как раньше, но хранение файлов стало независимым от конкретного сервера, готовым к масштабированию и интеграции с CDN.​

Screenshot 2026-02-22 231839.png

Собрал в список полезные ссылки по тематике облачного хранилища RustFS и его использования в Laravel:

RustFS как S3‑совместимое хранилище

Laravel + S3 (применимо и к RustFS)

RustFS в Laravel Sail / локальной разработке

Буду рад увидеть ваши комментарии, мнения, советы по выбранному решению и его реализации, а также напишите в комментариях, если вам интересно узнать больше о RustFS и о том, как я сам развернул его за 5 минут с нуля до готового к использованию решения.

Комментарии (0)
Комментировать
Пока нет комментариев

Станьте первым, кто прокомментирует эту запись