Using MinIO as File Storage in Laravel

Background⌗
We store our image resources and other static assets using S3. To reduce code complexity, we’ve been using the same S3 environment for development and testing stages, and everything had been running smoothly. However, suddenly one day, uploading files to S3 from our test server in China started timing out, and this problem couldn’t be resolved.
Initially, we considered uploading files to the Storage directory within the project during testing, but this made configuration and URL generation very complicated and wasn’t conducive to DevOps.
In our projects, we extensively use custom configurations or ENV variables to concatenate static resource URLs, resulting in poor code maintainability. Additionally, to ensure consistent access, we would need to set up a separate web service for uploaded files.
That’s when I remembered an open-source project I had seen on GitHub called MinIO, which is today’s main topic.
Introduction to MinIO⌗
MinIO is an S3-like storage service developed in Go. Why use it to replace S3? Because it’s compatible with S3’s API. This reduces complexity when integrating it into projects, especially when generating resource URLs.
Deploying MinIO⌗
I’ll use Docker Compose for deployment. Here’s the configuration file:
services:
minio:
image: minio/minio:latest
restart: always
hostname: minio
container_name: minio
ports:
- 9000:9000
- 9001:9001
volumes:
- /usr/www/data/minio:/data
environment:
- MINIO_DOMAIN=example.dev # Enable DNS Style Bucket mode
- MINIO_ROOT_USER=${MINIO_ROOT_USER}
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
command: server /data --console-address ":9001"
Setting up Nginx proxy:
server {
listen 80;
listen [::]:80;
server_name minio.example.dev bucket.example.dev minio-console.example.dev;
location / {
return 301 https://$host$request_uri;
}
}
# Domain used for file uploads
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name minio.example.dev;
# SSL
ssl_certificate certs/fullchain.cer;
ssl_certificate_key certs/example.dev.key;
ssl_stapling on;
ssl_stapling_verify on;
proxy_buffering off;
client_max_body_size 0;
ssl_trusted_certificate /etc/nginx/certs/ca-bundle.trust.crt;
include components/security.conf;
# gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
location / {
proxy_pass http://minio:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Domain for MinIO Bucket, so the bucket name doesn't need to be included in the URL when accessing uploaded files
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name bucket.example.dev;
# SSL
ssl_certificate certs/fullchain.cer;
ssl_certificate_key certs/example.dev.key;
ssl_stapling on;
ssl_stapling_verify on;
proxy_buffering off;
client_max_body_size 0;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
ssl_trusted_certificate /etc/nginx/certs/ca-bundle.trust.crt;
include components/security.conf;
# gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
location / {
# When DNS Style Bucket is not enabled, you need to use the Bucket created in MinIO as the proxy Endpoint
proxy_pass http://minio:9000/bucket/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Proxy configuration for MinIO admin console
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name minio-console.example.dev;
# SSL
ssl_certificate certs/fullchain.cer;
ssl_certificate_key certs/example.dev.key;
ssl_stapling on;
ssl_stapling_verify on;
proxy_buffering off;
client_max_body_size 0;
ssl_trusted_certificate /etc/nginx/certs/ca-bundle.trust.crt;
include components/security.conf;
location / {
proxy_pass http://minio:9001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ws {
proxy_pass http://minio:9001;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
chunked_transfer_encoding off;
}
}
MinIO also supports DNS Style Bucket, but it’s not enabled by default. To enable it, use the environment variable MINIO_DOMAIN=domain.com
to set the FQDN for MinIO. For example, if your bucket name is assets, the access method would be assets.domain.com
. After enabling DNS Style Bucket mode, you no longer need to use Nginx to map subdomains to paths. Note that this method requires resolving both TLD
and *.TLD
to the MinIO proxy server.
For example, using wildcard domains in Nginx as bucket access domains:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name *.example.dev;
# SSL
ssl_certificate certs/fullchain.cer;
ssl_certificate_key certs/example.dev.key;
ssl_stapling on;
ssl_stapling_verify on;
proxy_buffering off;
client_max_body_size 0;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
ssl_trusted_certificate /etc/nginx/certs/ca-bundle.trust.crt;
include components/security.conf;
# gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
location / {
# When DNS Style Bucket is enabled, you don't need to use the Bucket created in MinIO as the URL PATH.
proxy_pass http://minio:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
If you’re using Traefik, you can refer to the following configuration:
services:
minio:
image: minio/minio:latest
labels:
- traefik.enable=true
- traefik.http.routers.minio.tls=true
- traefik.http.routers.minio.tls.certresolver=step-ca
- traefik.http.routers.minio.rule=Host(`example.dev`)
- traefik.http.routers.minio.service=minio
- traefik.http.routers.minio.entrypoints=http,https
- traefik.http.services.minio.loadbalancer.server.port=9000
- traefik.http.routers.minio-console.tls=true
- traefik.http.routers.minio-console.tls.certresolver=step-ca
- traefik.http.routers.minio-console.rule=Host(`minio-console.example.dev`) || PathPrefix(`/ws`)
- traefik.http.routers.minio-console.service=minio-console
- traefik.http.routers.minio-console.entrypoints=http,https
- traefik.http.services.minio-console.loadbalancer.server.port=9001
- traefik.http.routers.bucket.tls=true
- traefik.http.routers.bucket.tls.certresolver=step-ca
- traefik.http.routers.bucket.tls.domains[0].main=example.dev
- traefik.http.routers.bucket.tls.domains[0].sans=*.example.dev
- traefik.http.routers.bucket.rule=HostRegexp(`^.+.example.dev$`)
- traefik.http.routers.bucket.service=bucket
- traefik.http.routers.bucket.priority=10
- traefik.http.routers.bucket.entrypoints=http,https
- traefik.http.services.bucket.loadbalancer.server.port=9000
restart: no
hostname: minio
container_name: minio
networks:
- traefik
volumes:
- minio-data:/data
environment:
TZ: Asia/Shanghai
MINIO_DOMAIN: minio.svc.dev
MINIO_ROOT_USER: developer
MINIO_ROOT_PASSWORD: Developer@1994
command: server /data --console-address ":9001"
Important Notes⌗
- Make sure to add the relevant
proxy_set_header
configurations, otherwise MinIO cannot be accessed normally; - Assign a separate domain for the Bucket to perfectly simulate S3 access URLs;
Integrating MinIO in Laravel⌗
Installing Dependencies⌗
composer require league/flysystem-aws-s3-v3:~1.0
Modifying Configuration File⌗
Modify the configuration in config/filesystems.php
as follows:
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'ap-northeast-1'),
'bucket' => env('AWS_BUCKET', 'neox'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false)
]
Modifying Environment Configuration⌗
Modify the configuration in the .env
file as follows:
FILESYSTEM_DRIVER=s3
AWS_BUCKET=static # Your bucket name
AWS_URL=https://bucket.example.dev # Prefix used for generating URLs
AWS_ENDPOINT=https://minio.example.dev # URL used for uploading files
AWS_DEFAULT_REGION=ch-shanghai # Region configured in MinIO backend
AWS_ACCESS_KEY_ID=MINIO_ACCESS_KEY_ID # User Access Key ID created in MinIO backend
AWS_SECRET_ACCESS_KEY=MINIO_ACCESS_KEY_SECRET # User Access Key SECRET created in MinIO backend
AWS_USE_PATH_STYLE_ENDPOINT=true # Must be true for perfect S3 compatibility
Uploading Files⌗
$uri = Storage::put($path, $request->file('file'), ['visibility' => 'public']);
if ($uri) {
return response()->json([
'uri' => $uri,
'url' => Storage::url($uri),
'filename' => Str::afterLast($uri, '/')
]);
}
Response example:
{
"uri": "trend/reports/8q472L1asBz06mM7VK7i4gd1Kyen4eWRaAcxlmX5.jpg",
"url": "https://bucket.example.dev/trend/reports/8q472L1asBz06mM7VK7i4gd1Kyen4eWRaAcxlmX5.jpg",
"filename": "8q472L1asBz06mM7VK7i4gd1Kyen4eWRaAcxlmX5.jpg"
}
At this point, we’ve completed the integration and use of MinIO. In the production environment, we just need to modify the .env
configuration items to match the production environment settings.
Coming Soon⌗
Since our test server data is cloned from the production environment, many image resources are stored in S3. So how do we synchronize files from S3 to the MinIO service on the test server?
In the future, I’ll share my experience using MinIO CLI for synchronization, as well as using events and message queues for resource synchronization.
I hope this is helpful, Happy hacking…