SSD Nodes Learn Hosting plans →
Panduan Matt ConnorOleh Matt Connor

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: 256m

Terapkan konfigurasi tersebut dan pastikan batasnya aktif:

docker compose up -d
docker stats --no-stream

Kolom 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

Dua penulisan tersedia untuk konsep yang sama. Karena itu, bagian ini membingungkan.

mem_limit, mem_reservation, memswap_limit, cpus, dan cpu_shares adalah kunci service tingkat atas yang diwarisi dari format file Compose 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, plugin docker compose, menerapkan deploy.resources.limits dan deploy.resources.reservations saat Anda menjalankan docker compose up, tanpa cluster Swarm. Plugin ini sekarang berada pada lini v5 (v5.6.0 adalah rilis stabil terbaru saat diperiksa pada 2026-10-06). Versinya langsung berpindah 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 khusus Swarm dalam blok deploy adalah kunci lainnya: mode, placement, update_config, dan endpoint_mode memiliki arti bagi docker stack deploy, tetapi diabaikan oleh docker compose up. Jadi, saran umum bahwa "deploy memerlukan Swarm" tidak benar untuk subseksi resources. Jika saran tersebut diikuti, 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 menghasilkan file yang sulit dipahami saat dibaca sekilas. Jangan menebak nilai mana yang berlaku. Tanyakan langsung kepada daemon:

docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.MemoryReservation}} {{.HostConfig.NanoCpus}}' app-1

Nilai memory dinyatakan dalam byte, sehingga 1g ditampilkan sebagai 1073741824. CPU dinyatakan dalam nano CPU, sehingga 1.5 ditampilkan sebagai 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 gagal start.

Apa yang terjadi ketika container mencapai limit

Container tidak melambat. Container berhenti.

Ketika suatu proses meminta page dan cgroup sudah mencapai memory.max, kernel terlebih dahulu mereklamasi resource yang dapat diambil dari dalam cgroup tersebut: page cache yang bersih, kemudian page yang dapat dipindahkan ke swap. Jika proses reclaim tidak membebaskan cukup resource, OOM (out of memory) killer pada cgroup memilih proses di dalam container lalu 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-1

true 137 adalah OOM kill. false 137 berarti ada pihak lain yang mengirimkan SIGKILL. Penyebab yang umum adalah docker compose stop mencapai grace period sepuluh detik karena aplikasi mengabaikan SIGTERM. Perbedaan ini menghemat waktu troubleshooting karena kedua masalah tersebut tidak berkaitan.

Ada dua tempat lain yang mencatat kejadian ini. Pantau daemon secara langsung:

docker events --filter event=oom

Kemudian baca log kernel, yaitu catatan yang tetap tersedia setelah restart:

sudo dmesg -T | grep -i -E 'memory cgroup out of memory|killed process'

Cgroup kill mencetak baris yang diawali Memory cgroup out of memory: Killed process 24713 (node). Baris tanpa prefix Memory cgroup menunjukkan OOM pada host, yang berarti mesin itu sendiri kehabisan RAM. Itulah kegagalan yang seharusnya dicegah oleh limit. Jika hal tersebut terjadi, berarti total limit Anda terlalu tinggi atau beberapa service sama sekali tidak memiliki limit.

Dengan restart: unless-stopped, loop OOM sulit terlihat karena service tampak aktif kembali di docker compose ps satu detik setelah berhenti. Periksa kolom uptime dan jumlah restart. Pasangkan limit tersebut dengan healthcheck yang melaporkan aplikasi sebagai unhealthy agar container yang terus berhenti dapat terlihat tanpa harus dipantau secara manual.

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 terlihat lebih buruk daripada kondisi sebenarnya

Angka MEM USAGE dalam docker stats mencakup page cache. Karena itu, container yang membaca file berukuran besar akan terus mendekati limitnya lalu menetap di sana. Ini normal dan bukan memory leak, karena cache bersih akan direclaim sebelum OOM killer dipanggil. Service seperti server media Jellyfin yang di-host sendiri akan terlihat 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.events

anon adalah anonymous memory, yaitu working set yang tidak dapat dibuang. file adalah page cache, yang dapat dibuang. Tetapkan limit berdasarkan anon ditambah margin, bukan berdasarkan totalnya. File memory.events memberikan bukti yang jelas: counter oom_kill yang lebih besar dari zero berarti kernel telah menghentikan sesuatu dalam container ini sejak container tersebut dijalankan, sedangkan counter max yang terus meningkat berarti container sedang ditahan pada batasnya saat ini. Kedua perintah memerlukan shell dan coreutils di dalam image, sehingga akan gagal pada image distroless atau scratch.

Batas memori pada VPS 8GB

Mulailah dari host, bukan dari aplikasi. Pada VPS 8GB, sisakan sekitar 1GB untuk kernel, Docker daemon, sshd, journald, dan shell login Anda. Dengan demikian, tersisa sekitar 7GB untuk dibagikan, dan jumlah seluruh limit container harus tetap di bawah angka tersebut. Overcommit dapat berjalan baik sampai dua service mencapai penggunaan puncaknya secara bersamaan.

Pembagian yang dapat digunakan pada server 8GB:

  • Reverse proxy: limit 128m. Proses ini kecil, dan limit yang ketat seperti ini akan segera mendeteksi reload konfigurasi yang tidak terkendali.
  • PostgreSQL: limit 2g, dengan shared_buffers disetel sekitar 512MB di konfigurasi database.
  • Application container: limit 1g.
  • Background worker: limit 512m.
  • Media atau file service: limit 2g, yang sebagian besar akan digunakan sebagai page cache.

Jangan menyalin angka tersebut ke stack Anda. Jalankan service dalam beban nyata selama sehari, pantau docker stats, ambil nilai puncak anon untuk setiap container, lalu tambahkan sekitar setengah dari nilai tersebut sebagai ruang cadangan. Limit yang terlalu ketat lebih buruk daripada tanpa limit karena dapat menghentikan service yang sehat saat terjadi lonjakan trafik normal.

Ada satu hal yang perlu dibahas secara khusus. Sebagian besar runtime tidak mengetahui limit tersebut kecuali Anda memberitahukannya. PostgreSQL dapat dengan mudah menetapkan ukuran shared_buffers dan work_mem melebihi limit containernya, lalu dihentikan. JVM (Java virtual machine) memerlukan -XX:MaxRAMPercentage=75 agar ukuran heap ditentukan berdasarkan limit cgroup, bukan RAM host. Node.js memerlukan --max-old-space-size dalam megabyte, dengan nilai di bawah limit container. Jika tidak, garbage collector membiarkan heap terus bertambah sampai kernel bertindak. Ollama memiliki kondisi serupa dengan parameter yang berbeda, karena menaikkan num_ctx akan memperbesar KV cache hingga ratusan megabyte dan container dapat berhenti di tengah prompt yang panjang. cgroup tidak bernegosiasi. 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 (kini menggunakan lini v5), menerapkan deploy.resources.limits dan deploy.resources.reservations saat Anda menjalankan docker compose up pada satu host. Pastikan 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 memang memerlukan Swarm adalah mode, placement, update_config, dan endpoint_mode.

Apa arti kode keluar 137 di Docker Compose?

Artinya proses utama menerima SIGKILL, karena 137 adalah 128 ditambah sinyal 9. OOM killer pada kernel adalah penyebab yang umum, tetapi batas waktu shutdown menghasilkan kode yang sama jika aplikasi mengabaikan SIGTERM. Jalankan docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' <container> untuk membedakan keduanya. true 137 menunjukkan proses dihentikan karena kehabisan memori, 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. Menetapkan keduanya pada satu service hanya membuat file lebih sulit dibaca, jadi pilih salah satu dan verifikasi hasilnya dengan docker inspect.

Mengapa container saya menggunakan seluruh batas memorinya tetapi tidak dihentikan?

Angka penggunaan dalam docker stats mencakup page cache, yang akan dibuang oleh kernel saat terjadi tekanan memori, bukan memicu OOM kill. Jalankan docker compose exec <service> grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat dan baca nilai anon, yang menunjukkan 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 harus dibiarkan tidak dialokasikan pada VPS 8GB?

Sisakan sekitar 1GB untuk kernel, daemon Docker, sshd, journald, dan shell Anda sendiri, lalu pertahankan jumlah seluruh batas container di bawah sisa 7GB. Pantau nilai puncak anon per container selama beban nyata selama satu hari sebelum menetapkan angkanya, dan perlakukan total tersebut sebagai anggaran, bukan target yang harus dipenuhi.