File storage
Laravel has a filesystem abstraction (opens in a new tab) that lets us easily change where files are stored.
When running on Lambda, you will need to use the s3
adapter to store files on AWS S3.
To do this, set FILESYSTEM_DISK: s3
either in serverless.yml
or your production .env
file. We can also create an S3 bucket via serverless.yml
directly:
# ...
provider:
# ...
environment:
# environment variable for Laravel
FILESYSTEM_DISK: s3
AWS_BUCKET: !Ref Storage
iam:
role:
statements:
# Allow Lambda to read and write files in the S3 buckets
- Effect: Allow
Action: s3:*
Resource:
- !Sub '${Storage.Arn}' # the storage bucket
- !Sub '${Storage.Arn}/*' # and everything inside
resources:
Resources:
# Create our S3 storage bucket using CloudFormation
Storage:
Type: AWS::S3::Bucket
That's it! The AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN) are set automatically in AWS Lambda, you don't have to define them.
Public files
Laravel has a special disk called public
(opens in a new tab): this disk stores files that we want to make public, like uploaded photos, generated PDF files, etc.
Again, those files cannot be stored on Lambda, i.e. they cannot be stored in the default storage/app/public
directory. You need to store those files on S3.
Do not run php artisan storage:link
in AWS Lambda: it is now useless, and it will fail because the filesystem is read-only in Lambda.
To store public files on S3, you could replace the disk in the code:
- Storage::disk('public')->put('avatars/1', $fileContents);
+ Storage::disk('s3')->put('avatars/1', $fileContents);
but doing this will not let your application work locally. A better solution, but more complex, involves making the public
disk configurable. Let's change the following lines in config/filesystems.php
:
/*
|--------------------------------------------------------------------------
| Default Public Filesystem Disk
|--------------------------------------------------------------------------
*/
'public' => env('FILESYSTEM_DISK', 'public_local'),
...
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
'public_local' => [ // Rename `public` to `public_local`
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'token' => env('AWS_SESSION_TOKEN'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
],
's3_public' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'token' => env('AWS_SESSION_TOKEN'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_PUBLIC_BUCKET'),
'url' => env('AWS_URL'),
],
],
You can now configure the public
disk to use S3 by changing serverless.yml
or your production .env
:
FILESYSTEM_DISK=s3
FILESYSTEM_DISK_PUBLIC=s3