Docker Compose میں OOM روکنے کے لیے memory limits
Docker Compose میں memory اور CPU limits لگائیں تاکہ ایک container آپ کے VPS کو بند نہ کرے۔ deploy.resources، mem_limit، exit 137، swap اور sizing کی وضاحت۔
Docker Compose کی memory limit کیا کرتی ہے
Docker Compose کی memory limit وہ سخت حد ہے جو Linux kernel ایک container کے cgroup پر عائد کرتا ہے۔ cgroup سے مراد control group ہے، یعنی kernel کی وہ سہولت جو processes کے کسی مجموعے کے وسائل کی پیمائش کرتی ہے۔ کسی service پر deploy.resources.limits.memory مقرر کرنے کے بعد وہ container اس میں درج مقدار سے زیادہ memory استعمال نہیں کر سکتا۔ جب container اس حد سے تجاوز کرنے کی کوشش کرتا ہے تو kernel اس کے اندر موجود کسی process کو ختم کر دیتا ہے، اور container عموماً code 137 کے ساتھ بند ہو جاتا ہے۔
یہ خاص طور پر VPS پر اہم ہے، جہاں RAM محدود ہوتی ہے اور host کی اضافی memory دستیاب نہیں ہوتی۔ memory leak یا خراب query رکھنے والا ایک container 8GB والے server کی تمام خالی memory استعمال کر سکتا ہے۔ اس کے بعد kernel عموماً database یا آپ کے SSH session جیسے کسی process کو ختم کر دیتا ہے، نہ کہ لازماً اس container کو جس نے مسئلہ پیدا کیا ہو۔ Limits پورے server کی خرابی کو ایک ایسی service تک محدود کر دیتی ہیں جو دوبارہ start ہو سکتی ہے۔
services:
app:
image: ghcr.io/example/app:1.4
deploy:
resources:
limits:
cpus: "1.5"
memory: 1g
reservations:
memory: 256mاسے apply کریں اور تصدیق کریں کہ limit فعال ہے:
docker compose up -d
docker stats --no-streamMEM USAGE / LIMIT column میں تقریباً 142MiB / 1GiB جیسی value دکھائی دینی چاہیے۔ اگر limit column میں host کی پوری RAM دکھائی دے تو setting apply نہیں ہوئی۔ اس وقت تک اس guide کے باقی حصے مددگار نہیں ہوں گے جب تک یہ setting apply نہ ہو جائے۔ اگر compose file آپ کے لیے نئی ہے تو VPS کے لیے Docker Compose کی بنیادی معلومات اس file layout کی وضاحت کرتی ہیں جس پر یہ guide مبنی ہے۔
deploy.resources.limits یا mem_limit: کون سا لاگو ہوتا ہے
ایک ہی تصور کے لیے دو مختلف تحریری صورتیں موجود ہیں، اسی لیے یہ معاملہ الجھن پیدا کرتا ہے۔
mem_limit، mem_reservation، memswap_limit، cpus اور cpu_shares پرانے Compose file formats سے وراثت میں ملنے والی top-level service keys ہیں۔ deploy.resources Swarm schema سے آیا تھا اور اب Compose Specification کا حصہ ہے، جسے docker compose آج پڑھتا ہے۔
دونوں ایک ہی host پر کام کرتے ہیں۔ Docker Compose، یعنی docker compose plugin، جب آپ docker compose up چلاتے ہیں تو deploy.resources.limits اور deploy.resources.reservations لاگو کرتا ہے؛ اس کے لیے کہیں بھی Swarm cluster ضروری نہیں۔ جانچ کے وقت plugin کی v5 series جاری تھی، اور 2026-10-10 کو v5.6.0 تازہ ترین stable release تھی۔ یہ v2 سے براہِ راست v5 پر چلا گیا اور 3 اور 4 کو چھوڑ دیا گیا، تاکہ پرانے Compose file format versions کے ساتھ الجھن نہ ہو۔ v5 کی کسی release نے resource limits کے طریقۂ کار کو تبدیل نہیں کیا۔ deploy block کے Swarm-oriented حصے دوسری keys ہیں، مثلاً placement، update_config اور endpoint_mode۔ یہ docker stack deploy کے لیے nodes کے cluster میں scheduling اور rollouts بیان کرتی ہیں۔ اس لیے یہ عام مشورہ غلط ہے کہ "deploy کے لیے Swarm درکار ہے"۔ resources subsection کے لیے یہ بات درست نہیں، اور اس پر عمل کرنے سے آپ کی services پر کوئی limit لاگو نہیں ہوتی۔
ہر project میں ایک ہی تحریری صورت استعمال کریں۔ ایک ہی service میں mem_limit: 512m اور deploy.resources.limits.memory: 1g لکھنا صرف پڑھنے میں مشکل نہیں بلکہ مسئلہ بھی پیدا کرتا ہے۔ Compose Specification کے مطابق، جب mem_limit set ہو تو اسے limits.memory کے ساتھ consistent ہونا چاہیے۔ اگر دونوں values مختلف ہوں تو specification یہ نہیں بتاتی کہ کون سی value ترجیح حاصل کرے گی۔ آپ جو بھی تحریری صورت استعمال کریں، یہ تصدیق ضرور کریں کہ daemon نے حقیقت میں کیا لاگو کیا:
docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.MemoryReservation}} {{.HostConfig.NanoCpus}}' app-1Memory values bytes میں ہوتی ہیں، اس لیے 1g کو 1073741824 کے طور پر print کیا جاتا ہے۔ CPU nano CPUs میں ہوتا ہے، اس لیے 1.5 کو 1500000000 کے طور پر print کیا جاتا ہے۔ کسی بھی field میں 0 کا مطلب ہے کہ کوئی limit set نہیں کی گئی۔ Docker کی جانب سے قبول کی جانے والی کم ترین memory limit 6m ہے؛ اس سے کم limit پر container start نہیں ہوتا۔
جب container حد تک پہنچتا ہے تو کیا ہوتا ہے
container سست نہیں ہوتا۔ یہ بند ہو جاتا ہے۔
جب کوئی process page طلب کرتا ہے اور cgroup پہلے ہی اپنے memory.max پر ہو، تو kernel پہلے اسی cgroup کے اندر ممکنہ چیزیں reclaim کرتا ہے: پہلے clean page cache، پھر وہ pages جنہیں swap کیا جا سکتا ہے۔ اگر reclaim سے کافی جگہ نہ بنے تو cgroup OOM (out of memory) killer container کے اندر کسی process کو منتخب کر کے اسے SIGKILL بھیج دیتا ہے۔ Container کے PID 1 کو ختم کرنے سے container بھی ختم ہو جاتا ہے۔ Exit code 137 دراصل 128 جمع signal 9 ہے، اس لیے 137 کسی بھی SIGKILL کی علامت ہے؛ یہ بذاتِ خود OOM کا ثبوت نہیں۔
docker compose ps -a
docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' app-1true 137 OOM kill ہے۔ false 137 عموماً اس بات کی نشاندہی کرتا ہے کہ کسی اور چیز نے SIGKILL بھیجا ہے (کوئی program جان بوجھ کر بھی 137 پر exit کر سکتا ہے، لیکن یہ کم ہوتا ہے)، اور عام وجہ یہ ہوتی ہے کہ docker compose stop اپنی دس سیکنڈ کی grace period پوری کر لیتا ہے کیونکہ app نے SIGTERM کو نظرانداز کیا۔ اس فرق کو سمجھنے سے کئی گھنٹے بچتے ہیں، کیونکہ دونوں مسائل کا آپس میں کوئی تعلق نہیں۔
یہ event مزید دو مقامات پر record ہوتا ہے۔ daemon کو live دیکھیں:
docker events --filter event=oomپھر kernel log پڑھیں، کیونکہ restart کے بعد بھی یہی record برقرار رہتا ہے:
sudo dmesg -T | grep -i -E 'memory cgroup out of memory|killed process'cgroup kill ایسی line لکھتا ہے جو Memory cgroup out of memory: Killed process 24713 (node) سے شروع ہوتی ہے۔ اس line کا مطلب ہے کہ کسی ایک container نے اپنی حد تک پہنچ کر OOM kill پیدا کیا، جبکہ host کے پاس اب بھی کافی free RAM ہو سکتی ہے۔ Memory cgroup prefix کے بغیر line host OOM کی نشاندہی کرتی ہے، یعنی پوری machine کی RAM ختم ہو گئی۔ Limits کا مقصد اسی failure کو روکنا ہے، اس لیے ایسی line عموماً اس بات کی نشاندہی کرتی ہے کہ limits کا مجموعہ بہت زیادہ ہے، کچھ services پر کوئی limit مقرر نہیں، یا Docker کے باہر کوئی چیز آپ کے چھوڑے ہوئے headroom سے زیادہ memory استعمال کر رہی ہے۔
restart: unless-stopped کے ساتھ OOM loop آسانی سے چھپ جاتا ہے، کیونکہ service کے مرنے کے ایک سیکنڈ بعد docker compose ps میں up دکھائی دیتی ہے۔ Uptime column اور restart count دیکھیں، اور limit کو ایسے healthcheck کے ساتھ جو app کو unhealthy ظاہر کرے استعمال کریں، تاکہ بار بار مرنے والا container آپ کے مسلسل نگرانی کیے بغیر بھی نمایاں رہے۔
Reservation صرف ایک اشارہ ہے، limit اصول ہے
reservations.memory (پرانا mem_reservation) ایک soft floor ہے۔ Docker اسے ایسی soft limit قرار دیتا ہے جو daemon کو host پر contention یا کم memory محسوس ہونے پر فعال ہوتی ہے۔ یہ container کو اس سے زیادہ memory استعمال کرنے سے کبھی نہیں روکتی، اور نہ ہی یہ ضمانت دیتی ہے کہ container کی درخواست کے وقت memory دستیاب ہوگی۔ یہ صرف kernel کو اس بات کی طرف مائل کرتی ہے کہ پہلے ان containers سے memory reclaim کرے جو اپنی reservation سے زیادہ استعمال کر رہے ہوں۔
اس لیے reservation اپنے طور پر کسی چیز کا تحفظ نہیں کرتی۔ اسے ایسی service کی نشان دہی کے لیے استعمال کریں جسے دباؤ کے وقت ترجیحی سلوک دینا چاہتے ہیں، اور safety کے لیے limit پر انحصار کریں۔ reservation کو limit سے کم رکھیں، ورنہ container start نہیں ہوگا: Docker configuration کو Minimum memory limit can not be less than memory reservation limit کے ساتھ reject کر دیتا ہے۔
Swap accounting، حقیقت پسندانہ جائزہ
زیادہ تر VPS images میں بالکل بھی swap file موجود نہیں ہوتی۔ swapon --show اور free -h چلائیں۔ اگر swap کی کل مقدار zero ہو تو نیچے دی گئی swap سے متعلق ہر setting بے اثر رہتی ہے، اور آپ کی memory limit صرف RAM کی حد ہوتی ہے۔
memswap_limit swap کی مقدار نہیں ہے۔ یہ memory اور swap کا مجموعہ ہے۔ mem_limit: 1g اور memswap_limit: 2g کے ساتھ container کو 1GB RAM اور 1GB swap ملتی ہے۔ دونوں values برابر مقرر کرنے سے container کے لیے کوئی swap باقی نہیں رہتی۔ mem_limit مقرر کرنے اور memswap_limit کو unset چھوڑنے سے container دوبارہ اپنی memory limit کے برابر swap استعمال کر سکتا ہے۔
Ubuntu 24.04 اور Debian 13 میں default طور پر cgroup v2 استعمال ہوتا ہے۔ اس میں swap ایک الگ counter (memory.swap.max) ہے، اور یہ اضافی setup کے بغیر کام کرتا ہے۔ پرانا message Your kernel does not support swap limit capabilities ان cgroup v1 hosts سے آتا ہے جو swapaccount=1 کے بغیر boot ہوئے ہوں۔ ان hosts پر memory limit لاگو رہتی ہے، لیکن swap والا حصہ نظرانداز کر دیا جاتا ہے۔
اس بارے میں حقیقت پسندانہ رہیں کہ swap سے کیا فائدہ ملتا ہے۔ یہ OOM kill کو کم ممکن نہیں بناتی، بلکہ اسے سست کرتی ہے، کیونکہ memory leak کرنے والا process RAM کی طرح swap بھی آسانی سے بھر دیتا ہے۔ اسی دوران shared VPS storage پر swap استعمال کرتے ہوئے ایک container مسلسل thrashing کرے تو server کی ہر دوسری service سست ہو جاتی ہے۔ latency-sensitive workloads کے لیے درست limit اور swap کے بغیر configuration زیادہ تیزی اور زیادہ قابل پیش گوئی انداز میں fail ہوتی ہے۔
میموری کا استعمال حقیقت سے زیادہ خراب کیوں دکھائی دیتا ہے
Linux میں docker stats کا MEM USAGE اعداد و شمار صرف غیر فعال page cache (cgroup v2 میں inactive_file) کو منہا کرتا ہے۔ اس لیے حال ہی میں استعمال ہونے والا cache شمار ہوتا رہتا ہے، اور بڑی فائلیں پڑھنے والا container اپنی limit کے قریب پہنچ کر وہیں رہتا ہے۔ یہ معمول کی بات ہے، memory leak نہیں، کیونکہ OOM killer کو بلانے سے پہلے clean cache reclaim کر لیا جاتا ہے۔ self-hosted Jellyfin media server جیسی service اسی وجہ سے مستقل طور پر اپنی حد کے قریب دکھائی دے گی۔
container کے اندر سے اس عدد کو cache اور حقیقی working set میں تقسیم کریں:
docker compose exec app grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat
docker compose exec app cat /sys/fs/cgroup/memory.eventsanon anonymous memory ہے، یعنی وہ working set جسے drop نہیں کیا جا سکتا۔ file page cache ہے، جسے drop کیا جا سکتا ہے۔ اپنی limit کا تعین anon اور اضافی گنجائش کی بنیاد پر کریں، کل memory کی بنیاد پر نہیں۔ memory.events فائل حتمی جواب دیتی ہے: صفر سے زیادہ oom_kill counter کا مطلب ہے کہ container کے شروع ہونے کے بعد kernel نے اس کے اندر کسی process کو kill کیا ہے، جبکہ بڑھتا ہوا max counter بتاتا ہے کہ container اس وقت اپنی حد پر پہنچا ہوا ہے۔ دونوں commands کے لیے image کے اندر shell اور coreutils درکار ہیں، اس لیے distroless یا scratch image پر یہ ناکام ہوں گی۔
8GB VPS پر وسائل کی حدود
ایپس سے نہیں بلکہ host سے آغاز کریں۔ 8GB VPS پر kernel، Docker daemon، sshd، journald اور اپنے login shell کے لیے تقریباً 1GB الگ رکھیں۔ اس طرح تقریباً 7GB دستیاب رہتی ہے، اور تمام containers کی limits کا مجموعہ اسی مقدار سے کم رہنا چاہیے۔ وسائل کی حد سے زیادہ تخصیص اس وقت تک کام کرتی ہے جب تک کسی دن دو services بیک وقت اپنی peak پر نہ پہنچ جائیں۔
8GB مشین کے لیے ایک قابلِ عمل تقسیم:
- Reverse proxy: 128m limit۔ یہ ایک چھوٹا process ہے، اور اتنی سخت limit runaway configuration reload کو فوراً پکڑ لیتی ہے۔
- PostgreSQL: 2g limit، اور database config میں
shared_buffersتقریباً 512MB پر set کریں۔ - Application container: 1g limit۔
- Background worker: 512m limit۔
- Media یا file service: 2g limit، جس کا زیادہ تر حصہ page cache ہوگا۔
ان اعداد کو اپنے stack میں نقل نہ کریں۔ Services کو ایک دن حقیقی load کے تحت چلائیں، docker stats monitor کریں، ہر container کی peak anon value نوٹ کریں، اور headroom کے طور پر اس میں تقریباً نصف مزید شامل کریں۔ بہت سخت limit کسی limit نہ ہونے سے بدتر ہے، کیونکہ یہ معمول کے traffic spike کے دوران ایک درست service کو ختم کر دیتی ہے۔
ایک مسئلہ الگ وضاحت کا مستحق ہے۔ کچھ runtimes اس limit کو نظرانداز کرتے ہیں جب تک آپ انہیں اس کے بارے میں نہ بتائیں، اور جو runtimes اسے پڑھتے ہیں وہ بھی اپنی memory کو آپ کی توقع کے مطابق size نہیں کر سکتے۔ PostgreSQL shared_buffers اور work_mem کو اپنے container limit سے زیادہ تک باآسانی بڑھا سکتا ہے اور پھر killed ہو جاتا ہے۔ جدید JVM (Java virtual machine) default طور پر cgroup limit کا پتا چلا لیتی ہے، لیکن OpenJDK جیسی HotSpot builds heap کے لیے اس limit کا صرف 25 percent دیتی ہیں۔ اس لیے -XX:MaxRAMPercentage=75 عموماً heap کو limit کا زیادہ حصہ استعمال کرنے دینے کا طریقہ ہے، جبکہ heap سے باہر memory کے لیے گنجائش باقی رہتی ہے۔ Node.js version 12 سے cgroup limit کی بنیاد پر اپنی default heap size مقرر کرتا ہے۔ یہ تقریباً container limit کے نصف تک ہوتی ہے اور تقریباً 2GB پر رک جاتی ہے۔ اس لیے --max-old-space-size صرف megabytes میں اس وقت set کریں جب آپ مختلف ceiling چاہتے ہوں، اور اسے container limit سے کم رکھیں تاکہ kernel کے مداخلت کرنے سے پہلے garbage collector کام کر سکے۔ Ollama میں بھی یہی صورتِ حال ہے، مگر knob مختلف ہے، کیونکہ num_ctx بڑھانے سے KV cache کا حجم سینکڑوں megabytes بڑھ جاتا ہے اور طویل prompt کے دوران container ختم ہو جاتا ہے۔ cgroup مذاکرات نہیں کرتا۔ یہ process کو ختم کر دیتا ہے۔
CPU limits کا طریقۂ کار بالکل مختلف ہے
cpus: "1.5" کا مطلب ایک core کے 150% کے برابر ہے، جسے CFS (completely fair scheduler) quota کے طور پر نافذ کیا جاتا ہے۔ Container کو ہر 100ms کے period میں CPU time کے 150ms ملتے ہیں، اور یہ وقت اس کے تمام threads میں مشترک ہوتا ہے۔ جب یہ وقت ختم ہو جائے تو kernel اگلے period تک اسے انتظار کرواتا ہے۔
یہی بنیادی فرق ہے۔ Memory limit سے تجاوز کرنے والے container کو kill کر دیا جاتا ہے۔ CPU limit سے تجاوز کرنے والے container کو throttle کیا جاتا ہے، لیکن وہ کم رفتار سے کام جاری رکھتا ہے۔ اس لیے CPU limit نسبتاً جارحانہ انداز میں مقرر کرنا محفوظ ہے، جبکہ memory limit میں اضافی گنجائش رکھنا ضروری ہے۔
cpu_shares ایک مختلف tool ہے: یہ relative weight ہوتا ہے، جو صرف اس وقت اثرانداز ہوتا ہے جب CPUs واقعی مکمل طور پر مصروف ہوں۔ 1024 اور 512 shares والے دو containers ایک مصروف core کو تقریباً 2:1 کے تناسب سے تقسیم کرتے ہیں، جبکہ idle system پر ان میں سے کسی پر پابندی نہیں لگتی۔ Services کی اہمیت کے مطابق ترجیح مقرر کرنے کے لیے shares استعمال کریں، اور حقیقی maximum ceiling درکار ہو تو cpus استعمال کریں۔ مثال کے طور پر، رات کو چلنے والے transcode job کو web server کے وسائل ختم کرنے سے روکنے کے لیے۔
FAQ
کیا deploy.resources.limits، Docker Swarm کے بغیر کام کرتا ہے؟
ہاں۔ Docker Compose، docker compose plugin (جو اب اپنی v5 لائن پر ہے)، ایک single host پر docker compose up چلانے کے وقت deploy.resources.limits اور deploy.resources.reservations لاگو کرتا ہے۔ docker inspect --format '{{.HostConfig.Memory}}' <container> سے اس کی تصدیق کریں۔ یہ limit کو bytes میں دکھاتا ہے اور جب کوئی limit لاگو نہ ہوئی ہو تو 0 دکھاتا ہے۔ deploy کے اندر موجود وہ keys جو Swarm کو مدنظر رکھ کر لکھی گئی ہیں، cluster سے متعلق keys ہیں، جیسے placement، update_config اور endpoint_mode۔
Docker Compose میں exit code 137 کا کیا مطلب ہے؟
اس کا تقریباً ہمیشہ مطلب یہ ہوتا ہے کہ main process کو SIGKILL موصول ہوا، کیونکہ 137، 128 جمع signal 9 کے برابر ہے۔ کوئی program جان بوجھ کر بھی 137 کے ساتھ exit کر سکتا ہے، لیکن یہ کم ہوتا ہے۔ kernel کا OOM killer عام وجہ ہے، لیکن جب کوئی app SIGTERM کو نظرانداز کرے تو shutdown timeout بھی یہی code پیدا کرتا ہے۔ دونوں میں فرق معلوم کرنے کے لیے docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' <container> چلائیں۔ true 137 memory kill ہے، جبکہ false 137 ایسا نہیں ہے۔
کیا مجھے mem_limit یا deploy.resources.limits.memory set کرنا چاہیے؟
docker compose کے ساتھ دونوں میں سے کوئی بھی کام کرتا ہے۔ deploy.resources.limits.memory موجودہ Compose Specification کی شکل ہے اور نئی file کے لیے بہتر default ہے۔ اگر آپ کی file کے باقی حصے پہلے ہی پرانی top-level keys استعمال کرتے ہیں تو mem_limit برقرار رکھیں۔ Compose Specification کے مطابق دونوں set ہونے کی صورت میں ان کی values یکساں ہونی چاہییں، اس لیے ایک طریقہ منتخب کریں اور docker inspect سے نتیجے کی تصدیق کریں۔
میرا container kill کیے بغیر اپنی مکمل memory limit تک کیوں پہنچا ہوا ہے؟
docker stats میں usage figure حال ہی میں استعمال ہونے والے page cache کو بھی شمار کرتی ہے۔ kernel دباؤ کے وقت اسے حذف کر دیتا ہے، بجائے اس کے کہ OOM kill شروع کرے۔ docker compose exec <service> grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat چلائیں اور anon value پڑھیں۔ یہ وہ working set ہے جسے reclaim نہیں کیا جا سکتا۔ کم anon value کے ساتھ زیادہ file value اس بات کی علامت ہے کہ container disk input اور output کر رہا ہے، نہ کہ فوراً بند ہونے والا ہے۔
8GB VPS پر کتنی RAM غیر مختص چھوڑنی چاہیے؟
kernel، Docker daemon، sshd، journald اور اپنے shell کے لیے تقریباً 1GB چھوڑیں۔ اس کے بعد تمام containers کی limits کا مجموعہ باقی 7GB سے کم رکھیں۔ حقیقی load کے دوران ہر container کی زیادہ سے زیادہ anon value کو ایک دن تک monitor کریں، پھر اعداد مقرر کریں۔ مجموعی memory کو بھرنے کے ہدف کے بجائے budget سمجھیں۔