Batas Memori Docker Compose agar Tidak OOM
Pelajari batas memori dan CPU Docker Compose dengan deploy.resources atau mem_limit, pahami exit 137, swap, dan cara memilih ukuran yang aman untuk VPS.
Batas memori Docker Compose
Batas memori Docker Compose adalah batas maksimum yang diterapkan kernel Linux pada cgroup (control group, fitur kernel yang mengukur sumber daya untuk sekumpulan proses) milik satu container. Tetapkan deploy.resources.limits.memory pada sebuah service, dan container tersebut tidak akan pernah menggunakan memori melebihi angka yang ditulis. Jika mencoba melampauinya, kernel akan menghentikan sebuah proses di dalam container, dan container biasanya keluar dengan kode 137.
Hal ini paling penting pada VPS, karena kapasitas RAM tetap dan tidak ada memori host tambahan untuk dipinjam. Satu container dengan kebocoran memori atau query yang buruk dapat menggunakan setiap halaman memori yang tersedia pada server 8GB. Kernel kemudian menghentikan proses yang dinilainya paling bermasalah. Proses tersebut sering kali berupa database atau sesi SSH Anda, bukan container yang menyebabkan masalah. Batas memori mengubah gangguan seluruh server menjadi masalah pada satu service yang dapat dimulai ulang.
services:
app:
image: ghcr.io/example/app:1.4
deploy:
resources:
limits:
cpus: "1.5"
memory: 1g
reservations:
memory: 256mTerapkan konfigurasi tersebut dan pastikan batasnya aktif:
docker compose up -d
docker stats --no-streamKolom MEM USAGE / LIMIT seharusnya menampilkan nilai seperti 142MiB / 1GiB. Jika kolom batas menampilkan seluruh RAM host, pengaturan belum diterapkan. Bagian panduan lainnya tidak akan membantu sebelum pengaturan tersebut diterapkan. Jika file Compose masih baru bagi Anda, dasar-dasar Docker Compose untuk VPS menjelaskan struktur file yang digunakan sebagai dasar konfigurasi ini.
deploy.resources.limits atau mem_limit: mana yang berlaku
Ada dua penulisan untuk konsep yang sama. Karena itu, bagian ini sering membingungkan.
mem_limit, mem_reservation, memswap_limit, cpus, dan cpu_shares adalah kunci service tingkat atas yang diwarisi dari format file Compose versi lama. deploy.resources berasal dari skema Swarm dan sekarang menjadi bagian dari Compose Specification, yaitu format yang dibaca docker compose saat ini.
Keduanya berfungsi pada satu host. Docker Compose, yaitu plugin docker compose, menerapkan deploy.resources.limits dan deploy.resources.reservations saat Anda menjalankan docker compose up, tanpa cluster Swarm sama sekali. Plugin ini sekarang berada pada lini v5 (v5.6.0 adalah rilis stabil terbaru saat diperiksa pada 2026-10-10). Versinya langsung beralih dari v2 ke v5, dengan melewati v3 dan v4 untuk menghindari kebingungan dengan versi format file Compose lama. Tidak ada rilis v5 yang mengubah cara penanganan batas resource. Bagian deploy yang berorientasi pada Swarm adalah kunci lainnya, seperti placement, update_config, dan endpoint_mode, yang menjelaskan penjadwalan dan peluncuran ulang pada cluster node untuk docker stack deploy. Jadi, anggapan umum bahwa "deploy memerlukan Swarm" salah untuk subseksi resources. Jika Anda mengikuti anggapan tersebut, service Anda tidak memiliki batas sama sekali.
Pilih satu penulisan untuk setiap project. Menulis mem_limit: 512m dan deploy.resources.limits.memory: 1g pada service yang sama bukan hanya sulit dibaca. Compose Specification menyatakan bahwa jika mem_limit ditetapkan, nilainya harus konsisten dengan limits.memory. Spesifikasi tersebut tidak menyatakan nilai mana yang berlaku jika keduanya berbeda. Apa pun penulisan yang Anda gunakan, pastikan nilai yang benar-benar diterapkan oleh daemon:
docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.MemoryReservation}} {{.HostConfig.NanoCpus}}' app-1Nilai memory dinyatakan dalam byte. Karena itu, 1g menampilkan 1073741824. CPU dinyatakan dalam nano CPU. Karena itu, 1.5 menampilkan 1500000000. Nilai 0 pada field mana pun berarti tidak ada batas yang ditetapkan. Batas memory terkecil yang diterima Docker adalah 6m. Di bawah nilai tersebut, container tidak dapat dijalankan.
Apa yang terjadi ketika container mencapai batas
Container tidak melambat. Container berhenti.
Ketika sebuah proses meminta page dan cgroup sudah mencapai memory.max, kernel terlebih dahulu mereklamasi resource yang dapat digunakan di dalam cgroup tersebut: page cache yang bersih, lalu page yang dapat dipindahkan ke swap. Jika reclaim tidak membebaskan cukup resource, OOM (out of memory) killer pada cgroup memilih sebuah proses di dalam container dan mengirimkan SIGKILL. Jika PID 1 milik container dihentikan, container ikut berhenti. Exit code 137 hanyalah 128 ditambah signal 9. Jadi, 137 merupakan tanda adanya SIGKILL, bukan bukti bahwa penyebabnya pasti OOM.
docker compose ps -a
docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' app-1true 137 adalah OOM kill. false 137 biasanya berarti ada pihak lain yang mengirimkan SIGKILL. Program juga dapat keluar dengan 137 secara sengaja, tetapi hal ini jarang terjadi. Penyebab yang umum adalah docker compose stop mencapai grace period sepuluh detik karena aplikasi mengabaikan SIGTERM. Perbedaan ini menghemat waktu berjam-jam karena kedua masalah tersebut tidak saling berkaitan.
Ada dua tempat lain yang mencatat kejadian ini. Pantau daemon secara langsung:
docker events --filter event=oomSelanjutnya, baca log kernel. Log ini tetap tersedia setelah restart:
sudo dmesg -T | grep -i -E 'memory cgroup out of memory|killed process'Container kill oleh cgroup mencetak baris yang diawali Memory cgroup out of memory: Killed process 24713 (node). Baris tersebut berarti satu container mencapai batasnya sendiri. Baris ini dapat muncul ketika host masih memiliki banyak RAM yang tersedia. Baris tanpa prefix Memory cgroup menunjukkan host OOM. Artinya, mesin itu sendiri kehabisan RAM. Inilah kegagalan yang hendak dicegah oleh limits. Jika pesan ini muncul, biasanya jumlah seluruh limit terlalu tinggi, beberapa service tidak memiliki limit sama sekali, atau sesuatu di luar Docker menggunakan resource lebih banyak daripada headroom yang disisakan.
Dengan restart: unless-stopped, loop OOM sulit terlihat karena service terlihat aktif di docker compose ps satu detik setelah berhenti. Periksa kolom uptime dan jumlah restart. Gabungkan limit tersebut dengan healthcheck yang melaporkan aplikasi sebagai tidak sehat agar container yang terus berhenti dapat terlihat tanpa harus dipantau terus-menerus.
Reservation adalah petunjuk, sedangkan limit adalah aturan
reservations.memory (mem_reservation yang lebih lama) adalah batas bawah lunak. Docker menjelaskannya sebagai batas lunak yang diaktifkan ketika daemon mendeteksi contention atau memori host yang rendah. Batas ini tidak pernah menghentikan container saat penggunaan memorinya melampaui nilai tersebut, dan tidak pernah menjamin bahwa memori akan tersedia ketika container memintanya. Batas ini hanya mengarahkan kernel untuk lebih dahulu mereklaim memori dari container yang berada di atas nilai reservation.
Jadi, reservation tidak melindungi apa pun jika digunakan sendiri. Gunakan reservation untuk menandai service yang ingin diprioritaskan saat terjadi tekanan, dan andalkan limit untuk menjaga keamanan. Tetapkan reservation di bawah limit. Jika tidak, container tidak akan start: Docker menolak konfigurasi tersebut dengan Minimum memory limit can not be less than memory reservation limit.
Akuntansi swap, apa adanya
Sebagian besar image VPS dirilis tanpa file swap sama sekali. Jalankan swapon --show dan free -h. Jika total swap adalah nol, semua pengaturan terkait swap di bawah ini tidak berpengaruh, dan batas memori Anda sepenuhnya hanya membatasi RAM.
memswap_limit bukan jumlah swap. Nilai ini adalah total memori ditambah swap. Dengan mem_limit: 1g dan memswap_limit: 2g, container mendapatkan RAM sebesar 1GB dan swap sebesar 1GB. Jika kedua nilai tersebut dibuat sama, container sama sekali tidak mendapatkan swap. Menetapkan mem_limit dan membiarkan memswap_limit tidak ditetapkan memungkinkan container menggunakan swap hingga sebesar batas memorinya.
Ubuntu 24.04 dan Debian 13 menggunakan cgroup v2 secara default. Pada versi ini, swap memiliki penghitung terpisah (memory.swap.max), dan konfigurasi ini dapat digunakan tanpa penyiapan tambahan. Pesan lama Your kernel does not support swap limit capabilities berasal dari host cgroup v1 yang di-boot tanpa swapaccount=1. Pada host tersebut, batas memori tetap berlaku, sedangkan bagian swap diabaikan.
Pahami dengan jelas manfaat swap. Swap membuat OOM kill berlangsung lebih lambat, bukan membuatnya lebih kecil kemungkinannya, karena proses yang mengalami kebocoran memori akan memenuhi swap sama mudahnya seperti memenuhi RAM. Sementara itu, container yang terus-menerus menggunakan swap pada storage VPS bersama akan memperlambat setiap service lain di server tersebut. Untuk layanan yang sensitif terhadap latensi, batas yang benar tanpa swap akan gagal lebih cepat dan lebih dapat diprediksi.
Mengapa penggunaan memori tampak lebih buruk daripada kondisi sebenarnya
Di Linux, angka MEM USAGE dalam docker stats hanya mengurangi cache halaman yang tidak aktif (inactive_file pada cgroup v2). Cache yang baru digunakan tetap dihitung. Container yang membaca file besar akan terus mendekati batasnya lalu bertahan di sana. Ini normal dan bukan kebocoran, karena cache bersih akan direklamasi sebelum OOM killer dipanggil. Service seperti server media Jellyfin yang di-host sendiri akan tampak terus-menerus mendekati batasnya karena alasan ini.
Pisahkan angka tersebut menjadi cache dan working set aktual dari dalam container:
docker compose exec app grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat
docker compose exec app cat /sys/fs/cgroup/memory.eventsanon adalah memori anonim, yaitu working set yang tidak dapat dibuang. file adalah cache halaman yang dapat dibuang. Tetapkan batas berdasarkan anon ditambah margin, bukan berdasarkan totalnya. File memory.events memberikan bukti yang jelas: nilai counter oom_kill di atas nol berarti kernel telah mematikan sesuatu dalam container ini sejak container dimulai, sedangkan counter max yang terus meningkat berarti container sedang ditahan pada batasnya saat ini. Kedua perintah tersebut memerlukan shell dan coreutils di dalam image, sehingga gagal pada image distroless atau scratch.
Batas memori pada VPS 8GB
Mulai dari host, bukan dari aplikasi. Pada VPS 8GB, sisakan sekitar 1GB untuk kernel, daemon Docker, sshd, journald, dan shell login Anda. Dengan demikian, tersedia sekitar 7GB untuk dibagikan, dan jumlah seluruh batas container harus tetap di bawah angka tersebut. Overcommit dapat berjalan baik hingga dua service mencapai penggunaan puncaknya secara bersamaan.
Pembagian yang dapat diterapkan pada server 8GB:
- Reverse proxy: batas 128m. Proses ini berukuran kecil, dan batas yang ketat seperti ini segera mendeteksi reload konfigurasi yang tidak terkendali.
- PostgreSQL: batas 2g, dengan
shared_buffersdiatur sekitar 512MB dalam konfigurasi database. - Container aplikasi: batas 1g.
- Worker latar belakang: batas 512m.
- Service media atau file: batas 2g, yang sebagian besar akan digunakan sebagai page cache.
Jangan menyalin angka tersebut ke stack Anda. Jalankan service dalam beban nyata selama satu hari, pantau docker stats, ambil nilai puncak anon untuk setiap container, lalu tambahkan sekitar setengahnya sebagai ruang cadangan. Batas yang terlalu ketat lebih buruk daripada tanpa batas, karena dapat menghentikan service yang sehat saat terjadi lonjakan trafik normal.
Satu jebakan perlu dibahas secara khusus. Beberapa runtime mengabaikan batas tersebut kecuali Anda memberi tahu runtime tentang batas itu. Runtime yang membacanya pun mungkin tidak menentukan ukuran penggunaannya seperti yang Anda harapkan. PostgreSQL dapat menetapkan ukuran shared_buffers dan work_mem melebihi batas containernya, lalu dihentikan. JVM (Java virtual machine) modern mendeteksi batas cgroup secara default. Namun, build HotSpot seperti OpenJDK kemudian memberikan heap hanya 25 persen dari batas tersebut. Karena itu, -XX:MaxRAMPercentage=75 biasanya digunakan agar heap dapat memakai bagian yang lebih besar dari batas tersebut, sambil menyisakan ruang untuk memori di luar heap. Node.js telah menentukan heap default dari batas cgroup sejak versi 12, yaitu sekitar setengah dari batas container dan kemudian berhenti meningkat di sekitar 2GB. Jadi, atur --max-old-space-size dalam megabyte hanya jika Anda menginginkan batas yang berbeda. Pertahankan nilainya di bawah batas container agar garbage collector bekerja sebelum kernel mengambil tindakan. Ollama memiliki perilaku yang sama dengan opsi yang berbeda, karena menaikkan num_ctx memperbesar cache KV hingga ratusan megabyte dan container dapat berhenti di tengah prompt yang panjang. cgroup tidak melakukan negosiasi. cgroup menghentikannya.
Batas CPU bekerja dengan cara yang sama sekali berbeda
cpus: "1.5" berarti 150% dari satu core, yang diberlakukan sebagai kuota CFS (completely fair scheduler). Container memperoleh waktu CPU sebesar 150ms dalam setiap periode 100ms, yang digunakan bersama oleh semua thread di dalamnya. Setelah kuota itu habis, kernel membuatnya menunggu hingga periode berikutnya.
Inilah perbedaan pentingnya. Container yang melampaui batas memorinya akan dihentikan. Container yang melampaui batas CPU akan dikenai throttling dan tetap berjalan, tetapi lebih lambat. Karena itu, batas CPU aman untuk ditetapkan secara agresif, sedangkan batas memori memerlukan ruang cadangan.
cpu_shares adalah alat yang berbeda: bobot relatif yang hanya berpengaruh ketika CPU benar-benar jenuh. Dua container dengan share 1024 dan 512 akan membagi core yang sibuk dengan rasio kira-kira dua banding satu. Pada server yang idle, keduanya tidak dibatasi. Gunakan share untuk menentukan prioritas layanan, dan gunakan cpus ketika Anda memerlukan batas maksimum nyata, misalnya untuk mencegah pekerjaan transcode malam hari menghabiskan sumber daya sehingga web server tidak memperoleh CPU yang cukup.
FAQ
Apakah deploy.resources.limits berfungsi tanpa Docker Swarm?
Ya. Docker Compose, plugin docker compose (yang kini berada pada lini v5), menerapkan deploy.resources.limits dan deploy.resources.reservations saat Anda menjalankan docker compose up pada satu host. Konfirmasikan dengan docker inspect --format '{{.HostConfig.Memory}}' <container>, yang menampilkan batas dalam byte dan menampilkan 0 jika tidak ada batas yang diterapkan. Kunci di dalam deploy yang dirancang dengan mempertimbangkan Swarm adalah kunci tingkat cluster, seperti placement, update_config, dan endpoint_mode.
Apa arti exit code 137 di Docker Compose?
Hampir selalu berarti proses utama menerima SIGKILL, karena 137 adalah 128 ditambah signal 9 (program juga dapat keluar dengan 137 secara sengaja, tetapi ini jarang terjadi). OOM killer kernel adalah penyebab yang umum, tetapi timeout shutdown menghasilkan kode yang sama ketika aplikasi mengabaikan SIGTERM. Jalankan docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' <container> untuk membedakan keduanya. true 137 menunjukkan memory kill, sedangkan false 137 tidak.
Sebaiknya saya menetapkan mem_limit atau deploy.resources.limits.memory?
Keduanya berfungsi dengan docker compose. deploy.resources.limits.memory adalah bentuk Compose Specification saat ini dan merupakan pilihan default yang lebih baik untuk file baru. Pertahankan mem_limit jika bagian lain dari file Anda sudah menggunakan kunci tingkat teratas yang lebih lama. Compose Specification mengharuskan keduanya konsisten jika sama-sama ditetapkan, jadi pilih salah satu dan verifikasi hasilnya dengan docker inspect.
Mengapa container saya menggunakan seluruh memory limit tanpa dihentikan?
Angka penggunaan pada docker stats masih menghitung page cache yang baru digunakan. Kernel akan menghapus cache tersebut saat terjadi tekanan memory, bukan langsung memicu OOM kill. Jalankan docker compose exec <service> grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat dan baca nilai anon, yaitu working set yang tidak dapat direklamasi. Nilai file yang tinggi di samping nilai anon yang rendah menunjukkan container sedang melakukan input dan output disk, bukan container yang akan segera berhenti.
Berapa banyak RAM yang sebaiknya tidak dialokasikan pada VPS 8GB?
Sisakan sekitar 1GB untuk kernel, Docker daemon, sshd, journald, dan shell Anda sendiri. Kemudian, pertahankan jumlah seluruh limit container di bawah sisa 7GB. Pantau nilai puncak anon per container selama satu hari dengan beban nyata sebelum menetapkan angkanya. Perlakukan total tersebut sebagai anggaran, bukan target yang harus dihabiskan.