Docker Compose में memory limit कैसे सेट करें
Docker Compose में deploy.resources का उपयोग करके CPU और RAM की सीमा तय करें। यह गाइड बताती है कि कैसे mem_limit लगाकर container को exit 137 के साथ क्रैश होने से बचाएं।
Docker Compose memory limit क्या करता है
Docker Compose memory limit एक hard cap है जिसे Linux kernel किसी container के cgroup (control group, वह kernel feature जो processes के समूह के लिए संसाधनों को मापता है) पर लगाता है। किसी service पर deploy.resources.limits.memory सेट करें और वह container आपके द्वारा लिखी गई संख्या से अधिक RAM का उपयोग कभी नहीं कर पाएगा। जब वह ऐसा करने का प्रयास करता है, तो kernel container के अंदर की एक process को मार देता है, और container आमतौर पर code 137 के साथ exit हो जाता है।
यह VPS पर सबसे अधिक मायने रखता है, जहाँ RAM निश्चित होती है और उधार लेने के लिए कोई अतिरिक्त host memory नहीं होती। memory leak या खराब query वाला एक container 8GB के box पर उपलब्ध हर free page को ले लेगा। इसके बाद kernel उस process को मार देता है जिसे वह सबसे खराब मानता है, जो अक्सर समस्या पैदा करने वाले container के बजाय database या आपका SSH session होता है। Limits पूरे सर्वर के outage को केवल एक service के restart में बदल देती हैं।
services:
app:
image: ghcr.io/example/app:1.4
deploy:
resources:
limits:
cpus: "1.5"
memory: 1g
reservations:
memory: 256mइसे लागू करें और पुष्टि करें कि limit live है:
docker compose up -d
docker stats --no-streamMEM USAGE / LIMIT column में 142MiB / 1GiB जैसा कुछ लिखा होना चाहिए। यदि limit column में पूरी host RAM दिखाई दे रही है, तो setting लागू नहीं हुई है, और जब तक यह ठीक नहीं हो जाती, तब तक यह guide आगे मदद नहीं करेगी। यदि compose file आपके लिए नई है, तो VPS के लिए Docker Compose की बुनियादी बातें उस file layout को कवर करती हैं जिस पर यह आधारित है।
deploy.resources.limits या mem_limit: कौन सा लागू होता है
एक ही विचार के लिए दो वर्तनी मौजूद हैं, यही कारण है कि यह भ्रमित करने वाला है।
mem_limit, mem_reservation, memswap_limit, cpus और cpu_shares पुराने Compose फ़ाइल स्वरूपों से विरासत में मिली टॉप-लेवल सर्विस कुंजियाँ हैं। deploy.resources स्वार्म (Swarm) स्कीमा से आया है और अब Compose Specification का हिस्सा है, जो कि वह स्वरूप है जिसे docker compose आज पढ़ता है।
दोनों एक ही होस्ट पर काम करते हैं। Docker Compose, जो कि docker compose प्लगइन है, deploy.resources.limits और deploy.resources.reservations को तब लागू करता है जब आप docker compose up चलाते हैं, भले ही कहीं भी कोई Swarm क्लस्टर न हो। यह प्लगइन अब अपनी v5 लाइन पर है (2026-10-10 को जाँचने पर v5.6.0 नवीनतम स्टेबल रिलीज़ थी)। यह v2 से सीधे v5 पर आ गया, पुराने Compose फ़ाइल स्वरूप संस्करणों के साथ भ्रम से बचने के लिए 3 और 4 को छोड़ दिया गया, और किसी भी v5 रिलीज़ ने संसाधन सीमाओं (resource limits) को संभालने के तरीके को नहीं बदला है। deploy ब्लॉक के स्वार्म-उन्मुख हिस्से अन्य कुंजियाँ हैं, जैसे placement, update_config और endpoint_mode, जो docker stack deploy के लिए नोड्स के क्लस्टर में शेड्यूलिंग और रोलआउट का वर्णन करते हैं। इसलिए यह आम सलाह कि "deploy के लिए Swarm की आवश्यकता है" संसाधन उपधारा (resources subsection) के लिए गलत है, और इसका पालन करने से आपकी सेवाओं पर कोई सीमा नहीं रहती है।
प्रति प्रोजेक्ट एक वर्तनी चुनें। एक ही सर्विस पर mem_limit: 512m और deploy.resources.limits.memory: 1g लिखना पढ़ने में कठिन होने से कहीं अधिक है: Compose Specification कहती है कि जब mem_limit सेट हो तो इसे limits.memory के अनुरूप होना चाहिए, और यह नहीं बताती कि असहमत होने पर कौन सा मान प्रभावी होगा। आप जो भी वर्तनी उपयोग करें, पुष्टि करें कि डेमन (daemon) ने वास्तव में क्या लागू किया है:
docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.MemoryReservation}} {{.HostConfig.NanoCpus}}' app-1मेमोरी मान बाइट्स में होते हैं, इसलिए 1g को 1073741824 के रूप में प्रिंट किया जाता है। CPU नैनो-CPUs में होता है, इसलिए 1.5 को 1500000000 के रूप में प्रिंट किया जाता है। किसी भी फ़ील्ड में 0 का अर्थ है कि कोई सीमा निर्धारित नहीं की गई थी। Docker द्वारा स्वीकार की जाने वाली सबसे छोटी मेमोरी सीमा 6m है, और उससे कम होने पर कंटेनर स्टार्ट होने से मना कर देता है।
जब कोई container अपनी सीमा (limit) तक पहुँच जाता है तो क्या होता है
Container की गति धीमी नहीं होती है। वह बंद हो जाता है।
जब कोई process page की मांग करती है और cgroup पहले से ही अपनी memory.max पर होता है, तो kernel सबसे पहले उस cgroup के भीतर जो कुछ भी संभव हो उसे reclaim करता है: clean page cache, और फिर वे pages जिन्हें swap किया जा सकता है। यदि reclaim से पर्याप्त memory खाली नहीं होती है, तो cgroup OOM (out of memory) killer container के भीतर एक process को चुनता है और उसे SIGKILL भेजता है। Container के PID 1 को kill करने से 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 को अनदेखा कर दिया था। यह अंतर घंटों का समय बचाता है, क्योंकि इन दोनों समस्याओं में कुछ भी समान नहीं है।
दो और स्थान इस घटना को record करते हैं। Daemon को live monitor करें:
docker events --filter event=oomफिर kernel log पढ़ें, जो वह record है जो restart के बाद भी सुरक्षित रहता है:
sudo dmesg -T | grep -i -E 'memory cgroup out of memory|killed process'Cgroup kill एक ऐसी line print करता है जो Memory cgroup out of memory: Killed process 24713 (node) से शुरू होती है। उस line का मतलब है कि एक container अपनी खुद की सीमा तक पहुँच गया है, और यह तब भी दिखाई दे सकता है जब host के पास अभी भी पर्याप्त RAM खाली हो। Memory cgroup prefix के बिना वाली line एक host OOM है, जिसका अर्थ है कि machine की खुद की RAM समाप्त हो गई है। यह वह विफलता है जिसे रोकने के लिए limits बनाई गई हैं, इसलिए इसे देखने का मतलब आमतौर पर यह है कि आपकी limits का योग बहुत अधिक है, कुछ services की कोई limit नहीं है, या Docker के बाहर की कोई चीज़ आपके द्वारा छोड़ी गई headroom से अधिक RAM का उपयोग कर रही है।
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 को इससे ऊपर जाने से कभी नहीं रोकता, और यह इस बात की गारंटी भी नहीं देता कि जब container memory मांगेगा तो वह उपलब्ध होगी। यह केवल kernel को उन containers से memory वापस लेने के लिए प्रेरित करता है जो अपनी reservation सीमा से ऊपर हैं।
इसलिए, reservation अपने आप में किसी चीज़ की सुरक्षा नहीं करता है। इसका उपयोग उन services को चिह्नित करने के लिए करें जिन्हें आप दबाव की स्थिति में प्राथमिकता देना चाहते हैं, और सुरक्षा के लिए limit पर निर्भर रहें। Reservation को limit से कम रखें, अन्यथा container start नहीं होगा: Docker इस configuration को Minimum memory limit can not be less than memory reservation limit के साथ अस्वीकार कर देता है।
Swap accounting, ईमानदारी से
अधिकांश VPS images में कोई swap file नहीं होती है। swapon --show और free -h चलाएँ। यदि swap का कुल योग शून्य है, तो नीचे दी गई swap से संबंधित कोई भी सेटिंग काम नहीं करेगी, और आपकी memory limit केवल RAM की सीमा होगी।
memswap_limit swap की मात्रा नहीं है। यह memory और swap का कुल योग है। mem_limit: 1g और memswap_limit: 2g के साथ, container को 1GB RAM और 1GB swap मिलता है। दोनों मानों को बराबर सेट करने पर container को कोई swap नहीं मिलता है। mem_limit को सेट करने और memswap_limit को unset छोड़ने पर container अपनी memory limit के बराबर तक swap का उपयोग कर सकता है।
Ubuntu 24.04 और Debian 13 डिफ़ॉल्ट रूप से cgroup v2 का उपयोग करते हैं, जहाँ swap एक अलग counter (memory.swap.max) है और यह बिना किसी अतिरिक्त सेटअप के काम करता है। पुराना संदेश Your kernel does not support swap limit capabilities उन cgroup v1 hosts से आता है जो swapaccount=1 के बिना boot होते हैं। उन पर memory limit तो लागू होती है लेकिन swap वाला हिस्सा अनदेखा कर दिया जाता है।
Swap आपको क्या लाभ देता है, इस बारे में ईमानदार रहें। यह OOM kill को धीमा बनाता है, कम संभावित नहीं, क्योंकि एक leaking process RAM की तरह ही swap को भी भर देती है। इस बीच, shared VPS storage पर swap का उपयोग करने वाला container उस box पर चल रही अन्य सभी services को धीमा कर देता है। latency के प्रति संवेदनशील किसी भी चीज़ के लिए, बिना swap के एक सही limit सेट करना बेहतर है, क्योंकि यह अधिक तेज़ी से और पूर्वानुमानित तरीके से fail होता है।
मेमोरी का उपयोग वास्तविक स्थिति से अधिक क्यों दिखता है
Linux पर, docker stats में MEM USAGE का आंकड़ा केवल inactive page cache (cgroup v2 पर inactive_file) को घटाता है। इसलिए, हाल ही में उपयोग किया गया cache अभी भी गिना जाता है, और जो container बड़ी फाइलें पढ़ता है, उसका उपयोग अपनी सीमा के करीब पहुँच जाता है और वहीं बना रहता है। यह सामान्य है और यह कोई leak नहीं है, क्योंकि OOM killer के सक्रिय होने से पहले clean cache को reclaim कर लिया जाता है। एक self-hosted Jellyfin media server जैसी सेवा ठीक इसी कारण से स्थायी रूप से अपनी सीमा के करीब दिखाई देगी।
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 जिसे हटाया नहीं जा सकता। file page cache है, जिसे हटाया जा सकता है। अपनी सीमा का निर्धारण anon और कुछ अतिरिक्त margin के आधार पर करें, न कि कुल योग के आधार पर। memory.events फाइल इस बहस को पूरी तरह समाप्त कर देती है: शून्य से ऊपर का oom_kill काउंटर यह दर्शाता है कि kernel ने container शुरू होने के बाद से इसमें कुछ न कुछ kill किया है, और बढ़ता हुआ max काउंटर यह दर्शाता है कि container को अभी उसकी सीमा पर रोका जा रहा है। दोनों commands के लिए image के अंदर shell और coreutils की आवश्यकता होती है, इसलिए वे distroless या scratch image पर काम नहीं करती हैं।
8GB VPS पर साइजिंग की सीमाएं
होस्ट से शुरुआत करें, न कि ऐप्स से। 8GB VPS पर, kernel, Docker daemon, sshd, journald और अपने login shell के लिए लगभग 1GB अलग रखें। इससे लगभग 7GB मेमोरी उपलब्ध रहती है, और सभी कंटेनर्स की कुल सीमा इससे कम होनी चाहिए। ओवरकमिटिंग (overcommitting) तब तक काम करती है जब तक कि दो सेवाएं एक साथ पीक पर न पहुंच जाएं।
8GB बॉक्स के लिए एक व्यावहारिक विभाजन:
- Reverse proxy: 128m की सीमा। यह एक छोटी प्रक्रिया है, और इतनी सख्त सीमा एक खराब कॉन्फ़िगरेशन रीलोड को तुरंत पकड़ लेती है।
- PostgreSQL: 2g की सीमा, जिसमें डेटाबेस कॉन्फ़िगरेशन में
shared_buffersको लगभग 512MB पर सेट किया गया हो। - Application container: 1g की सीमा।
- Background worker: 512m की सीमा।
- Media या file service: 2g की सीमा, जिसका अधिकांश हिस्सा पेज कैश (page cache) होगा।
इन नंबरों को अपने स्टैक में कॉपी न करें। सेवाओं को एक दिन के लिए वास्तविक लोड के तहत चलाएं, docker stats पर नज़र रखें, प्रति कंटेनर अधिकतम anon मान लें और उसमें लगभग आधा हिस्सा अतिरिक्त हेडरूम के रूप में जोड़ें। बहुत सख्त सीमा तय करना बिना सीमा के होने से भी बदतर है, क्योंकि यह सामान्य ट्रैफिक स्पाइक के दौरान एक स्वस्थ सेवा को भी बंद कर देती है।
एक जाल विशेष उल्लेख के योग्य है। कुछ रनटाइम्स सीमा को तब तक अनदेखा करते हैं जब तक आप उन्हें इसके बारे में न बताएं, और जो इसे पढ़ते हैं वे भी शायद उस तरह से खुद को साइज न करें जैसा आप उम्मीद करते हैं। PostgreSQL खुशी-खुशी shared_buffers और work_mem को अपनी कंटेनर सीमा से आगे बढ़ा देगा और मारा जाएगा। एक आधुनिक JVM (Java virtual machine) डिफ़ॉल्ट रूप से cgroup सीमा का पता लगा लेता है, लेकिन OpenJDK जैसे HotSpot बिल्ड इसे केवल 25 प्रतिशत हीप देते हैं, इसलिए -XX:MaxRAMPercentage=75 का उपयोग करना सामान्य तरीका है ताकि हीप सीमा का अधिक उपयोग कर सके और हीप के बाहर मेमोरी के लिए जगह बची रहे। Node.js ने वर्जन 12 से cgroup सीमा से अपने डिफ़ॉल्ट हीप को साइज किया है, जो कंटेनर सीमा का लगभग आधा होता है और 2GB के आसपास स्थिर हो जाता है, इसलिए --max-old-space-size को केवल तभी मेगाबाइट्स में सेट करें जब आप एक अलग सीलिंग चाहते हों, और इसे कंटेनर सीमा से नीचे रखें ताकि कर्नेल के हस्तक्षेप करने से पहले गारबेज कलेक्टर काम कर सके। Ollama के साथ भी यही कहानी है, बस इसमें एक अलग नॉब है, क्योंकि num_ctx बढ़ाने से KV cache सैकड़ों मेगाबाइट बढ़ जाता है और कंटेनर एक लंबे प्रॉम्प्ट के बीच में ही बंद हो जाता है। cgroup बातचीत नहीं करता। यह बस बंद कर देता है।
CPU limits का व्यवहार पूरी तरह अलग होता है
cpus: "1.5" का अर्थ है एक core का 150%, जिसे CFS (completely fair scheduler) quota के रूप में लागू किया जाता है। Container को हर 100ms की अवधि में 150ms का CPU समय मिलता है, जो उसके सभी threads में साझा होता है। जब यह समय समाप्त हो जाता है, तो kernel उसे अगली अवधि तक प्रतीक्षा करने के लिए मजबूर करता है।
यह एक महत्वपूर्ण अंतर है। यदि कोई container अपनी memory limit से ऊपर जाता है, तो उसे kill कर दिया जाता है। यदि कोई container अपनी CPU limit से ऊपर जाता है, तो उसे throttled कर दिया जाता है और वह धीमी गति से चलता रहता है। इसलिए CPU limit को आक्रामक रूप से सेट करना सुरक्षित है, जबकि memory limit के लिए अतिरिक्त headroom की आवश्यकता होती है।
cpu_shares एक अलग tool है: यह एक सापेक्ष भार (relative weight) है जो केवल तब मायने रखता है जब CPU वास्तव में saturated हों। 1024 और 512 के shares वाले दो containers एक व्यस्त core को लगभग दो-एक के अनुपात में विभाजित करते हैं, और idle box पर किसी को भी प्रतिबंधित नहीं किया जाता है। सेवाओं को उनके महत्व के आधार पर रैंक करने के लिए shares का उपयोग करें, और जब आपको वास्तविक सीमा (ceiling) की आवश्यकता हो, तो cpus का उपयोग करें। उदाहरण के लिए, किसी nightly transcode job को अपने web server के संसाधनों को खत्म करने से रोकने के लिए।
FAQ
क्या deploy.resources.limits बिना Docker Swarm के काम करता है?
हाँ। Docker Compose, जो कि docker compose प्लगइन (अब v5 लाइन पर है) का उपयोग करता है, docker compose up चलाने पर एक ही होस्ट पर deploy.resources.limits और deploy.resources.reservations लागू करता है। इसकी पुष्टि docker inspect --format '{{.HostConfig.Memory}}' <container> से करें, जो बाइट्स में सीमा प्रिंट करता है और कोई सीमा न होने पर 0 दिखाता है। deploy के भीतर Swarm को ध्यान में रखकर लिखी गई कुंजियाँ क्लस्टर वाली होती हैं, जैसे कि placement, update_config और endpoint_mode।
Docker Compose में exit code 137 का क्या अर्थ है?
इसका लगभग हमेशा मतलब यह होता है कि मुख्य प्रोसेस को SIGKILL प्राप्त हुआ है, क्योंकि 137 का मान 128 जमा सिग्नल 9 होता है (कोई प्रोग्राम जानबूझकर भी 137 के साथ एग्जिट कर सकता है, लेकिन यह दुर्लभ है)। कर्नेल OOM killer इसका सामान्य कारण है, लेकिन जब कोई ऐप SIGTERM को अनदेखा करता है तो शटडाउन टाइमआउट भी यही कोड उत्पन्न करता है। इनमें अंतर करने के लिए docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' <container> चलाएँ। true 137 एक मेमोरी किल है, और false 137 नहीं है।
क्या मुझे mem_limit सेट करना चाहिए या deploy.resources.limits.memory?
दोनों ही docker compose के साथ काम करते हैं। deploy.resources.limits.memory वर्तमान Compose Specification का प्रारूप है और नई फ़ाइल के लिए बेहतर विकल्प है। यदि आपकी फ़ाइल के बाकी हिस्से पहले से ही पुरानी टॉप-लेवल कुंजियों का उपयोग कर रहे हैं, तो mem_limit को बनाए रखें। Compose Specification के अनुसार दोनों के सेट होने पर उनमें निरंतरता होनी चाहिए, इसलिए एक को चुनें और docker inspect के साथ परिणाम की पुष्टि करें।
मेरा कंटेनर अपनी पूरी मेमोरी सीमा पर क्यों है, जबकि वह किल नहीं हो रहा है?
docker stats में उपयोग का आंकड़ा हाल ही में उपयोग किए गए पेज कैश को भी गिनता है, जिसे कर्नेल दबाव पड़ने पर OOM किल ट्रिगर करने के बजाय हटा देता है। docker compose exec <service> grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat चलाएँ और anon मान पढ़ें, जो कि वर्किंग सेट है जिसे पुनः प्राप्त नहीं किया जा सकता। कम anon मान के साथ उच्च file मान का मतलब है कि कंटेनर डिस्क इनपुट और आउटपुट कर रहा है, न कि यह कि वह बंद होने वाला है।
मुझे 8GB VPS पर कितनी RAM आवंटित किए बिना छोड़नी चाहिए?
कर्नेल, Docker daemon, sshd, journald और अपने स्वयं के शेल के लिए लगभग 1GB छोड़ दें, फिर सभी कंटेनर सीमाओं का योग शेष 7GB के भीतर रखें। संख्याएँ तय करने से पहले एक दिन वास्तविक लोड के तहत प्रति कंटेनर अधिकतम anon मान पर नज़र रखें, और कुल योग को भरने के लक्ष्य के बजाय एक बजट के रूप में मानें।