Backend14 min read3 views

Apa itu MinIO (Object Storage)

Masih menyimpan file upload di server lokal? Saatnya beralih ke Object Storage. Pelajari konsep MinIO, S3-compatible API, dan best practice arsitektur modern.

Halo! Sini duduk sebentar. Hari ini saya mau bahas sesuatu yang sering banget jadi batu sandungan buat developer yang baru transisi dari membuat aplikasi skala kecil ke aplikasi skala menengah atau enterprise.

Kita akan bahas tentang Object Storage, spesifiknya MinIO, dan kenapa mulai sekarang kamu harus merubah cara berpikirmu tentang bagaimana aplikasi menyimpan file.

Jangan Simpan Semua File di Server

Bayangkan skenario ini: Kamu baru saja mendeploy aplikasi pertamamu. Ada fitur upload foto profil dan dokumen. Sebagai jalan paling cepat, kamu buat folder /var/www/uploads di server, lalu mengarahkan kode aplikasimu untuk menyimpan semua file upload user ke sana.

Awalnya berjalan lancar. Tapi beberapa bulan kemudian, aplikasimu meledak dan trafiknya naik tajam. Apa yang terjadi?

  1. Server Penuh: Disk space-mu habis. Aplikasi crash karena tidak bisa menulis log.
  2. Scaling Jadi Mimpi Buruk: Karena trafik tinggi, kamu tambah satu server lagi (Server B) di belakang Load Balancer. Tapi, kalau user A upload foto profil dan request itu masuk ke Server A, fotonya akan tersimpan di Server A. Ketika user A refresh halaman dan kebetulan diarahkan ke Server B, fotonya hilang! File-nya tidak sinkron.
  3. Docker Ephemeral: Kamu mulai pakai Docker. Kamu redeploy container-nya, dan boom! Semua file upload user hilang karena sifat container yang ephemeral (sementara).
  4. Backup yang Sulit: Setiap kali kamu mau mem-backup source code, kamu harus mem-filter folder uploads yang ukurannya sudah ratusan Gigabyte. File aplikasi dan file user bercampur aduk.
Paham kan masalahnya? Local filesystem itu bagus untuk development, tapi jadi bencana saat aplikasi mulai scale. Kita butuh tempat terpisah yang khusus menangani file. Di situlah kita berkenalan dengan Object Storage.

Apa Itu Object Storage?

Mari kita luruskan dulu konsep dasarnya. Kalau kamu biasa menyimpan file di Windows atau Mac, kamu pakai Filesystem.
Filesystem itu ibarat lemari baju. Kamu punya rak (direktori), kotak di dalam rak (sub-direktori), dan baju di dalamnya (file). Kalau mau mencari baju, kamu harus tahu hirarkinya: Lemari > Rak 2 > Kotak Merah > Baju.jpg.

Object storage tidak bekerja seperti itu. Object storage itu ibarat gudang raksasa yang datar.
Tidak ada folder atau sub-folder yang sebenarnya. Setiap barang (file) yang masuk ke gudang ini langsung diberi label unik, isi barangnya, dan catatan tentang barang tersebut.

Di Object Storage, setiap file disebut Object, dan memiliki tiga hal:

  1. Key: Nama unik pengenalnya (contoh: 2026/08/baju.jpg).
  2. Data: Isi file-nya itu sendiri (binary).
  3. Metadata: Informasi tambahan tentang file tersebut (contoh: di-upload oleh siapa, ukurannya berapa, formatnya apa).
Object-object ini disimpan di dalam sebuah wadah besar yang disebut Bucket.

Plaintext
Bucket: app-uploads
 ├── object-1 (Key: user-1-profile.jpg)
 ├── object-2 (Key: invoice-001.pdf)
 └── object-3 (Key: backup.zip)
Ingat: Object storage bukanlah database. Database menstrukturkan data tekstual dan relasional. Object storage menyimpan file/binary dalam jumlah masif dengan arsitektur yang sangat scalable.

Apa Itu MinIO?

Sekarang masuk ke alatnya. MinIO adalah salah satu software Object Storage yang open-source dan High Performance.

Bukan cuma sekadar "tempat nyimpen file", MinIO didesain untuk menangani file statis apa pun: image, PDF, video, document, backup database, log sistem, hingga model Machine Learning (AI).

Kenapa senior engineer suka banget pakai MinIO?

  • Self-hosted: Kamu bisa install MinIO di VPS kamu sendiri. Tidak harus bayar mahal ke cloud provider kalau budget masih terbatas.
  • S3-Compatible: (Ini yang paling penting, saya bahas khusus di poin selanjutnya).
  • Sangat Ringan & Cepat: Ditulis dengan bahasa Go, jalan di Docker dengan sangat mulus.
  • Cocok untuk Kubernetes: MinIO adalah warga kelas satu di ekosistem Cloud-Native.

Memahami Konsep "S3-Compatible"

Kamu pasti pernah dengar Amazon S3 (Simple Storage Service). S3 adalah standar industri untuk object storage di AWS.

Nah, karena S3 sangat populer, API (cara aplikasi berkomunikasi dengan S3) menjadi standar de facto. Bayangkan S3 API ini sebagai sebuah "Bahasa".

MinIO adalah S3-Compatible. Artinya, MinIO "berbicara" dengan bahasa yang persis sama dengan Amazon S3.

Kenapa ini luar biasa? Karena kalau aplikasimu sudah diprogram menggunakan SDK/Library Amazon S3 (berbicara bahasa S3), aplikasimu bisa langsung dihubungkan ke MinIO tanpa perlu ubah kode aplikasinya secara signifikan. Kamu cuma perlu ganti URL Endpoint dan kredensialnya.

Plaintext
  Application (Menggunakan S3 SDK)
           |
           | Berbicara "Bahasa S3"
           v
       ┌───────────┐
       | S3 API    |
       └─┬───────┬─┘
         |       |
    Bisa ke      Atau ke
         v       v
      MinIO     Amazon S3
Abstraksi ini membuat kita bebas dari vendor lock-in. Mulai development pakai MinIO di lokal, saat production kalau ada budget tinggal switch ke AWS S3, atau tetap scale MinIO di server sendiri. Kodenya? Sama persis!

Komponen Penting MinIO

Biar kamu nggak bingung pas lihat dashboard atau dokumentasinya, pahami istilah ini:

  • Server: Mesin atau container tempat MinIO berjalan.
  • Client: Aplikasimu (Backend API) yang request untuk simpan/ambil file.
  • Bucket: Wadah utama. Biasanya satu aplikasi dipisah per bucket (misal: bucket-staging, bucket-prod).
  • Object: File yang kamu simpan (foto, pdf).
  • Object Key: String unik untuk mengidentifikasi object.
  • Metadata: Data tentang data (contoh: Content-Type: image/jpeg).
  • Access Key & Secret Key: Username dan Password untuk API. Jangan pernah bocor!
  • Endpoint: URL tempat MinIO server bisa diakses (misal: http://localhost:9000).
  • Policy: Aturan siapa yang boleh baca/tulis ke bucket tertentu.
Catatan penting tentang Object Key:
Walaupun kamu melihat struktur seperti ini di dashboard MinIO:

Plaintext
users/
  123/
    profile.jpg
Itu BUKAN folder users yang di dalamnya ada folder 123.
Object storage itu datar. Key-nya secara harafiah adalah string panjang: "users/123/profile.jpg". Tanda garis miring / hanya visualisasi UI agar manusia lebih mudah membacanya.

MinIO vs Local Filesystem

Kapan pakai apa? Coba lihat perbandingan ini:

Aspek Local Filesystem (/var/www) MinIO (Object Storage)
Deployment Sangat mudah (langsung jalan) Butuh setup awal (Docker/Server)
Multi-Server Sulit (butuh NFS/rsync) Sangat cocok, terpusat
S3 API Tidak ada Ya, standar industri
Docker/K8s Rawan data hilang (butuh volume mapping kompleks) Sangat cocok (Stateless App)
Metadata File Terbatas (dari OS) Kaya (bisa custom metadata)
Presigned URL Tidak ada (native) Tersedia
 
Kapan local filesystem masih masuk akal?
Kalau aplikasimu cuma internal tool kecil, jalan di satu server VPS murah, tidak akan pernah di-scale ke dua server, dan butuh kecepatan setup 5 menit. Jangan over-engineering kalau memang tidak perlu.

Tapi untuk aplikasi modern yang serius? Wajib Object Storage.

Arsitektur Aplikasi dengan MinIO

Kalau aplikasimu pakai MinIO, begini arsitektur standarnya.

Plaintext
User
  |
  v
Frontend (Web/Mobile)
  |
  v
Backend API (Laravel/Go/Node.js)
  |
  +------------------> PostgreSQL (Menyimpan Metadata/Referensi)
  |
  +------------------> MinIO (Menyimpan File Fisiknya)
Aturan Emas: Database hanya menyimpan informasi (referensi) tentang file. File binernya (binary) masuk ke MinIO.

Contoh baris di PostgreSQL (table users):

  • id: 123
  • name: Budi
  • avatar_path: avatars/users/123/profile-xyz.jpg (Ini adalah Object Key!)
MinIO menyimpan file-nya di key avatars/users/123/profile-xyz.jpg.

Upload File: Apa yang Sebenarnya Terjadi?

Banyak junior salah paham tentang cara kerja upload. Alurnya seharusnya begini:

  1. Frontend kirim gambar ke Backend API.
  2. Backend API memvalidasi (ukurannya oke? formatnya beneran gambar?).
  3. Backend meng-generate Object Key unik (misal pakai UUID).
  4. Backend mengirim file ke MinIO menggunakan S3 Client.
  5. MinIO merespons "OK".
  6. Backend menyimpan Object Key tersebut ke Database (PostgreSQL).
  7. Backend membalas Frontend "Upload Sukses".
Kenapa harus me-rename nama asli?
Jangan pernah pakai nama asli file! Bayangkan ada 3 user berbeda meng-upload file dengan nama profile.jpg.
Kalau kamu pakai nama aslinya sebagai Object Key, file user 1 akan ditimpa (di-overwrite) oleh file user 2, dan ditimpa lagi oleh user 3.
Selalu generate UUID: users/123/profile/f47ac10b-58cc.jpg.

Download dan Access File

Untuk menampilkan file ke user, ada tiga cara:

  1. Bucket Public: File bisa diakses siapa saja lewat URL MinIO. Cocok untuk aset web (logo, banner).
  2. Backend sebagai Proxy: Frontend request ke Backend, Backend tarik dari MinIO, lalu Backend kasih ke Frontend. (Aman, tapi membebani RAM/Bandwidth server Backend).
  3. Presigned URL (Paling Direkomendasikan):
Apa itu Presigned URL?
Ini adalah URL ajaib yang dibuat oleh Backend-mu, yang memberikan izin sementara (misal: 15 menit) bagi siapa pun yang punya URL tersebut untuk men-download (atau upload) satu file spesifik dari MinIO langsung, tanpa membebani Backend.

Plaintext
User request file -> Backend verifikasi hak akses -> 
Backend generate Presigned URL (tanpa download file) -> 
User download langsung dari MinIO memakai URL tersebut.
Ini sangat krusial untuk file private seperti KTP atau invoice.

Integrasi MinIO dengan Laravel

Laravel punya filesystem abstraction yang luar biasa. Kamu tinggal atur .env:

Cuplikan kode
FILESYSTEM_DISK=s3

AWS_ACCESS_KEY_ID=minioadmin
AWS_SECRET_ACCESS_KEY=minioadmin
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=app-storage
AWS_ENDPOINT=http://localhost:9000
AWS_USE_PATH_STYLE_ENDPOINT=true
Catatan: AWS_USE_PATH_STYLE_ENDPOINT=true wajib untuk MinIO karena secara default MinIO tidak menggunakan subdomain style seperti AWS S3.

Di kode Controller-mu, ini sesederhana:

PHP
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

// Generate nama unik
$fileName = Str::uuid() . '.' . $request->file('avatar')->getClientOriginalExtension();
$objectKey = 'users/' . $userId . '/' . $fileName;

// Upload ke MinIO (S3 Disk)
Storage::disk('s3')->put(
    $objectKey,
    file_get_contents($request->file('avatar'))
);

// Simpan $objectKey ke Database...
Lihat betapa bersihnya? Kode ini tidak peduli di belakangnya MinIO atau AWS S3. Abstraksinya jalan sempurna.

Integrasi MinIO dengan Golang

Kalau kamu pakai Go, kamu bisa pakai official MinIO SDK atau AWS SDK (karena S3 compatible). Ini contoh pakai minio-go/v7:

Go
package main

import (
	"context"
	"log"
	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
)

func main() {
	endpoint := "localhost:9000"
	accessKeyID := "minioadmin"
	secretAccessKey := "minioadmin"

	// Init S3 Client
	minioClient, err := minio.New(endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
		Secure: false, // true untuk HTTPS (production)
	})
	if err != nil {
		log.Fatalln(err)
	}

	// Upload Object
	bucketName := "app-storage"
	objectName := "documents/report-xyz.pdf"
	filePath := "/tmp/report-xyz.pdf"

	info, err := minioClient.FPutObject(context.Background(), bucketName, objectName, filePath, minio.PutObjectOptions{ContentType: "application/pdf"})
	if err != nil {
		log.Fatalln(err)
	}

	log.Printf("Successfully uploaded %s of size %d\n", objectName, info.Size)
}
Sangat terstruktur. Kamu bikin Client, lalu gunakan method FPutObject atau PutObject untuk melempar file-nya.

MinIO dengan Docker

Di development, kita biasanya menjalankan MinIO pakai Docker. Ini contoh docker-compose.yml yang standar:

YAML
version: '3.8'
services:
  minio:
    image: minio/minio
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin
    ports:
      - "9000:9000"  # API Port
      - "9001:9001"  # Console/Web UI Port
    volumes:
      - minio_data:/data # INI SANGAT PENTING!

volumes:
  minio_data:
Bedah baris penting:

  • port 9000: Ini port API. Aplikasimu (Laravel/Go) nembak ke sini.
  • port 9001: Ini Web UI/Console. Kamu bisa buka di browser untuk lihat-lihat bucket secara visual.
  • volumes: minio_data:/data: Tanpa ini, setiap kali container MinIO mati atau di-restart, semua file upload kamu HILANG. Persistent volume memastikan file tetap ada secara fisik di host machine.

MinIO dalam Production

MinIO di localhost dan MinIO production adalah dua konteks yang sama sekali berbeda! Di production, kamu tidak bisa asal docker up lalu ditinggal tidur.

Kamu harus memikirkan:

  • HTTPS: API S3 wajib pakai SSL/TLS.
  • Access Management: Jangan pakai akun root (MINIO_ROOT_USER) di aplikasi. Buat User khusus di MinIO yang policy-nya hanya bisa Read/Write ke bucket tertentu (Least Privilege).
  • Persistent Disk: Gunakan block storage yang reliabel, dengan konfigurasi RAID atau ter-backup berkala.
  • High Availability: Di production skala besar, MinIO di-deploy dalam mode Distributed (minimal 4 node) agar kalau 1 server mati, file tetap aman (Disaster Recovery).

MinIO + Nginx

Biar aman dan rapi, MinIO di production biasanya disembunyikan di belakang Reverse Proxy seperti Nginx.

Plaintext
Internet
   | (HTTPS)
   v
Nginx (Termination SSL)
   |
   +----> api.example.com (Ke Backend Laravel/Go)
   |
   +----> storage.example.com (Ke MinIO port 9000)
   |
   +----> console.storage.example.com (Ke MinIO UI port 9001)
Nginx yang akan memegang SSL Certificate (misal dari Let's Encrypt). Komunikasi dari Nginx ke MinIO di internal network bisa menggunakan HTTP biasa. Ini membuat manajemen domain dan sertifikat jauh lebih mudah.

Security (Keamanan)

Ini area di mana banyak yang kecolongan.

  • Jangan Expose Secret Key: Secret Key ibarat password database. Jangan taruh di Frontend (React/Vue/Mobile App). Komunikasi ke MinIO harus selalu lewat Backend-mu, atau lewat Presigned URL yang di-generate Backend.
  • Jangan Hardcode Credential: Selalu gunakan Environment Variable (.env).
  • Batasi Bucket Policy: Jangan buat semua bucket "Public". Bucket KTP/Invoice harus "Private".
  • Validasi File di Backend: Jangan percaya request upload user. Cek MIME type-nya, batasi ukurannya (misal maksimal 5MB). File berbahaya (seperti .php atau .exe palsu) bisa merusak sistem jika tidak difilter.

Kesalahan yang Sering Dilakukan Junior Developer

Sebagai mentor, ini hal-hal yang sering saya temui saat melakukan Code Review:

  1. Menyimpan binary file besar langsung di PostgreSQL: (BLOB). Kenapa salah? DB jadi super berat, backup jadi lama, memory habis. Solusi: Simpan file di MinIO, simpan URL/Key di DB.
  2. Menggunakan nama file asli sebagai Object Key: profile_pic.jpg. Dampaknya: File ketimpa sama user lain. Solusi: Pakai UUID.
  3. Membuat bucket KTP menjadi public: Dampaknya: Data bocor, aplikasi masuk berita (kasus kebocoran data). Solusi: Bucket private + Presigned URL.
  4. Menaruh Access Key MinIO Production di Git/Github: Dampaknya: Hacker bot akan scan repo-mu, masuk ke MinIO kamu, hapus semua datamu, dan minta tebusan ransomware. Solusi: Selalu ignore .env.
  5. Tidak memikirkan ukuran file: User upload video 1 GB langsung ke RAM backend sebelum di-forward ke MinIO. Dampaknya: Server langsung Out of Memory. Solusi: Kasih batasan upload size di API, atau gunakan Presigned Upload URL agar user langsung upload ke MinIO.

Database Schema untuk File Storage

Walaupun file disimpan di MinIO, database tetap raja untuk manajemen metadata. Biasanya saya membuat satu tabel khusus untuk menyimpan record semua file:

SQL
CREATE TABLE files (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    original_name VARCHAR(255) NOT NULL,
    object_key TEXT NOT NULL,
    bucket VARCHAR(100) NOT NULL,
    mime_type VARCHAR(100),
    size_bytes BIGINT,
    uploaded_by UUID REFERENCES users(id),
    created_at TIMESTAMP DEFAULT NOW()
);
Dengan skema ini, hubungan relasionalmu aman. Kalau table users butuh foto profil, tinggal beri foreign key profile_file_id yang me-refer ke tabel files.

Studi Kasus: Mini Bank Application

Mari kita rangkum pemahaman kita dalam kasus nyata. Kamu membuat aplikasi Mini Bank.
Aplikasi ini punya fitur KYC (Verifikasi identitas) yang butuh foto KTP dan foto Selfie, serta fitur download bukti transfer (receipt).

Arsitektur:

Plaintext
                    ┌──────────────┐
                    │  React App   │
                    └──────┬───────┘
                           │ (Upload KTP)
                           v
                    ┌──────────────┐
                    │   Go API     │ (Verifikasi Ukuran/Tipe)
                    └──────┬───────┘
                           │
             (1) Save Data │ (2) PutObject
                           v
         ┌──────────────┐      ┌──────────────┐
         │ PostgreSQL   │      │    MinIO     │
         └──────────────┘      └──────────────┘
Alur KYC:

  1. User upload KTP (gambar) lewat React.
  2. Go API menerima. Generate UUID: kyc-documents/user-99/ktp-ab12cd.jpg.
  3. Go API melempar file tersebut ke MinIO ke bucket bank-private.
  4. Go API menyimpan object_key tersebut ke PostgreSQL di tabel user_kyc.
  5. Saat tim admin bank mau lihat KTP tersebut lewat internal dashboard, Go API men-generate Presigned URL yang valid selama 30 menit dan mengirimkannya ke dashboard.
Tidak ada satu pun file KTP yang bocor ke publik. Sangat rapi.

MinIO + Kubernetes

Ketika aplikasimu sudah sangat masif dan masuk ke Kubernetes (K8s), MinIO juga bisa ikut masuk.

Di Kubernetes, aplikasi API kita (Laravel/Go) bersifat Stateless (kalau di-restart tidak masalah). Tapi MinIO adalah Stateful workload. File tidak boleh hilang.
Oleh karena itu, di K8s, MinIO membutuhkan:

  • Persistent Volume (PV) & Persistent Volume Claim (PVC): Ini untuk meminta "Hard disk" permanen dari cloud provider (misal AWS EBS atau GCP Persistent Disk) agar menempel ke Pod MinIO.
  • StatefulSet: MinIO di-deploy bukan sebagai Deployment biasa, melainkan StatefulSet agar network identity dan urutan disk-nya terjaga.
Pemahaman dasarnya: K8s mengatur container-nya, tapi storage fisiknya tetap dijaga secara permanen di luar siklus hidup Pod.

Kapan Menggunakan MinIO dan Kapan Tidak?

Gunakan MinIO jika:

  • Kamu membutuhkan abstraksi S3 API untuk fleksibilitas di masa depan.
  • Kamu butuh fitur Presigned URL untuk keamanan file private.
  • Kamu ingin self-host object storage di VPS karena biaya AWS S3 terlalu mahal untuk bandwidth aplikasimu.
  • Aplikasimu di-deploy menggunakan Docker atau Kubernetes (arsitektur stateless).
  • Kamu berurusan dengan banyak aset statis/biner (gambar, PDF, video).
Pertimbangkan alternatif (seperti local filesystem biasa) jika:

  • Aplikasimu hanya blog WordPress sederhana atau web profil perusahaan yang statis.
  • Kamu hanya perlu menyimpan 10-20 gambar kecil.
  • Kamu menghindari operational complexity (tidak mau pusing me-maintain container tambahan).

Mental Model yang Harus Dipahami Junior

Sebagai penutup teknis, saya ingin meluruskan "Mental Model"-mu.
Jangan berpikir MinIO sebagai folder online.

MinIO (dan Object Storage pada umumnya) adalah sebuah Service / Layanan berbasis API.
Kamu tidak "menyalin (copy)" file ke MinIO seperti kamu memindahkan file ke Flashdisk. Kamu berkomunikasi dengan HTTP API (lewat S3 SDK) untuk me-request agar layanan MinIO membuat, mengambil, atau menghapus sebuah Object.

Semuanya API-driven.

Plaintext
Application ---> HTTP (S3 Protocol) ---> Object Storage (MinIO)
                                              |
                                              +--- Bucket (Namespace)
                                              +--- Object (File)
                                              +--- Metadata
                                              +--- Policy (Izin Akses)

Best Practice Checklist

Saat kamu mau mengimplementasikan MinIO di proyekmu berikutnya, cek daftar ini:

  • [ ] Bucket strategy sudah jelas (mana yang public, mana yang private).
  • [ ] Object key menggunakan UUID atau identifier unik, tidak pernah original file name.
  • [ ] Kredensial (Access/Secret Key) tidak di-hardcode di source code, melainkan pakai .env.
  • [ ] HTTPS/SSL diaktifkan di sisi MinIO atau melalui Nginx (Production).
  • [ ] Database men-tracking informasi object key dan bucket.
  • [ ] Akses file private secara ketat menggunakan Presigned URL.
  • [ ] Menggunakan Persistent Volume di Docker/K8s.
  • [ ] Ukuran upload maksimal (file size limit) diaktifkan di Backend/Reverse Proxy.
  • [ ] MIME type dan ektensi file divalidasi dengan ketat di Backend sebelum diteruskan ke MinIO.
  • [ ] Menggunakan akun IAM / User Policy khusus (bukan root user) untuk aplikasi.

Kesimpulan

Nah, sekarang kamu sudah paham gambar besarnya. Object storage seperti MinIO bukan sekadar teknologi keren-kerenan. Ini adalah komponen fundamental dari pemisahan tanggung jawab (Separation of Concerns) dalam arsitektur modern.

  • Application Server (Laravel/Go) khusus menangani komputasi dan business logic.
  • Database (PostgreSQL/MySQL) khusus menangani data terstruktur dan relasional.
  • Object Storage (MinIO/S3) khusus menangani bongkahan file binary (gambar, dokumen).
Dengan memecah beban seperti ini, kamu bisa men-scale ketiga komponen tersebut secara terpisah dan independen seiring berjalannya waktu.

Jadi, jangan simpan file di /var/www/uploads lagi, ya? Paham kan? Sip! Selamat ngoding!