Storage & backups

Laravel

2 min read

Laravel works with object storage through the built-in s3 disk of the Storage facade. Uploaded files, avatars, documents and exports can be stored in a bucket instead of the server disk.

Installation

The examples are for Laravel 10 or later. Install the S3 adapter for Flysystem:

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

Configuration

Add the connection settings to .env:

FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=<Access Key>
AWS_SECRET_ACCESS_KEY=<Secret Key>
AWS_DEFAULT_REGION=default
AWS_BUCKET=my-bucket
AWS_ENDPOINT=https://s3.uzcstorage.uz
AWS_USE_PATH_STYLE_ENDPOINT=true
AWS_URL=https://s3.uzcstorage.uz/my-bucket

The s3 disk in config/filesystems.php already reads these variables. Add two lines to it that disable unnecessary checksum headers:

's3' => [
    'driver' => 's3',
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION'),
    'bucket' => env('AWS_BUCKET'),
    'url' => env('AWS_URL'),
    'endpoint' => env('AWS_ENDPOINT'),
    'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
    'request_checksum_calculation' => 'when_required',
    'response_checksum_validation' => 'when_required',
    'throw' => true,
],

Check the connection with php artisan tinker:

Storage::disk('s3')->put('test.txt', 'Hello, UzCloud!');
Storage::disk('s3')->get('test.txt');

Working with Files

use Illuminate\Support\Facades\Storage;

$disk = Storage::disk('s3');

$disk->put('docs/hello.txt', 'Hello, UzCloud!');          // write
$text = $disk->get('docs/hello.txt');                     // read
$exists = $disk->exists('docs/hello.txt');                // check
$disk->copy('docs/hello.txt', 'archive/hello.txt');       // copy
$disk->delete('docs/hello.txt');                          // delete
$files = $disk->files('docs');                            // list a folder
$disk->putFileAs('backups', new \Illuminate\Http\File('/tmp/backup.zip'), 'backup.zip'); // stream a large file

Upload Files from a Form

public function store(Request $request)
{
    $request->validate(['avatar' => 'required|image|max:5120']);

    // Saves to s3 under avatars/<random name>.<ext> and returns the path
    $path = $request->file('avatar')->store('avatars', 's3');

    $request->user()->update(['avatar_path' => $path]);
    return back();
}

Files are private by default. For private files, give a temporary link:

$url = Storage::disk('s3')->temporaryUrl($path, now()->addMinutes(30));

// Force download with a file name
$url = Storage::disk('s3')->temporaryUrl($path, now()->addMinutes(30), [
    'ResponseContentDisposition' => 'attachment; filename="report.pdf"',
]);

For public files (for example, product images), open read access to a folder with a bucket policy and use a permanent link:

$url = Storage::disk('s3')->url('public/products/1.jpg');
// https://s3.uzcstorage.uz/my-bucket/public/products/1.jpg

Direct Upload from a Browser

Large files are better uploaded by the browser directly to the bucket, without passing them through PHP. Create a temporary upload link with temporaryUploadUrl (available in recent Laravel versions; in older versions, create the link with the SDK as shown in the PHP article):

['url' => $url, 'headers' => $headers] = Storage::disk('s3')->temporaryUploadUrl(
    'uploads/' . Str::uuid() . '.mp4', now()->addMinutes(10)
);
return response()->json(compact('url', 'headers'));

The browser sends the file with fetch(url, { method: "PUT", headers, body: file }). Configure CORS for the bucket.

Tip

Keep public and local disks for temporary files and move user files to s3: then the application servers stay stateless and can be scaled or recreated without losing data.

Tip

See also: