
Okay, here’s the thing: I love Laravel. It’s smooth, opinionated in a good way, and the ecosystem is just chef’s kiss. But when it came to working with object storage, especially something S3-compatible like MinIO, I hit more roadblocks than I expected.
If you’re trying to integrate MinIO with Laravel and hoping it’ll be as easy as “change the endpoint and go,” well… you’re half right.
Let me walk you through what I did, what worked, and what tripped me up — all in a chill, no-BS guide that actually works.
First Off, Why Even Use MinIO?
I was working on a side project — a lightweight file-based SaaS — and wanted to mimic S3 without burning my AWS credits for local dev.
Also, I needed something:
- Self-hosted
- Fast
- Compatible with Laravel’s
Storage::disk('s3')
That’s where MinIO comes in. It’s open-source, S3 API-compatible, and pretty dang fast. You can run it locally, on Docker, or in production if you want.
Step 1: Running MinIO with Docker (The Easy Way)
I’ll be honest: I didn’t feel like installing a bunch of stuff manually, so I used Docker.
Here’s my docker-compose.yml
(feel free to copy-paste):
version: '3'
services:
minio:
image: minio/minio
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
command: server /data --console-address ":9001"
volumes:
- minio_data:/data
volumes:
minio_data:
Then:
docker-compose up -d
Boom. Done. Now go to http://localhost:9001 and log in with:
- Username:
minioadmin
- Password:
minioadmin
Is that secure? No. But it’s local. Relax.
Step 2: Create a Bucket
Inside the MinIO web console, I created a bucket called laravel-bucket
.
You can name it whatever you want, but note it down — Laravel’s config will need this later.
Also, I didn’t mess with public access yet — keeping it simple for now.
Step 3: Install the Right Laravel Dependencies
I already had Laravel 10 set up, so I ran:
composer require league/flysystem-aws-s3-v3 "^3.0"
Note: Without this, Laravel won’t understand how to talk S3.
Step 4: Configure Laravel to Talk to MinIO
Now here’s where I messed up the first time — I kept copying S3 configs from tutorials without adjusting the endpoint and path style.
.env
entries:
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=minioadmin
AWS_SECRET_ACCESS_KEY=minioadmin
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=laravel-bucket
AWS_ENDPOINT=http://localhost:9000
AWS_USE_PATH_STYLE_ENDPOINT=true
Yes, even locally, you need the region. And yes, the path_style
thing matters. More on that later.
In config/filesystems.php
, make sure this looks right:
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', true),
],
And for local dev, I set 'visibility' => 'public'
in the config when needed.
Step 5: Testing File Uploads (And The Gotchas)
Now I tried this:
Storage::disk('s3')->put('test-file.txt', 'Hello MinIO!');
Guess what? It worked. First try. I was shocked too 😅
Then I tried downloading it:
$content = Storage::disk('s3')->get('test-file.txt');
Still worked.
BUT: when I tried to access it via URL using Storage::disk('s3')->url(...)
, it didn’t return a usable public link — because MinIO defaults to private files.
Step 6: Making Files Public (Sort Of)
MinIO doesn’t have the exact same permission model as S3. You can set a policy in the web console to allow read access, but I didn’t want to mess too much yet.
So instead, for dev, I exposed files manually by grabbing them from http://localhost:9000/laravel-bucket/my-file.txt
.
For production? I’d use signed URLs or pre-signed temporary URLs via:
Storage::disk('s3')->temporaryUrl(
'test-file.txt',
now()->addMinutes(10)
);
Boom. Safe-ish and easy.
Step 7: Bonus — Artisan Integration
Another cool trick: you can use Artisan commands to interact with the MinIO disk like this:
php artisan storage:link
php artisan tinker
>>> Storage::disk('s3')->exists('test-file.txt')
Great for debugging if things get weird.
Final Thoughts
MinIO has saved me so much time and money during local development. I no longer fake S3 or stub it out — I actually test against an S3-like system.
Is it production-ready? Yeah, a lot of teams use it. But even just for local dev, it’s a no-brainer.
Just remember:
- Use
use_path_style_endpoint
- Keep your bucket name consistent
- Don’t forget those weird AWS env vars, even if you’re not using real AWS
That’s it. You’re good to go.
Pro Tip:
Set up multiple MinIO buckets for staging vs dev. Same Laravel config, just different .env
buckets = less confusion later.