How to Set Up Amazon S3-Compatible MinIO for Laravel Applications

MinIO Laravel integration, Amazon S3 compatible storage, Laravel MinIO setup

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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top