- MinIO là S3-compatible object storage open source, viết bằng Go, tương thích 100% AWS S3 API, dùng được mọi SDK.
- Chạy single-node trên VPS Cloud TND đủ cho indie dev và small startup, scale lên cluster 4 node cho production HA.
- Chi phí: 1TB storage AWS S3 ~23 USD/tháng, self-host MinIO trên Cloud VPS 80 + 500GB block ~800k/tháng (rẻ 70%).
- Tính năng đầy đủ: bucket policy, IAM user, lifecycle, versioning, server-side encryption, presigned URL, event notification.
- Tích hợp dễ với Next.js, Laravel, Django qua SDK aws-sdk hay s3-cli, không cần đổi code khi migrate từ AWS S3.
AWS S3 là chuẩn industry cho object storage, nhưng đắt và data transfer egress tốn kinh khủng (0.09 USD/GB out, ai download 100GB là tốn 9 USD). Cho indie dev và small startup, MinIO là alternative tuyệt vời: self-host trên VPS, API tương thích S3 100%, không tốn egress fee. Mình đang dùng MinIO cho 5 dự án (lưu ảnh user upload, video tutorial, backup database, log archive), tổng 8TB data, chi phí dưới 2 triệu/tháng VPS.
Bài này hướng dẫn cài MinIO từ A đến Z trên Cloud VPS, tạo bucket, setup IAM, presigned URL cho frontend upload trực tiếp, lifecycle policy auto cleanup, và backup cross-region với mirror command.
1. MinIO vs AWS S3 vs Cloudflare R2 vs Backblaze B2
| Provider | Storage 1TB/tháng | Egress 1TB/tháng | Self-host |
|---|---|---|---|
| AWS S3 Standard | 23 USD | ~90 USD | Không |
| Cloudflare R2 | 15 USD | Free (no egress fee) | Không |
| Backblaze B2 | 5 USD | 1 USD (10x storage free) | Không |
| MinIO self-host VPS | ~32k (block storage) | Included bandwidth | Có |
| DigitalOcean Spaces | 5 USD/250GB | 1TB free | Không |
Cloudflare R2 sweet spot khi cần serve global. MinIO win khi muốn full control, không lock-in, và data lưu nội địa VN.
2. Cài MinIO single-node trên Cloud VPS
# Cloud VPS 80 (4GB RAM, 80GB SSD) cho data nhỏ
# Cho data lớn (500GB+) attach thêm block storage
# Tạo user dành riêng
useradd -m -s /bin/bash minio
mkdir -p /data/minio
chown minio:minio /data/minio
# Download binary
wget https://dl.min.io/server/minio/release/linux-amd64/minio -O /usr/local/bin/minio
chmod +x /usr/local/bin/minio
# mc CLI tool
wget https://dl.min.io/client/mc/release/linux-amd64/mc -O /usr/local/bin/mc
chmod +x /usr/local/bin/mc# /etc/systemd/system/minio.service
[Unit]
Description=MinIO Object Storage
After=network.target
[Service]
User=minio
Group=minio
Environment="MINIO_ROOT_USER=admin"
Environment="MINIO_ROOT_PASSWORD=ChangeThisToStrongPassword12345"
Environment="MINIO_SERVER_URL=https://s3.your-domain.com"
Environment="MINIO_BROWSER_REDIRECT_URL=https://console-s3.your-domain.com"
ExecStart=/usr/local/bin/minio server /data/minio --console-address ":9001" --address ":9000"
Restart=on-failure
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.targetsystemctl enable --now minio
systemctl status minio
journalctl -u minio -f3. Caddy reverse proxy với HTTPS
# /etc/caddy/Caddyfile
s3.your-domain.com {
reverse_proxy 127.0.0.1:9000 {
flush_interval -1
transport http {
response_header_timeout 300s
}
}
encode gzip
request_body {
max_size 5GB
}
}
console-s3.your-domain.com {
reverse_proxy 127.0.0.1:9001
encode gzip
}max_size 5GB cho upload file lớn. Reload caddy. Truy cập console-s3.your-domain.com với user/pass đã set, sẽ thấy web UI MinIO giống AWS S3 console.
4. Tạo bucket và setup mc CLI
# Setup mc alias
mc alias set local https://s3.your-domain.com admin ChangeThisToStrongPassword12345
# Tạo bucket
mc mb local/user-uploads
mc mb local/video-tutorial
mc mb local/database-backups
mc mb local/logs-archive
# List
mc ls local
mc tree local
# Enable versioning cho bucket quan trọng
mc version enable local/database-backups
# Quota nếu cần
mc quota set local/user-uploads --size 100GB5. Tạo IAM user và policy
Không dùng root credential trong app. Tạo IAM user riêng cho mỗi service.
# /opt/minio/policies/upload-only.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
"Resource": ["arn:aws:s3:::user-uploads/*"]
},
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::user-uploads"]
}
]
}
# Tạo policy
mc admin policy create local app-upload /opt/minio/policies/upload-only.json
# Tạo user
mc admin user add local app-user app-secret-key-strong-password
# Attach policy
mc admin policy attach local app-upload --user app-user
# Verify
mc admin user info local app-user6. Integrate vào Node.js với AWS SDK v3
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner// /app/services/s3.js
import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3"
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
const s3 = new S3Client({
endpoint: "https://s3.your-domain.com",
region: "us-east-1", // MinIO không quan tâm region
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY,
secretAccessKey: process.env.S3_SECRET_KEY
},
forcePathStyle: true // quan trọng cho MinIO
})
export async function uploadFile(bucket, key, body, contentType) {
await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: body,
ContentType: contentType
}))
return `https://s3.your-domain.com/${bucket}/${key}`
}
export async function getPresignedUploadUrl(bucket, key) {
const cmd = new PutObjectCommand({ Bucket: bucket, Key: key })
return getSignedUrl(s3, cmd, { expiresIn: 600 }) // 10 phút
}7. Frontend upload trực tiếp qua presigned URL
// Backend endpoint
app.post('/api/upload-url', async (req, res) => {
const key = `users/${req.user.id}/${Date.now()}-${req.body.filename}`
const url = await getPresignedUploadUrl('user-uploads', key)
res.json({ uploadUrl: url, key })
})
// Frontend
async function uploadFile(file) {
const { uploadUrl, key } = await fetch('/api/upload-url', {
method: 'POST',
body: JSON.stringify({ filename: file.name })
}).then(r => r.json())
await fetch(uploadUrl, {
method: 'PUT',
body: file,
headers: { 'Content-Type': file.type }
})
console.log(`Uploaded to key: ${key}`)
}Pattern này tránh upload qua backend Node, giảm bandwidth server. Frontend nói chuyện thẳng MinIO.
8. Lifecycle policy auto cleanup
# /opt/minio/lifecycle/logs-archive.json
{
"Rules": [
{
"ID": "DeleteOldLogs",
"Status": "Enabled",
"Expiration": {
"Days": 90
},
"Filter": { "Prefix": "" }
},
{
"ID": "TransitionToArchive",
"Status": "Enabled",
"Transition": {
"Days": 30,
"StorageClass": "STANDARD_IA"
}
}
]
}
mc ilm import local/logs-archive < /opt/minio/lifecycle/logs-archive.jsonLog lưu 30 ngày standard, sau đó move sang IA (Infrequent Access), 90 ngày auto xoá. Tiết kiệm storage rất nhiều cho data tạm.
9. Server-side encryption
# Bật SSE-S3 (MinIO tự manage key)
mc encrypt set sse-s3 local/database-backups
# Hoặc SSE-KMS với external KMS
export MINIO_KMS_KES_ENDPOINT=https://kes.your-domain.com:7373
export MINIO_KMS_KES_CERT_FILE=/etc/minio/kes.crt
export MINIO_KMS_KES_KEY_FILE=/etc/minio/kes.key
systemctl restart minioCho compliance nghiêm ngặt (FinTech, HealthTech), dùng KES external. Cho indie/startup, SSE-S3 đủ.
10. Backup MinIO sang Cloudflare R2 cross-region
# Setup alias R2
mc alias set r2 https://abc123.r2.cloudflarestorage.com YOUR_R2_KEY YOUR_R2_SECRET
# Mirror sync hằng đêm
mc mirror --overwrite --remove local/user-uploads r2/backup-user-uploads
# Cron job
crontab -e
0 3 * * * /usr/local/bin/mc mirror --overwrite --remove local/user-uploads r2/backup-user-uploadsR2 không tốn egress fee, là backup destination lý tưởng. Khi VPS chết, restore từ R2 về MinIO mới bằng lệnh mc mirror ngược lại.
11. Cluster MinIO 4 node cho HA production
Single node OK cho indie. Cho production critical, dùng 4 node MinIO Erasure Coding:
# Trên 4 VPS Cloud 80 (4GB RAM, 200GB block storage mỗi node)
# Mỗi node chạy:
minio server http://node{1...4}.your-domain.com/data/disk{1...4}
# Erasure coding 4+2 chịu được mất 2 disk/node mà data vẫn intactSetup cluster tốn 4x giá single node nhưng đổi lại HA, scale dung lượng và bandwidth tốt hơn. Mình recommend chỉ dùng cluster khi data quá quan trọng (HIPAA, PCI) hoặc traffic rất lớn.
12. Monitoring với Prometheus
# MinIO expose /minio/v2/metrics/cluster
# Add vào prometheus.yml:
scrape_configs:
- job_name: minio
metrics_path: /minio/v2/metrics/cluster
bearer_token: $(mc admin info local --json | jq -r .info.scrapeBearerToken)
static_configs:
- targets: ['127.0.0.1:9000']
# Grafana dashboard ID: 13502 (MinIO Cluster)Monitor disk usage, IOPS, latency, errors. Alert khi disk > 80% hay node down.
13. Use case thực tế và best practice
- User upload ảnh/video: bucket user-uploads với presigned URL, lifecycle 1 năm.
- Database backup: bucket db-backups với versioning, encryption, retention 90 ngày.
- Log archive: bucket logs với lifecycle move IA 30 ngày, xoá 1 năm.
- Static asset CDN: bucket public với policy public-read, Caddy + Cloudflare cache.
- Video stream: bucket videos, Caddy reverse proxy với header Range cho HTTP partial.
- AI model weights: bucket models, version mỗi lần update, share giữa các node training.
14. Pitfall thường gặp
- forcePathStyle: true bắt buộc cho MinIO. Quên là 404 not found mọi request.
- Disk full: MinIO không gửi alert tự động. Monitor riêng và alert khi 80%.
- Bucket policy public: cẩn thận, có thể leak file private. Default keep private, presigned URL cho user truy cập.
- Backup quên dùng versioning: file bị override không recover được. Bật versioning cho bucket critical.
- CORS lỗi khi upload từ browser: set CORS policy cho bucket allow origin frontend domain.
15. Migration từ AWS S3 sang MinIO
mc alias set aws https://s3.amazonaws.com AKIA... your-secret
mc alias set local https://s3.your-domain.com admin password
# Mirror toàn bộ
mc mirror --preserve aws/my-prod-bucket local/migrated-bucket
# Đổi app config từ AWS endpoint sang MinIO endpoint
# Test, switch DNS nếu cần
# Tắt AWS bucket sau khi confirm OKMình migrate 4 dự án từ S3 sang MinIO trong 2024, tổng data 12TB, downtime mỗi dự án dưới 10 phút (switch endpoint trong env).
16. Cost breakdown indie hacker thực tế
| Setup | Capacity | Cost/tháng |
|---|---|---|
| VPS Cloud 80 + 80GB SSD | ~70GB usable (after MinIO overhead) | 799k |
| VPS Cloud 80 + 500GB block | ~480GB usable | ~1.300k (tuỳ block storage giá) |
| VPS Cloud 160 + 1TB block | ~970GB usable | ~2.500k |
| So sánh AWS S3 1TB + 1TB egress | 1TB | ~113 USD ~2.800k |
Self-host MinIO tiết kiệm 30-50% cho 1TB, tiết kiệm 70%+ nếu egress nhiều. Càng nhiều traffic càng có lợi.
17. CORS policy cho frontend upload
# /opt/minio/cors/user-uploads.json
[
{
"AllowedOrigin": ["https://app.your-domain.com"],
"AllowedMethod": ["GET", "PUT", "POST", "DELETE", "HEAD"],
"AllowedHeader": ["*"],
"ExposeHeader": ["ETag", "x-amz-request-id"],
"MaxAgeSeconds": 3600
}
]
# Apply
mc cors set local/user-uploads /opt/minio/cors/user-uploads.jsonKhông set CORS thì browser block upload presigned URL với lỗi "Access-Control-Allow-Origin missing". Đây là pitfall hay gặp khi mới setup.
18. Event notification webhook
MinIO gửi webhook khi có event (upload, delete) tới endpoint app, để xử lý async (gen thumbnail, scan virus, update DB):
mc admin config set local notify_webhook:1 endpoint="https://app.your-domain.com/webhooks/s3-upload" auth_token="secret_webhook_token"
mc admin service restart local
# Add event subscription
mc event add local/user-uploads arn:minio:sqs::1:webhook --event "s3:ObjectCreated:*"Khi user upload ảnh, MinIO POST tới webhook, app tạo thumbnail trong background. Pattern này tách concerns rất tốt.
19. CDN edge cache cho MinIO public bucket
Bucket public (asset, hình sản phẩm) qua Cloudflare CDN free tier, edge cache giúp serve global nhanh, giảm load MinIO server:
- Trỏ CNAME cdn.your-domain.com sang s3.your-domain.com (proxied qua Cloudflare).
- Cloudflare Page Rule: Cache Level Cache Everything, Edge TTL 1 month.
- Origin gửi header Cache-Control: public, max-age=31536000, immutable cho asset versioned.
- Cloudflare cache hit ratio 90%+, MinIO chỉ phục vụ 10% request gốc.
20. Bài học sau 2 năm chạy MinIO production
- Disk I/O là bottleneck. SSD CEPH của TND đủ cho 99% workload, NVMe local nhanh hơn nhưng không cần thiết.
- Backup quan trọng hơn HA. Single node + daily backup R2 rẻ và đủ tốt cho indie. Cluster cho production critical.
- Bandwidth ăn nhiều khi serve video/large file. Combine với Cloudflare CDN free tier.
- Versioning + lifecycle cứu nhiều vụ accidental delete.
- Update MinIO version cẩn thận, test trên staging trước. Đôi khi có breaking change.
Sau 2 năm chạy MinIO cho 5 dự án, không có data loss nghiêm trọng nào. Tiết kiệm ước tính 30 triệu/năm so với dùng AWS S3 đầy đủ. Self-host object storage là quyết định đúng cho indie và startup VN.
VPS chạy MinIO thay AWS S3 cho dev solo
Cloud VPS TND sẵn AlmaLinux 9, Ubuntu 22/24, Debian 12/13. SSD CEPH, snapshot 1-click, backup hằng ngày, network 200Mbps trong nước. Cloud VPS 80 (4GB RAM, 80GB SSD) đủ cho 50-70GB object data. Scale lên Cloud VPS 160 + block storage cho TB-scale.
Xem 8 cấu hình Cloud VPS →FAQ
MinIO có 100% tương thích S3 SDK không?
~95% API tương thích, bao gồm tất cả call phổ biến (PutObject, GetObject, MultipartUpload, presigned URL). Một số tính năng AWS-specific như S3 Select, Object Lock variations, Glacier Deep Archive thì MinIO không có. Cho 99% use case dev thông thường, MinIO swap-in được không đổi code.
RAM tối thiểu cho MinIO là bao nhiêu?
512MB RAM chạy được nhưng chậm. Khuyến nghị 2GB+ cho single node, 4GB+ cho production. RAM ít ảnh hưởng nhiều hơn CPU vì MinIO cache hot object trong memory.
MinIO có tính phí license không?
Community version AGPL v3 hoàn toàn free, đủ feature cho indie và startup. Enterprise SUBNET license trả tiền cho support 24/7, audit log nâng cao, performance tuning chuyên gia. Cho 90% use case, community version đủ dùng.
Có chạy MinIO trên Cloud VPS 20 (199k) được không?
Được cho hobby và dev, không recommend production. 1GB RAM + 20GB SSD đủ chạy thử nghiệm hoặc lưu vài chục GB asset cá nhân. Production indie startup nên Cloud VPS 40 hoặc 80.
So với Cloudflare R2 thì MinIO có gì khác?
R2 managed, không egress fee, global edge. MinIO self-host, full control, data nội địa. R2 tốt cho global SaaS phục vụ user nhiều châu lục. MinIO tốt cho data sensitive cần lưu VN (compliance) hoặc workload internal không expose internet.
21. Roadmap nâng cấp dài hạn
Bắt đầu single node, đến khi data lên 500GB và uptime quan trọng thì migrate cluster 4 node với erasure coding. Đầu tư monitoring Prometheus + Grafana từ đầu để biết khi nào cần scale. Combine với CDN Cloudflare cho asset public và backup cross-region R2 cho disaster recovery. Setup này đã giúp nhiều indie hacker Việt Nam tiết kiệm hàng chục triệu mỗi năm so với dùng AWS S3 đầy đủ trong khi vẫn đảm bảo độ tin cậy đủ tốt cho production.



