> ## Documentation Index
> Fetch the complete documentation index at: https://cloud.laravel.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating Object Storage to Laravel Cloud

> Learn how to move files from another storage provider to Laravel Cloud Object Storage using Laravel's read-through fallback disk.

If you're moving an existing application to Laravel Cloud, you likely already have files sitting in another provider's bucket. Copying everything before you cut over can be slow and force downtime. Laravel's read-through filesystem driver lets you cut over immediately instead. Reads that miss on your new Laravel Cloud Object Storage bucket automatically fall back to your old provider and copy the file into the new bucket for next time, so your files migrate themselves as your application accesses them.

## Before you start

<Note>
  The read-through driver requires Laravel 13.26.0 or later. Run `composer update laravel/framework` before continuing. If your old provider is S3-compatible, you'll also need `league/flysystem-aws-s3-v3`; see the [Object Storage prerequisites](/docs/resources/object-storage#prerequisites).
</Note>

This works for any two Flysystem-compatible disks, not just S3-compatible ones, so it also covers moving off local disk storage or another provider's driver.

## Step 1: Create your Laravel Object Storage bucket

If you haven't already, [create a Laravel Object Storage bucket](/docs/resources/object-storage#creating-buckets) and attach it to your environment. Note its disk name (for example, `r2`); Laravel Cloud automatically injects the `AWS_*` credentials your application needs to reach it.

Leave your old provider's credentials in place; you'll need them for the fallback disk in the next step.

## Step 2: Configure your old provider as a fallback disk

In `config/filesystems.php`, add a disk entry for your old provider using whatever credentials it requires:

```php theme={null}
'disks' => [

    // ...

    'legacy' => [
        'driver' => 's3',
        'key' => env('LEGACY_AWS_ACCESS_KEY_ID'),
        'secret' => env('LEGACY_AWS_SECRET_ACCESS_KEY'),
        'region' => env('LEGACY_AWS_DEFAULT_REGION'),
        'bucket' => env('LEGACY_AWS_BUCKET'),
        'endpoint' => env('LEGACY_AWS_ENDPOINT'), // omit for AWS S3; required for R2, Spaces, B2, etc.
    ],

],
```

Laravel Cloud only injects credentials for the bucket attached to your environment, so add your old provider's credentials as custom environment variables under your environment's settings.

## Step 3: Add a read-through disk

Combine your new Laravel Cloud bucket (`primary`) and your old provider (`fallback`) into a single read-through disk:

```php theme={null}
'disks' => [

    // ...

    'assets' => [
        'driver' => 'read-through',
        'primary' => 'r2',
        'fallback' => 'legacy',
    ],

],
```

Reads check the new bucket first. If the file isn't there yet, Laravel reads it from the old provider, copies it into the new bucket, and returns it, all within the same request. Writes and directory listings always go to the new bucket, so anything your application saves after cutover lands directly on Laravel Cloud. Existence and metadata checks, such as `Storage::exists` and `Storage::size`, consult either disk without triggering a copy.

<Tip>
  By default, a failed promotion (for example, the new bucket is briefly unreachable) doesn't fail the read; Laravel just returns the file from the fallback disk. Set `'throw_on_promotion_failure' => true` on the disk if you'd rather the read fail loudly. Either way, keep an eye on your logs during the migration window: [Laravel Nightwatch](/docs/knowledge-base/nightwatch-on-cloud) surfaces exceptions like these in real time, which helps catch credential or permission issues with either disk early.
</Tip>

## Step 4: Point your application at the read-through disk

Update `FILESYSTEM_DISK` to your new read-through disk, and redeploy:

```
FILESYSTEM_DISK=assets
```

From this point on, every file read through `Storage::disk('assets')`, or the default `Storage` facade, transparently pulls from whichever bucket actually has the file, and quietly migrates it to Laravel Cloud along the way.

## Step 5: Speed up the migration (optional)

Waiting for real traffic to touch every file can leave rarely accessed files on your old provider indefinitely. To migrate everything up front instead, loop over the fallback disk's contents and read each file through the read-through disk:

```php theme={null}
use Illuminate\Support\Facades\Storage;

$legacy = Storage::disk('legacy');
$assets = Storage::disk('assets');

foreach ($legacy->allFiles() as $path) {
    $assets->get($path);
}
```

<Info>
  For large buckets, dispatch this as a queued job per file (or per batch of files) instead of running it inline.
</Info>

## Step 6: Retire the old provider

Once every object in your old bucket exists in Laravel Cloud, remove the fallback. Use your storage provider's inventory or compare the object keys in both buckets to confirm the migration is complete:

1. Set `FILESYSTEM_DISK` back to your bucket's disk name (`r2`).
2. Remove the `legacy` and `assets` disk entries from `config/filesystems.php`.
3. Redeploy.
4. Cancel or delete the bucket with your old provider.
