Today DigitalOcean announced a great new product called Spaces that is essentially a much cheaper version of Amazon's S3 object storage product. For $5 per month, you get 250GB of storage and 1TB of bandwidth included. This would cost over $95 per month with S3. Additional storage costs $0.02 per GB, but the best part is that additional bandwidth is just $0.01 per GB - a huge saving compared to S3's $0.09 per GB. I recently had an idea for a product that would have had significant storage and bandwidth requirements but I ultimately decided not to pursue it as using S3 would have been too expensive and I didn't want to roll my own storage solution. With the pricing on the Spaces product, this idea is now viable.

Configuring DigitalOcean Spaces in Laravel

Like many products that compete with S3, Spaces has an S3-compatible API, which means you can use existing Amazon S3 clients with it. For Laravel developers, you can simply use the existing S3 driver for Flysystem to connect to Spaces.

To get started, sign up for DigitalOcean Spaces and generate an access key using the instructions under the section Option 1 — Sharing Access to Spaces with Access Keys in the Managing Access to DigitalOcean Spaces guide. You'll also need to create a bucket to store your files in.

If you haven't already, make sure your project has the Flysystem S3 driver is installed:

$ composer require league/flysystem-aws-s3-v3

Next, add a new disk that uses the S3 driver but also provides an endpoint parameter in the disk configuration in config/filesystems.php:

'spaces' => [
    'driver' => 's3',
    'key' => env('DO_SPACES_KEY'),
    'secret' => env('DO_SPACES_SECRET'),
    'endpoint' => env('DO_SPACES_ENDPOINT'),
    'region' => env('DO_SPACES_REGION'),
    'bucket' => env('DO_SPACES_BUCKET'),
],

You'll also need to add new environment variables to your project's .env file:

DO_SPACES_KEY=[YOURKEY]
DO_SPACES_SECRET=[YOURSECRET]
DO_SPACES_ENDPOINT=https://nyc3.digitaloceanspaces.com
DO_SPACES_REGION=nyc3
DO_SPACES_BUCKET=[YOURBUCKET]

Credit for making me aware of this approach goes to boptom on Laracasts.

Using the Spaces disk

All that's left is to try it out! I'm going to create two routes in routes/web.php for the purpose of demonstrating uploading files and displaying them.

Route::get('upload', function() {
    $files = Storage::disk('spaces')->files('uploads');

    return view('upload', compact('files'));
});

Route::post('upload', function() {
    Storage::disk('spaces')->putFile('uploads', request()->file, 'public');

    return redirect()->back();
});

These should be pretty self-explanatory. The first method retrieves any existing files and responds with a view named upload. The second takes an upload form submission and stores the file. In this case I'm making the file public to make retrieving it simple. Finally you'll need to create the file resources/views/upload.blade.php:

<form method="post" enctype="multipart/form-data">
    {{ csrf_field() }}

    <h1>Upload</h1>
    <label>
        Upload a file<br>
        <input type="file" name="file" />
    </label>

    <p><button>Submit</button></p>
</form>

<h1>Existing Files</h1>

<ul>
@forelse($files as $file)
    <li><a href="{{ Storage::disk('spaces')->url($file) }}">{{ Storage::disk('spaces')->url($file) }}</a></li>
@empty
    <li><em>No files to display.</em></li>
@endforelse
</ul>

If you try it out, you should now be able to upload files with the form and once complete a link will appear in the list below that will open the file from DigitalOcean's server.