Файлова система контейнера тимчасова. Усе, що записано в контейнер (наприклад, у storage/app/public на диску local), зникає при його перестворенні - тобто при кожному деплої.
Варіант 1 - том Docker:
services:
app:
volumes:
- uploads:/var/www/html/storage/app
volumes:
uploads:
- дані переживають перестворення контейнера;
- працює лише на одному сервері: другий сервер чи друга репліка на іншому хості цих файлів не бачать;
- бекапи томів - окрема турбота;
- віддавати файли користувачам доводиться через застосунок або Nginx з доступом до тому.
Варіант 2 - об'єктне сховище (рекомендовано): S3, Cloudflare R2, DigitalOcean Spaces, MinIO.
// config/filesystems.php
'disks' => [
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'endpoint' => env('AWS_ENDPOINT'),
],
],
$path = $request->file('avatar')->store('avatars', 's3');
$url = Storage::disk('s3')->url($path);
- контейнери без стану: будь-яку кількість реплік на будь-яких серверах можна знищити й перестворити;
- файли віддаються з CDN, а не через застосунок;
- надійність і бекапи - на боці провайдера;
- приватні файли - через тимчасові підписані посилання (
temporaryUrl).
Що ще не повинно жити в контейнері:
- сесії - в Redis чи базі, а не в файлах, інакше користувача розлогінить після деплою чи при переході на іншу репліку;
- кеш - Redis чи база; файловий кеш у кожній репліці свій і не очищується разом;
- логи - в
stderr, звідки їх збирає Docker.
Локальна розробка: MinIO в Compose імітує S3, і застосунок працює з тим самим драйвером, що й у продакшені.
Важлива деталь Laravel: php artisan storage:link створює символьне посилання public/storage → storage/app/public. З S3 воно не потрібне, а з томом - має існувати в образі чи створюватися при старті.