Integrating Cloudflare R2 Storage with Laravel

Lokman Musliu Founder and CEO of Lucky Media
Lokman Musliu

January 1, 2024 · 5 min read

Integrating Cloudflare R2 Storage with Laravel

Are you looking for an affordable and more efficient way to store objects than AWS S3? Check out Cloudflare R2, an S3-compatible storage service with zero egress fees and a generous free tier. It’s a cost-effective option for developers and businesses. I’ll show you how to use Cloudflare R2 with your Laravel app, helping you save money and switch smoothly. This guide covers everything from setting up to allowing public access, so you can fully use Cloudflare R2 with Laravel.

Cloudflare R2 vs AWS S3

Let's compare Cloudflare R2 and Amazon S3 for object storage. Cloudflare R2 gives you a free tier with 10 GB of storage and lots of operations, unlike S3's 5 GB and fewer operations. With no egress fees, R2 can save you a lot of money, especially if you get a lot of data. It’s also trying to match S3’s worldwide reliability and speed.

Cloudflare R2 has an S3-compatible API and a tool to help you switch easily. It also has lower costs for storage and operations after the free tier, making it a good deal for those who use a lot of data. Amazon S3 is still a strong option, especially if you’re already using AWS, but you should think about both the costs and technical details when choosing a service.

Install the S3 Filesystem driver

Cloudflare R2 works with the league/flysystem-aws-s3-v3 driver and it has an S3 Compatible API. Install the driver with this command:

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

Configure a new disk

Next, you’ll need to update your disk configuration. In your Laravel project, locate and edit the config/filesystems.php file to include a new r2 disk configuration as follows:

return [

    /*
    |--------------------------------------------------------------------------
    | Default Filesystem Disk
    |--------------------------------------------------------------------------
    |
    | Here you may specify the default filesystem disk that should be used
    | by the framework. The "local" disk, as well as a variety of cloud
    | based disks are available to your application. Just store away!
    |
    */

    'default' => env('FILESYSTEM_DISK', 'local'),

    /*
    |--------------------------------------------------------------------------
    | Filesystem Disks
    |--------------------------------------------------------------------------
    |
    | Here you may configure as many filesystem "disks" as you wish, and you
    | may even configure multiple disks of the same driver. Defaults have
    | been setup for each driver as an example of the required options.
    |
    | Supported Drivers: "local", "ftp", "sftp", "s3"
    |
    */

    'disks' => [

        'local' => [
            'driver' => 'local',
            'root' => storage_path('app'),
        ],

        'public' => [
            'driver' => 'local',
            'root' => storage_path('app/public'),
            'url' => env('APP_URL').'/storage',
            'visibility' => 'public',
        ],

        'r2' => [
             'driver' => 's3',
             'key' => env('CLOUDFLARE_R2_ACCESS_KEY_ID'),
             'secret' => env('CLOUDFLARE_R2_SECRET_ACCESS_KEY'),
             'region' => 'us-east-1', // Cloudflare R2 doesn't have specific regions, so 'us-east-1' is fine.
             'bucket' => env('CLOUDFLARE_R2_BUCKET'),
             'url' => env('CLOUDFLARE_R2_URL'),
             'visibility' => 'private',
             'endpoint' => env('CLOUDFLARE_R2_ENDPOINT'),
             'use_path_style_endpoint' => env('CLOUDFLARE_R2_USE_PATH_STYLE_ENDPOINT', false),
             'throw' => false,
        ],

        '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),
            // 'visibility' => 'public', // https://statamic.dev/assets#visibility
        ],

        'assets' => [
            'driver' => 'local',
            'root' => public_path('assets'),
            'url' => '/assets',
            'visibility' => 'public',
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Symbolic Links
    |--------------------------------------------------------------------------
    |
    | Here you may configure the symbolic links that will be created when the
    | `storage:link` Artisan command is executed. The array keys should be
    | the locations of the links and the values should be their targets.
    |
    */

    'links' => [
        public_path('storage') => storage_path('app/public'),
    ],

];

Let’s go ahead and update our environment file with the necessary variables. Remember, it’s always a good practice to keep the .env.example file in sync as well. By doing so, you’ll be helping your team members to set up their environments more efficiently.

CLOUDFLARE_R2_ACCESS_KEY_ID=
CLOUDFLARE_R2_SECRET_ACCESS_KEY=
CLOUDFLARE_R2_BUCKET=
CLOUDFLARE_R2_ENDPOINT=
CLOUDFLARE_R2_URL=

Create a new R2 bucket

Let's get started with setting up your storage bucket on Cloudflare! First, log in to your Cloudflare dashboard. Look for the "R2" section in the sidebar and click on it. Once you're there, you'll see an option to "Create bucket" – go ahead and select that. For this guide, we'll name our new bucket images.

Decide the location for your bucket. If your application needs to comply with GDPR, we recommend using the Specify Jurisdiction option and selecting the European Union to ensure compliance. For everyone else, choosing Automatic will let Cloudflare intelligently serve your assets from the location nearest to your users, optimizing for speed and efficiency.

r2 create bucket screen

Generate an API token

To begin, navigate to the R2 page within your Cloudflare dashboard and click on Manage R2 API Tokens. Here, you’ll want to create a new token and grant it Object Read & Write permissions, specifically associated with the images bucket we previously set up. It’s important to avoid assigning full access to your API key, as this can lead to a security nightmare.

After you’ve successfully created the token, Cloudflare will provide you with an Access Key ID and a Secret Access Key. You must keep these credentials safe, as they are the keys to accessing your Cloudflare resources programmatically. Treat them with the same level of security you would for your most confidential information.

cloudflare r2 create api token screen

Public bucket

If you need to keep your bucket private, skip this step!

To make your files publicly accessible, you’ll need to activate the Public Access feature for your bucket. The good news is that if you’re using Cloudflare as your DNS provider, this process is simplified for you. All you need to do is specify the domain you wish to use. In our case, we’ve chosen assets.luckymedia.dev as our domain.

Once you’ve linked a custom domain to your storage bucket, anyone can access the contents of your bucket through that domain. Moreover, your application can benefit from the advantages of Cloudflare, including bot management, the Access service, and enhanced caching capabilities.

Cloudflare R2 CORS policy

When you attempt to upload files using the Storage facade, you might get a CORS error. That’s because CORS policies require proper configuration to allow resource sharing between different origins. It’s important to set up CORS correctly. Here’s how you can do it step by step:

Head over to your bucket, go to the CORS Policy and click on the Add CORS Policy button.

You can add two origins ( local and production ):

The .test domain is our local Valet domain that we use for testing.

[
  {
    "AllowedOrigins": [
      "http://luckymedia-api.test",
      "https://www.luckymedia.dev"
    ],
    "AllowedMethods": [
      "GET",
      "POST",
      "DELETE",
      "PUT",
      "HEAD"
    ]
  }
]

Update your Environment file

With the credentials that were shown to you in the previous step, we now need to update our env file as shown below:

CLOUDFLARE_R2_ACCESS_KEY_ID=ACCESS_KEY
CLOUDFLARE_R2_SECRET_ACCESS_KEY=SECRET_KEY
CLOUDFLARE_R2_BUCKET=BUCKET_NAME
CLOUDFLARE_R2_ENDPOINT=ENDPOINT_URL
CLOUDFLARE_R2_URL=https://assets.luckym.dev

Using Cloudflare R2 with Laravel storage facade

You can now easily integrate your R2 storage with the Laravel Storage facade. Here’s a small example from the Laravel documentation:

use Illuminate\Support\Facades\Storage;
 
Storage::disk('r2')->put('example.txt', 'Contents');

For added convenience, you can set r2 as your default disk in your .env file, eliminating the need to specify disk('r2') each time:

FILESYSTEM_DISK=r2

Conclusion

Cloudflare R2 is a great alternative for AWS S3 if you want a cheaper and more efficient way to store objects, especially in Laravel apps. It has a good free plan, absence of egress fees, and works with S3 APIs. R2 makes it easy for developers and companies to store and handle their data without paying too much. By following this guide, you can add Cloudflare R2 to your Laravel projects and use its benefits while saving money. Whether you’re starting fresh or improving an old project, Cloudflare R2 is a strong option to consider for your development tools.


Bring Your Ideas to Life 🚀

If you need help with a Laravel project let’s get in touch.

Lucky Media is proud to be recognized as a leading Laravel Development Agency

Technologies

Laravel
Lokman Musliu Founder and CEO of Lucky Media
Lokman Musliu

Founder and CEO of Lucky Media

Stay up-to-date

Be updated with all news, products and tips we share!

Let’s chat

We partner with a limited number of brands each quarter to ensure senior-level attention on every project.

lokman and arlind headshots
Teamwork