SSD Nodes Learn Hosting plans →
Mga Gabay Matt ConnorNi Matt Connor · Na-update 2026-08-30

Docker Compose memory limit para iwas OOM at exit 137

Itakda ang memory at CPU limits sa Docker Compose para hindi pabagsakin ng isang container ang VPS. Alamin ang deploy.resources, mem_limit, swap, at exit 137.

Ano ang ginagawa ng Docker Compose memory limit

Ang Docker Compose memory limit ay hard cap na ipinapataw ng Linux kernel sa cgroup ng isang container. Ang cgroup (control group) ay feature ng kernel na sumusukat sa paggamit ng resources ng isang grupo ng processes. Itakda ang deploy.resources.limits.memory sa isang service at hindi kailanman lalampas ang container na iyon sa itinakdang halaga. Kapag sinubukan nitong lumampas, papatayin ng kernel ang isang process sa loob ng container. Karaniwang mag-e-exit ang container na may code 137.

Pinakamahalaga ito sa isang VPS, kung saan fixed ang RAM at walang ekstrang host memory na maaaring hiramin. Ang isang container na may memory leak o maling query ay maaaring gumamit ng bawat libreng page sa isang 8GB machine. Pagkatapos, papatayin ng kernel ang process na itinuturing nitong pinakamalubha ang epekto. Madalas, database o SSH session ang napapatay sa halip na ang container na sanhi ng problema. Ginagawang isang service na maaaring mag-restart ng limits ang outage ng buong server.

services:
  app:
    image: ghcr.io/example/app:1.4
    deploy:
      resources:
        limits:
          cpus: "1.5"
          memory: 1g
        reservations:
          memory: 256m

I-apply ito at kumpirmahing aktibo na ang limit:

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

Dapat magpakita ang MEM USAGE / LIMIT column ng halagang gaya ng 142MiB / 1GiB. Kung ipinapakita ng limit column ang buong RAM ng host, hindi na-apply ang setting. Hindi makatutulong ang natitirang bahagi ng guide na ito hangga't hindi ito naaayos. Kung bago sa iyo ang compose file mismo, ipinapaliwanag sa mga pangunahing kaalaman sa Docker Compose para sa VPS ang layout ng file na ginagamit dito.

deploy.resources.limits o mem_limit: alin ang nalalapat

May dalawang spelling para sa parehong konsepto, kaya nakakalito ito.

Ang mem_limit, mem_reservation, memswap_limit, cpus, at cpu_shares ay mga top-level service key na minana mula sa mas lumang Compose file format. Ang deploy.resources ay nagmula sa Swarm schema at bahagi na ngayon ng Compose Specification, ang format na binabasa ng docker compose sa kasalukuyan.

Pareho silang gumagana sa iisang host. Inilalapat ng Compose V2, ang docker compose plugin, ang deploy.resources.limits at deploy.resources.reservations kapag pinapatakbo mo ang docker compose up, kahit walang Swarm cluster. Ang mga bahagi ng deploy block na para lamang sa Swarm ay ang iba pang key: ang mode, placement, update_config, at endpoint_mode ay may kahulugan sa docker stack deploy at binabalewala ng docker compose up. Kaya mali ang karaniwang payo na “kailangan ng Swarm ang deploy” para sa resources subsection. Kapag sinunod mo ito, walang limit ang iyong mga service.

Pumili ng isang spelling para sa bawat project. Kapag isinulat mo ang mem_limit: 512m at deploy.resources.limits.memory: 1g sa parehong service, magiging mahirap basahin agad ang file. Sa halip na manghula kung aling value ang ginamit, itanong ito sa daemon:

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

Ang mga value para sa memory ay nasa bytes, kaya ipinapakita ang 1g bilang 1073741824. Ang CPU ay nasa nano CPUs, kaya ipinapakita ang 1.5 bilang 1500000000. Ang 0 sa anumang field ay nangangahulugang walang itinakdang limit. Ang pinakamaliit na memory limit na tinatanggap ng Docker ay 6m, at kapag mas mababa rito, hindi magsisimula ang container.

Ano ang nangyayari kapag naabot ng container ang limit

Hindi bumabagal ang container. Namatay ito.

Kapag humihingi ng page ang isang process at nasa memory.max na ang cgroup, unang binabawi ng kernel ang makakaya nito sa loob ng cgroup na iyon: clean page cache, pagkatapos ay mga page na maaaring i-swap. Kung hindi sapat ang makuhang memory, pumipili ang cgroup OOM (out of memory) killer ng process sa loob ng container at pinapadalhan ito ng SIGKILL. Kapag pinatay ang PID 1 ng container, nagtatapos ang container. Ang exit code 137 ay 128 lang na idinagdag sa signal 9, kaya ang 137 ay fingerprint ng anumang SIGKILL, hindi patunay na OOM ang sanhi.

docker compose ps -a
docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' app-1

Ang true 137 ay isang OOM kill. Ang false 137 ay nangangahulugang may ibang nagpadala ng SIGKILL, at ang karaniwang sanhi ay ang pag-abot ng docker compose stop sa ten second grace period nito dahil hindi pinansin ng app ang SIGTERM. Mahalaga ang pagkakaibang ito dahil magkaiba ang sanhi ng dalawang problema.

Dalawa pa ang nagtatala ng event. I-monitor nang live ang daemon:

docker events --filter event=oom

Pagkatapos, basahin ang kernel log. Ito ang rekord na nananatili kahit mag-restart:

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

Ang cgroup kill ay nagpi-print ng linyang nagsisimula sa Memory cgroup out of memory: Killed process 24713 (node). Ang linyang walang prefix na Memory cgroup ay host OOM. Ibig sabihin, naubusan mismo ng RAM ang machine. Ito ang failure na dapat pigilan ng mga limit. Kapag nakita ito, senyales ito na masyadong mataas ang kabuuan ng iyong mga limit, o may ilang service na walang limitasyon.

Sa restart: unless-stopped, madaling hindi mapansin ang OOM loop dahil lumilitaw na up ang service sa docker compose ps isang segundo matapos itong mamatay. Suriin ang uptime column at restart count. Ipares din ang limit sa isang healthcheck na nag-uulat na unhealthy ang app para makita ang container na paulit-ulit na namamatay kahit hindi mo ito mino-monitor.

Ang reservation ay pahiwatig; ang limit ang sinusunod na panuntunan

Ang reservations.memory (ang mas lumang mem_reservation) ay isang soft floor. Inilalarawan ito ng Docker bilang soft limit na ina-activate kapag may contention o mababa ang memory sa host. Hindi nito kailanman pinipigilan ang container na lumampas dito, at hindi rin nito ginagarantiya na magiging available ang memory kapag kailangan ito ng container. Naaapektuhan lamang nito ang kernel upang unahin ang pag-reclaim mula sa mga container na lampas sa kanilang reservation.

Kaya walang napoprotektahan ang reservation kapag mag-isa lamang itong ginagamit. Gamitin ito upang markahan ang serbisyong nais mong bigyan ng mas maayos na pagtrato kapag mataas ang pressure, at umasa sa limit para sa safety. Panatilihing mas mababa ang reservation kaysa sa limit; kung hindi, hindi magsisimula ang container: tatanggihan ng Docker ang config gamit ang Minimum memory limit can not be less than memory reservation limit.

Swap accounting, sa tamang pananaw

Karamihan ng VPS image ay walang swap file. Patakbuhin ang swapon --show at free -h. Kung zero ang kabuuang swap, walang epekto ang alinmang swap-related setting sa ibaba, at purong RAM cap ang memory limit mo.

Hindi halaga ng swap ang memswap_limit. Kabuuan ito ng memory at swap. Sa mem_limit: 1g at memswap_limit: 2g, magkakaroon ang container ng 1GB RAM at 1GB swap. Kapag magkapantay ang dalawang value, walang swap ang container. Kapag itinakda ang mem_limit at hindi itinakda ang memswap_limit, maaari muling gumamit ang container ng swap hanggang sa laki ng memory limit nito.

Ang Ubuntu 24.04 at Debian 13 ay gumagamit ng cgroup v2 bilang default. Dito, hiwalay na counter ang swap (memory.swap.max), at gumagana ito nang walang karagdagang setup. Ang lumang mensaheng Your kernel does not support swap limit capabilities ay nagmumula sa mga cgroup v1 host na nag-boot nang walang swapaccount=1. Sa mga host na iyon, nananatili ang memory limit pero hindi ginagamit ang bahagi para sa swap.

Maging tapat sa pakinabang ng swap. Pinapabagal nito ang OOM kill; hindi nito binabawasan ang posibilidad nito, dahil pupunuin ng memory leak ang swap nang kasingdali ng RAM. Samantala, ang container na patuloy na gumagamit ng swap sa shared VPS storage ay nagpapabagal sa lahat ng iba pang serbisyo sa server. Para sa anumang serbisyong sensitibo sa latency, mas mabilis at mas predictable ang failure kapag tama ang limit at walang swap.

Kung bakit mas mataas ang tingin sa memory usage kaysa sa aktuwal

Kasama sa figure na MEM USAGE sa docker stats ang page cache, kaya ang container na nagbabasa ng malalaking file ay umaakyat hanggang sa limit nito at nananatili roon. Normal ito at hindi ito memory leak, dahil nire-reclaim ang clean cache bago pa man tawagin ang OOM killer. Ang serbisyo gaya ng self-hosted Jellyfin media server ay magmumukhang palaging malapit sa ceiling nito dahil dito.

Hatiin ang value sa cache at aktuwal na working set mula sa loob ng container:

docker compose exec app grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat
docker compose exec app cat /sys/fs/cgroup/memory.events

Ang anon ay anonymous memory, ang working set na hindi maaaring i-drop. Ang file ay page cache, na maaaring i-drop. Itakda ang limit batay sa anon at magdagdag ng margin, hindi batay sa kabuuang value. Malinaw na ipinapakita ng file na memory.events ang aktuwal na kalagayan: ang oom_kill counter na higit sa zero ay nangangahulugang may pinatay ang kernel sa loob ng container na ito mula nang magsimula ito, at ang tumataas na max counter ay nangangahulugang kasalukuyang pinananatili ang container sa ceiling nito. Kailangan ng dalawang command ng shell at coreutils sa loob ng image, kaya mabibigo ang mga ito sa distroless o scratch image.

Mga limitasyon sa sizing sa isang 8GB VPS

Magsimula sa host, hindi sa mga app. Sa isang 8GB VPS, maglaan ng humigit-kumulang 1GB para sa kernel, Docker daemon, sshd, journald, at sarili mong login shell. Nasa 7GB ang matitira para ilaan, at dapat mas mababa rito ang kabuuan ng mga limitasyon ng lahat ng container. Gumagana ang overcommitting hanggang sa araw na sabay mag-peak ang dalawang serbisyo.

Isang praktikal na hati para sa isang 8GB na server:

  • Reverse proxy: 128m limit. Maliit itong process, at agad na matutukoy ng ganitong kasikip na limit ang hindi nakokontrol na config reload.
  • PostgreSQL: 2g limit, at ang shared_buffers ay itakda sa humigit-kumulang 512MB sa database config.
  • Application container: 1g limit.
  • Background worker: 512m limit.
  • Media o file service: 2g limit, na karamihan ay mapupunta sa page cache.

Huwag basta kopyahin ang mga numerong ito sa sarili mong stack. Patakbuhin ang mga serbisyo sa tunay na load sa loob ng isang araw, i-monitor ang docker stats, kunin ang peak na anon value ng bawat container, at magdagdag ng humigit-kumulang kalahati nito bilang headroom. Mas masama ang limit na masyadong masikip kaysa walang limit, dahil pinapahinto nito ang maayos na serbisyo kapag may normal na pagtaas ng traffic.

May isang karaniwang problema na dapat talakayin nang hiwalay. Hindi nakikita ng karamihan ng runtime ang limit maliban kung sasabihan mo sila tungkol dito. Kusang itatakda ng PostgreSQL ang shared_buffers at work_mem nang lampas sa container limit nito, kaya maaari itong ma-kill. Kailangan ng isang JVM (Java virtual machine) ang -XX:MaxRAMPercentage=75 upang ibatay ang sizing ng heap sa cgroup limit sa halip na sa RAM ng host. Kailangan ng Node.js ang --max-old-space-size sa megabytes, na dapat mas mababa sa container limit, kung hindi ay palalakihin ng garbage collector ang heap hanggang makialam ang kernel. Ganito rin ang sitwasyon sa Ollama, ngunit ibang knob ang ginagamit, dahil pinapalaki ng pagtaas ng num_ctx ang KV cache nang daan-daang megabytes at namamatay ang container sa kalagitnaan ng mahabang prompt. Hindi nakikipagnegosasyon ang cgroup. Pinapatay nito.

Ganap na iba ang pag-uugali ng CPU limits

Ang cpus: "1.5" ay nangangahulugang 150% ng isang core. Ipinapatupad ito bilang CFS (completely fair scheduler) quota. Nakakakuha ang container ng 150ms na CPU time sa bawat 100ms na period, na pinaghahatian ng lahat ng thread nito. Kapag nagamit na nito ang oras na iyon, paghihintayin ito ng kernel hanggang sa susunod na period.

Ito ang mahalagang pagkakaiba. Kapag lumampas ang isang container sa memory limit nito, pinapatay ito. Kapag lumampas ito sa CPU limit nito, tina-throttle ito at nagpapatuloy, pero mas mabagal. Kaya ligtas magtakda ng mas agresibong CPU limit, samantalang kailangang may headroom ang memory limit.

Ang cpu_shares ay ibang tool: relative weight ito na may epekto lamang kapag talagang saturated ang mga CPU. Ang dalawang container na may shares na 1024 at 512 ay humahati sa isang busy core nang humigit-kumulang two-to-one. Sa isang idle na machine, walang pinaghihigpitan sa alinman sa mga ito. Gamitin ang shares upang itakda ang priority ng mga serbisyo ayon sa kahalagahan, at gamitin ang cpus kapag kailangan mo ng aktuwal na ceiling, halimbawa upang hindi maagawan ng nightly transcode job ng CPU ang web server.

FAQ

Gumagana ba ang deploy.resources.limits kapag walang Docker Swarm?

Oo. Inilalapat ng Compose V2 ang deploy.resources.limits at deploy.resources.reservations kapag pinatakbo mo ang docker compose up sa iisang host. Kumpirmahin ito gamit ang docker inspect --format '{{.HostConfig.Memory}}' <container>, na nagpi-print ng limit sa bytes at nagpi-print ng 0 kapag walang inilapat na limit. Ang mga key sa loob ng deploy na talagang nangangailangan ng Swarm ay mode, placement, update_config, at endpoint_mode.

Ano ang ibig sabihin ng exit code 137 sa Docker Compose?

Ibig sabihin, nakatanggap ang pangunahing proseso ng SIGKILL, dahil ang 137 ay 128 dagdag ang signal 9. Karaniwang sanhi ang kernel OOM killer, pero pareho ring code ang ibinibigay kapag nag-expire ang shutdown timeout habang binabalewala ng app ang SIGTERM. Patakbuhin ang docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' <container> upang matukoy ang pagkakaiba. Ang true 137 ay memory kill, samantalang hindi ganoon ang false 137.

Dapat ko bang itakda ang mem_limit o deploy.resources.limits.memory?

Pareho itong gumagana sa docker compose. Ang deploy.resources.limits.memory ang kasalukuyang anyo sa Compose Specification at mas magandang default para sa bagong file. Panatilihin ang mem_limit kung ginagamit na ng iba pang bahagi ng file mo ang mga lumang top-level key. Kapag itinakda ang pareho sa isang service, mas mahirap lamang basahin ang file, kaya pumili ng isa at beripikahin ang resulta gamit ang docker inspect.

Bakit umaabot sa buong memory limit nito ang container ko pero hindi ito napatitigil?

Kasama sa usage figure sa docker stats ang page cache. Awtomatikong binabawi ito ng kernel kapag may pressure sa memory, sa halip na mag-trigger ng OOM kill. Patakbuhin ang docker compose exec <service> grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat at basahin ang value ng anon, na tumutukoy sa working set na hindi maaaring bawiin. Ang mataas na value ng file kasabay ng mababang value ng anon ay nagpapahiwatig na nagsasagawa ang container ng disk input at output, hindi na malapit na itong mag-terminate.

Gaano karaming RAM ang dapat kong iwanang hindi inilalaan sa isang 8GB VPS?

Maglaan ng humigit-kumulang 1GB para sa kernel, Docker daemon, sshd, journald, at sarili mong shell. Pagkatapos, panatilihing mas mababa sa natitirang 7GB ang kabuuan ng lahat ng container limit. Subaybayan ang peak na value ng anon ng bawat container sa ilalim ng totoong load sa loob ng isang araw bago magtakda ng mga value, at ituring ang kabuuan bilang budget, hindi target na kailangang punan.