Membatasi dan menyetel resource container melalui flag --memory, --cpus, --pids-limit dan --ulimit, memahami perilaku OOM-killer, membaca docker stats untuk capacity planning, serta merencanakan limit yang tepat untuk database, cache, dan web server di atas cgroups v2.

Setelah di episode 24 sebelumnya kita merapikan sisi infrastruktur — konfigurasi daemon, rotasi log, migrasi data-root, dan Docker contexts — pada episode kali ini kita beralih ke hal yang menentukan kualitas hidup semua container di dalam host: resource. Kalian sudah bisa menjalankan container dengan docker run, tapi pertanyaannya sekarang: berapa banyak CPU dan memory yang diizinkan untuk satu container?
Mengapa topik ini begitu penting? Bayangkan sebuah apartemen tanpa aturan tentang pemakaian air dan listrik: penghuni yang paling boros akan membuat semua tetangganya menderita. Hal yang sama terjadi di server Docker. Satu container aplikasi yang tiba-tiba bocor memory karena bug, atau satu worker yang mengkonsumsi seluruh CPU karena loop tak berujung, bisa membuat seluruh host tidak responsif — termasuk container database yang harusnya melayani ribuan request. Inilah yang disebut noisy neighbor problem. Di Kubernetes, penjadwal menangani ini lewat resource requests/limits; di Docker, kalian memegang kendali penuh lewat flag dan opsi cgroups yang akan kita bedah di episode ini.
Sebelum membahas flag, kalian perlu tahu di mana batasan itu diterapkan. Docker tidak menciptakan mekanisme limitnya sendiri — ia memanfaatkan cgroups (control groups), fitur kernel Linux yang mengelompokkan proses dan membatasi pemakaian CPU, memory, dan PID di dalam tiap kelompok. Di kernel modern, implementasinya adalah cgroups v2, yang ter-mount di /sys/fs/cgroup.
grep cgroup /proc/filesystems
cat /sys/fs/cgroup/cgroup.controllersSetiap container yang berjalan akan muncul sebagai direktori di bawah /sys/fs/cgroup/ dengan identitas berdasarkan cgroupnya. Ketika kalian mengetik docker run --memory 512m, Docker menerjemahkannya menjadi penulisan nilai ke file memory.max (di cgroups v2) pada cgroup container tersebut. Kernellah yang menegakkan batasannya, bukan aplikasi — aplikasi tidak bisa "menawar" keluar dari batasan ini. Inilah kekuatan container: isolasi resource dijamin oleh kernel, bukan oleh kemauan aplikasi.
Tiga flag utama untuk memory:
--memory (atau -m): batas keras (hard limit) RAM yang boleh dipakai container.--memory-swap: total memory + swap yang diizinkan. Nilai ini harus lebih besar dari --memory; jika set sama dengan --memory, swap dimatikan total.--memory-reservation: soft limit — sebatas "anjuran" yang longgar saat host tidak sedang tertekan; ketika host kehabisan memory, kernel akan menekan container mendekati nilai ini.docker run -d --name api \
--memory 512m \
--memory-swap 1g \
--memory-reservation 256m \
nginxWarning
Jebakan paling umum di dunia nyata: menyetel --memory tetapi lupa --memory-swap. Ketika --memory-swap tidak disetel namun --memory disetel, Docker memberi nilai swap sebesar dua kali lipat --memory. Konsekuensinya, aplikasi yang "tenang" saat RAM penuh justru memakai swap secara besar-besaran — yang bagi workload latency-sensitive (database, cache) bisa jauh lebih lambat daripada di-kill. Jika kalian ingin mematikan swap, setel --memory-swap sama persis dengan --memory.
Bagaimana cara mengonfirmasi limit yang sudah diterapkan? docker inspect menampilkan semuanya di blok HostConfig:
docker inspect api --format \
'{{.HostConfig.Memory}} / {{.HostConfig.MemorySwap}}'Angka yang keluar dalam satuan byte — inilah sumber kebenaran yang dipakai oleh kernel, berbeda dengan konfigurasi di file compose yang masih "harapan".
Untuk CPU ada dua pendekatan yang saling melengkapi:
--cpus: batas CPU time dalam unit core. --cpus 1.5 berarti container boleh memakai rata-rata 1,5 core penuh — kernel membaginya dalam rentang waktu.--cpuset-cpus: mengunci container ke core fisik tertentu (misalnya 0-1 atau 2,3). Ini untuk workload latency-sensitive yang tidak mau berlomba dengan container lain untuk mendapatkan core yang sama — seperti membeli kursi bernomor di kereta alih-alih berebut tempat duduk.docker run -d --name worker \
--cpus 2 \
--cpuset-cpus 0-1 \
--pids-limit 512 \
redis--pids-limit: membatasi jumlah proses (PID) dalam container. Ini pelindung penting dari fork bomb — aplikasi yang tiba-tiba menggandakan diri sendiri (biasanya karena bug atau serangan) hingga menjatuhkan host. Nilai yang wajar tergantung workload; untuk Node.js/Go biasanya 128-512 sudah cukup.--ulimit : menyetel batas level OS untuk proses di dalam container. Yang paling sering disetel adalah nofile (jumlah file descriptor yang bisa dibuka) dan nproc (jumlah proses per user). Aplikasi web yang menangani banyak koneksi butuh nofile tinggi:docker run -d --name nginx \
--ulimit nofile=65536:65536 \
--pids-limit 256 \
nginxIngat: memberikan --ulimit nofile tanpa batas bukan "hadiah" — setiap file descriptor memakan memory kernel, jadi nilai harus direncanakan, bukan dinaikkan asal-asalan.
Ini bagian yang paling sering disalahpahami. Ketika sebuah container melampaui batas --memory, kernel tidak menolak alokasi memory tersebut dengan sopan — kernel memanggil OOM-killer, dan OOM-killer memilih satu atau lebih proses untuk di-kill berdasarkan OOM score. Semakin tinggi skor, semakin besar kemungkinan ia dipilih untuk dikorbankan.
OOM score dihitung dari pemakaian memory aktual dan penyesuaian oom_score_adj. Docker memberi --oom-score-adj pada container yang berjalan dengan --oom-kill-disable, dan setiap container mendapat nilai default sesuai batasannya. Aturan praktisnya:
--memory kecil cenderung mendapat oom_score_adj lebih tinggi (lebih mudah di-kill).docker run -d --name api \
--memory 512m \
--oom-kill-disable \
nginxImportant
--oom-kill-disable adalah opsi yang hanya boleh dipakai dengan --memory dan dengan alasan yang sangat spesifik — misalnya mengamankan proses yang sedang menulis data penting agar tidak di-kill di tengah jalan. Tanpa --memory, flag ini tidak berpengaruh (OOM tetap terjadi di level host). Efek sampingnya: jika container di-kill, seluruh host ikut tertekan. Di production, lebih baik membiarkan OOM-killer bekerja — container yang di-kill bisa di-restart, host yang membeku tidak bisa.
Untuk observasi, perintah docker inspect juga menampilkan OomKillDisable dan OomScoreAdj di HostConfig — dua nilai yang akan sering kalian cek saat menyelidiki "container tiba-tiba mati".
docker stats adalah jendela real-time ke pemakaian resource seluruh container:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
3f8a1c2d9b4e api 12.50% 190.2MiB / 512MiB 37.15% 1.23MB / 4.5MB 12MB / 0B
9d2c0e1a5f7b redis 3.25% 45.1MiB / 256MiB 17.61% 890kB / 2.1MB 5MB / 0B
7a4b9c1d2e3f web 41.75% 880.3MiB / 1GiB 85.97% 45.1MB / 9.2MB 30MB / 0BBagaimana membaca tabel ini untuk perencanaan kapasitas?
web di 85% dari limit 1 GiB adalah alarm — kalian punya sedikit ruang aman sebelum OOM-killer berbicara.Aturan umum capacity planning: limit host harus lebih besar dari jumlah limit semua container, dan sisakan ruang untuk daemon itu sendiri (biasanya 5-10%). Jika total limit container sudah menyentuh kapasitas fisik host, setiap restart container akan saling berebut — dan host mulai thrashing.
Setiap driver network yang kita bahas di episode 9 membawa trade-off performa:
Bagi kebanyakan aplikasi web, bridge sudah lebih dari cukup. Jangan "mengoptimalkan" jaringan sebelum mengukur — ukurlah dulu dengan tool seperti iperf di dalam container, karena kegagalan performa di dunia nyata lebih sering berasal dari memory yang kurang, disk yang lambat, atau aplikasi yang menunggu I/O, bukan dari driver network.
Beberapa kebiasaan kecil yang dampaknya besar di production:
--privileged. Flag ini memberi container akses penuh ke perangkat host — setara root di host itu sendiri. Ia tidak hanya bahaya keamanan, tapi juga sering jadi penyebab performa aneh (container melihat seluruh /sys dan mencoba mengelola resource yang seharusnya tidak ia sentuh). Hampir selalu ada alternatif yang lebih sempit: --cap-add, --device, atau --security-opt.overlay2 dengan Copy-on-Write: menulis file besar ke layer writable berarti menyalin seluruh file tiap kali dimodifikasi (copy-up). Bind mount atau named volume mengakses file langsung dari disk host tanpa lapisan CoW — perbedaan yang sangat terasa untuk aplikasi dengan I/O berat seperti upload file, cache file, atau database.docker run -d --name uploads \
-v /data/uploads:/app/uploads \
nginxPerbedaan ini adalah alasan mengapa database tidak pernah menyimpan data di layer writable: menempatkan data Postgres/MySQL di named volume (seperti yang kalian pelajari di episode 8) tidak hanya aman saat recreate, tapi juga lebih cepat karena melewati lapisan CoW.
Tidak ada satu ukuran untuk semua, tapi ada titik awal yang masuk akal berdasarkan karakter workload:
| Workload | Karakter | Limit awal yang wajar | Catatan |
|---|---|---|---|
| Database (PostgreSQL/MySQL) | Memory-hungry, latency-sensitive, I/O berat | --memory 2g, --cpus 2, swap off | Data di volume; hindari OOM; swap mematikan query |
| Cache (Redis) | Memory-sensitive, sangat cepat | --memory 1g, --memory-swap 1g (tanpa swap) | maxmemory di Redis harus di bawah limit |
| Web server / API | Beragam, banyak koneksi | --memory 512m, --cpus 1, nofile tinggi | Tergantung concurrency; pantau docker stats |
| Background worker | CPU-bound, bisa lambat | --cpus 1-2, --memory 512m, --pids-limit | Batasi pids agar fork bomb tidak menjatuhkan host |
Untuk latency-sensitive workload (database, Redis), pertimbangkan --cpuset-cpus agar inti CPU tidak dibagi dengan worker yang boros, dan pastikan swap benar-benar mati — swap adalah pengganti kecepatan dengan "penundaan", musuh utama aplikasi yang harus konsisten di bawah milidetik.
Dalam Compose, ada dua pendekatan tergantung mode:
mem_limit, mem_reservation, cpus, pids_limit, dan ulimits langsung di service.deploy.resources.limits dan deploy.resources.reservations.services:
api:
image: my-api:1.4.0
mem_limit: 512m
mem_reservation: 256m
cpus: 1.5
pids_limit: 256
ulimits:
nofile:
soft: 65536
hard: 65536
restart: unless-stoppedservices:
api:
image: my-api:1.4.0
deploy:
replicas: 2
resources:
limits:
cpus: "1.5"
memory: 512M
reservations:
cpus: "0.5"
memory: 256MPerhatikan perbedaan semantiknya: reservations adalah jaminan minimum yang dijanjikan scheduler (container "berhak" atas itu), sedangkan limits adalah pagar keras yang tidak boleh dilewati. Validasi hasilnya dengan docker inspect atau, untuk stack, docker service inspect.
Memory tanpa memory-swap. Seperti dibahas: swap jadi dua kali lipat --memory, dan workload latency-sensitive menelan penundaan besar. Setel --memory-swap secara eksplisit, atau samakan dengan --memory untuk mematikan swap.
Swappiness host yang terlalu tinggi. Nilai vm.swappiness (default 60 di banyak distro) membuat kernel cenderung memindahkan halaman ke swap sebelum waktunya — buruk untuk container memory-sensitive. Untuk host yang didominasi container, sysadmin sering menurunkannya ke 10 atau bahkan 0:
sudo sysctl -w vm.swappiness=10Memberi --pids-limit tak terbatas. Container tanpa batas PID bisa menjadi mesin fork bomb: satu proses yang menggandakan diri membuat host kehabisan PID space dan akhirnya seluruh host tidak bisa membuat proses baru — termasuk proses login SSH. Batasi sejak awal.
Limit tanpa monitoring. Menyetel limit bukan akhir pekerjaan — kalian harus mengamati docker stats dan metrik jangka panjang. Limit yang terlalu kecil menyebabkan OOM berulang; yang terlalu besar membuat host over-committed. Keduanya hanya terlihat dengan data.
Pada episode 25 ini kalian telah memahami bahwa di balik setiap flag resource Docker berdiri cgroups v2 yang ditegakkan oleh kernel: --memory, --memory-swap, --memory-reservation untuk batas memory; --cpus dan --cpuset-cpus untuk CPU; --pids-limit dan --ulimit untuk proses serta file descriptor. Kalian juga memahami perilaku OOM-killer — termasuk mengapa --oom-kill-disable adalah keputusan berisiko yang harus disertai alasan kuat, cara membaca docker stats untuk capacity planning, dampak driver network terhadap latensi, serta titik awal limit yang wajar untuk database, cache, dan web server.
Inti yang harus kalian bawa:
--memory-swap secara eksplisit; lupa menyetelnya berarti memberi swap dua kali lipat.--oom-kill-disable.docker stats adalah alat capacity planning; catat baseline, bukan snapshot.--privileged.Host yang sehat dan container yang terjaga membuat fondasi siap untuk topik yang lebih dalam: keamanan. Di episode 26 selanjutnya kita akan membahas Docker Security Advanced — Secrets, Rootless & Supply Chain: bagaimana menyimpan dan menyalurkan secret tanpa membuatnya terukir di layer image, menjalankan daemon tanpa root dengan Rootless Docker dan userns-remap, serta checklist supply chain security mulai dari pin digest, scanning di CI, penandatanganan image dengan Cosign, hingga SBOM. Sampai jumpa di episode 26!