SSD Nodes Learn 🎉 VPS $5.50/माह से
गाइड Matt Connorलेखक: Matt Connor

Docker Compose में interactive shell कैसे खोलें

चल रहे Docker Compose container में interactive shell खोलने के लिए docker compose exec का उपयोग करें। यदि सेवा बंद है तो docker compose run --rm का प्रयोग करें।

docker compose exec के साथ interactive shell प्राप्त करें

docker compose exec web bash पहले से चल रहे container के अंदर एक interactive shell खोलता है, जो web service के रूप में चल रहा है। exec के बाद दिया गया नाम आपके compose.yaml से ली गई service का नाम है, न कि container का नाम। यदि image में bash उपलब्ध नहीं है, तो इसके बजाय sh का उपयोग करें।

docker compose ps
docker compose exec web bash

सबसे पहले docker compose ps चलाएं। इसमें web को running स्थिति के साथ सूचीबद्ध होना चाहिए। इसके बाद, दूसरा command आपको container के अंदर एक prompt पर ले जाता है, और exit या Ctrl-D आपको host पर वापस ले आता है। आपके बाहर निकलने के बाद भी service चलती रहती है, क्योंकि exec ने मुख्य process के साथ एक दूसरी process शुरू की थी। आपकी shell बंद करने से PID 1 (process id 1) पर कोई प्रभाव नहीं पड़ता, जो कि वह process है जिसे चलाने के लिए container बनाया गया था।

यह अंदर जाने के दो तरीकों में से एक है। exec पहले से मौजूद container से जुड़ता है। docker compose run उसी service definition से एक नया container बनाता है। इस guide में बाकी सब कुछ इसी एक अंतर पर आधारित है।

Compose में -it वैकल्पिक क्यों है लेकिन plain docker के साथ आवश्यक है

दो flags एक session के interactive भाग को नियंत्रित करते हैं। -i stdin को खुला रखता है, ताकि आप जो टाइप करें वह process तक पहुँच सके। -t एक pseudo terminal allocate करता है, जिसे TTY कहा जाता है, ताकि shell एक prompt print करे और arrow keys को handle करे। Plain docker exec डिफ़ॉल्ट रूप से दोनों को बंद रखता है, यही कारण है कि आपने जो भी उदाहरण देखा है उसमें docker exec -it लिखा होता है। docker compose exec आपके लिए दोनों को चालू कर देता है, इसलिए docker compose exec -it web bash और docker compose exec web bash एक ही काम करते हैं। Compose अभी भी -it को स्वीकार करता है ताकि पुरानी muscle memory काम करती रहे।

आप कुछ ही सेकंड में TTY की कमी महसूस करेंगे। shell चलता है, लेकिन यह कोई prompt print नहीं करता है और Ctrl-C कभी भी process तक नहीं पहुँचता है। विपरीत स्थिति, जहाँ आपको Compose से TTY allocate न करने के लिए कहना होता है, उसका अपना flag और अपना section नीचे दिया गया है।

जब इमेज में bash न हो तो क्या करें

Alpine आधारित इमेज में bash के लिए पूछने पर exec इस तरह विफल हो जाता है:

OCI runtime exec failed: exec failed: unable to start container process: exec: "bash": executable file not found in $PATH: unknown

वह संदेश exec की समस्या नहीं है। यह बताता है कि जिस बाइनरी को आपने मांगा है वह इमेज में नहीं है। Alpine में BusyBox होता है, जो ash को /bin/sh के रूप में प्रदान करता है और इसमें bash बिल्कुल नहीं होता, इसलिए sh के लिए पूछें:

docker compose exec web sh

Debian और Ubuntu आधारित इमेज, जिनमें -slim टैग शामिल हैं, में bash होता है, और bash आपको कमांड हिस्ट्री और बेहतर कंप्लीशन की सुविधा देता है। इसलिए पहले bash का प्रयास करें और फिर sh पर वापस आएं। sh लगभग हर सामान्य उद्देश्य वाली इमेज में मौजूद होता है।

कुछ इमेज में कोई शेल नहीं होता है। Distroless इमेज और FROM scratch से बनी इमेज में केवल एप्लिकेशन बाइनरी और उसकी लाइब्रेरी होती हैं और कुछ नहीं, ऐसा जानबूझकर किया जाता है, क्योंकि जो शेल वहां मौजूद नहीं है उसका उपयोग आपके खिलाफ नहीं किया जा सकता है। उनमें, sh उसी संदेश के साथ विफल हो जाता है और प्रयास करने के लिए कुछ नहीं बचता है। दो तरीके काम करते हैं। Google की distroless इमेज :debug टैग प्रकाशित करती हैं जो BusyBox शेल जोड़ते हैं, इसलिए टैग को अस्थायी रूप से बदलने से आप अंदर जा सकते हैं। या टारगेट के नेमस्पेस के अंदर एक अलग कंटेनर शुरू करें:

CID=$(docker compose ps -q web)
docker run --rm -it --network "container:$CID" --pid "container:$CID" nicolaka/netshoot

अब आपके पास netshoot के टूल्स हैं जो एप्लिकेशन के नेटवर्क की ओर इशारा कर रहे हैं, इसलिए curl localhost:8080 और ss -lntp ऐसे व्यवहार करते हैं जैसे आप इसके अंदर हों। जो फाइलसिस्टम आप देखते हैं वह netshoot का है, न कि ऐप का। चूंकि प्रोसेस नेमस्पेस साझा किया गया है, इसलिए जब आप root होते हैं तो ls /proc/1/root/ टारगेट की अपनी फाइलों तक पहुंच जाता है।

जब service न चल रही हो, तो docker compose run --rm का उपयोग करें

exec के लिए एक चल रहे container की आवश्यकता होती है। यदि आप इसे एक बंद service पर चलाएंगे, तो यह मना कर देगा:

service "web" is not running

यह आपके लिए कुछ भी start नहीं करेगा। docker compose run ऐसा करेगा:

docker compose run --rm web bash

run, web service definition से एक नया container बनाता है, जिसमें वही image, environment, volumes और networks होते हैं, और यह service के command को आपके द्वारा टाइप किए गए command से बदल देता है। --rm बाहर निकलने पर उस container को हटा देता है। यदि आप --rm को हटा देते हैं, तो बचे हुए container myproject-web-run-4f1c2b जैसे नामों के तहत जमा हो जाते हैं, जिन्हें docker compose ps -a आपको दिखाएगा और जिन्हें कोई अन्य प्रक्रिया साफ नहीं करती है।

run के दो व्यवहार लोगों को आश्चर्यचकित करते हैं। यह service के ports को तब तक publish नहीं करता जब तक आप --service-ports न जोड़ें, और यह जानबूझकर किया गया है: यदि दूसरा container host port 8080 को bind करने की कोशिश करेगा जबकि पहला container पहले से ही उसे पकड़े हुए है, तो यह bind: address already in use के साथ विफल हो जाएगा। यह आपके shell के आने से पहले service द्वारा depends_on के तहत सूचीबद्ध हर चीज को भी start कर देता है, इसलिए अंदर एक त्वरित नज़र डालने से database और cache start हो सकते हैं। --no-deps इसे छोड़ देता है।

run, image के ENTRYPOINT के माध्यम से जाता है, जबकि exec ऐसा नहीं करता है। exec आपके command को सीधे मौजूदा container में start करता है, इसलिए entrypoint script इसे कभी नहीं देख पाती है। run के तहत, आपका bash उस script के arguments के रूप में पहुँचता है। कई official images अपने entrypoint को exec "$@" के साथ समाप्त करती हैं, इसलिए यह सीधे आगे बढ़ जाता है और आपको shell मिल जाता है। एक script जो अपने स्वयं के arguments की व्याख्या करती है, वह उनके साथ कुछ और करेगी, और फिर आप उस एक run के लिए entrypoint को बदल देते हैं:

docker compose run --rm --entrypoint sh web

यह सबसे आम कारण है कि जो command exec के तहत काम करता है, वह run के तहत अलग व्यवहार करता है, और command और entrypoint के बीच का विभाजन यह बताता है कि आप हर बार image configuration के किस हिस्से को बदल रहे हैं।

exec या run: चुनाव कैसे करें

  • exec के लिए एक चल रहे container की आवश्यकता होती है। run के लिए इसकी आवश्यकता नहीं होती, और run dependencies को start कर सकता है।
  • exec लाइव process list और फाइलों को वर्तमान स्थिति में देखता है, जिसमें वे बदलाव भी शामिल हैं जो application ने start होने के बाद किए हैं। run इमेज की एक clean copy प्राप्त करता है, इसलिए उसमें वे बदलाव नहीं होते।
  • exec entrypoint को छोड़ देता है। run उसे execute करता है।
  • run एक container पीछे छोड़ देता है जब तक कि आप --rm का उपयोग न करें।

यह देखने के लिए exec का उपयोग करें कि वास्तव में क्या हो रहा है। समान environment की एक अस्थायी copy के लिए, एक बार चलने वाले migration command के लिए, या जब वास्तविक service इतनी देर तक न चले कि उसमें exec किया जा सके, तब run --rm का उपयोग करें।

उपयोगी exec flags: user, working directory और replicas

अधिकांश images non-root user पर चलती हैं, इसलिए exec shell के भीतर diagnostic tool इंस्टॉल करने का प्रयास यहाँ विफल हो जाता है:

E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)

-u root आपको उसी container में root shell देता है:

docker compose exec -u root web sh

-w /srv/app केवल उस command के लिए working directory सेट करता है। -e KEY=value आपके session में एक environment variable जोड़ता है, न कि service में। जब कोई service एक से अधिक replica चलाती है, तो --index 2 यह तय करता है कि आप किस container में पहुँचेंगे। यदि आप mounted directory पर file ownership की जाँच कर रहे हैं, तो container images में PUID और PGID यह बताता है कि user names के बजाय numeric ids क्यों यह तय करती हैं कि वहाँ कौन लिख सकता है।

Database container के अंदर psql या mysql shell प्राप्त करना

Client पहले से ही database image के अंदर मौजूद है, इसलिए आपको host पर किसी client की आवश्यकता नहीं है और न ही port publish करने की जरूरत है:

docker compose exec db psql -U postgres -d app
docker compose exec db mariadb -u root -p

Postgres images में psql होता है, MySQL images में mysql होता है, और MariaDB images में mariadb होता है। Connection container के अंदर से बनाया जाता है, इसलिए यह तब भी काम करता है जब compose file में कोई database port publish न किया गया हो। यह अधिक सुरक्षित व्यवस्था है: यदि आपने कोई port publish नहीं किया है, तो internet से कोई भी उस तक नहीं पहुँच सकता।

एक गलती लोगों का काफी समय बर्बाद करती है। Docker के command देखने से पहले ही, आपका shell host पर variables को expand कर देता है, इसलिए जब वह variable केवल container के अंदर मौजूद होता है, तो -U "$POSTGRES_USER" एक खाली string भेजता है। Single quotes और container के अंदर का shell इसे सही जगह पर expand करते हैं:

docker compose exec db sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"'

यहाँ बिना किसी command के docker compose run --rm db का उपयोग न करें। यह उसी data volume पर एक दूसरा Postgres server start करने का प्रयास करता है, और वह start होने से मना कर देता है:

FATAL:  lock file "postmaster.pid" already exists

Lock file अपना काम कर रही है, क्योंकि एक ही data directory में लिखने वाले दो servers उसे corrupt कर देंगे। जब database चल रहा हो, तो running container में exec करें। क्या database को वास्तव में Compose में होना चाहिए, यह एक अलग निर्णय है, और database को Docker में या host पर चलाना इस विषय के फायदे और नुकसान को स्पष्ट करता है।

Startup पर console की आवश्यकता वाली services: stdin_open और tty

exec और run उन shells के लिए हैं जिन्हें आप स्वयं खोलते हैं। जिस service की मुख्य प्रक्रिया स्वभाव से interactive होती है, उसे compose file में दो keys की आवश्यकता होती है:

services:
  console:
    image: python:3.12-slim
    command: python
    stdin_open: true
    tty: true

stdin_open: true का मान docker run -i है और tty: true का मान docker run -t है। इनके बिना container start होता है और तुरंत code 0 के साथ exit हो जाता है, और docker compose ps -a में Exited (0) दिखाई देता है। कुछ भी crash नहीं हुआ है। stdin पर terminal न होने के कारण python तुरंत end of file पढ़ लेता है और सामान्य रूप से बंद हो जाता है, जो कि किसी ऐसे program के लिए सही व्यवहार है जिसे कोई type नहीं कर रहा है।

दोनों keys set होने पर, चल रही प्रक्रिया से जुड़ें:

docker attach $(docker compose ps -q console)

Ctrl-P और फिर Ctrl-Q के साथ detach करें, जिससे प्रक्रिया चलती रहती है। यह sequence तभी काम करता है जब container में TTY और stdin दोनों खुले हों। इसके विपरीत, Ctrl-C दबाने पर PID 1 को interrupt भेजा जाता है और service रुक जाती है।

सामान्य services के लिए दोनों keys को बंद रखें। एक web server कभी भी stdin नहीं पढ़ता है, और tty: true के कारण कई programs colour output और line buffering पर switch हो जाते हैं क्योंकि उन्हें लगता है कि कोई उन्हें देख रहा है, जिससे docker compose logs escape codes से भर जाता है।

cron और CI में scripted exec विफल क्यों होता है: -T flag

एक exec कमांड जो आपके टर्मिनल में काम करती है, वह cron जॉब या continuous integration (CI) रनर के अंदर विफल हो जाती है:

the input device is not a TTY

Compose डिफ़ॉल्ट रूप से एक pseudo terminal की मांग करता है, और cron जॉब को कोई टर्मिनल नहीं देता है, इसलिए आपकी कमांड चलने से पहले ही अनुरोध विफल हो जाता है। -T इस अनुरोध को बंद कर देता है:

0 3 * * * docker compose -f /srv/app/compose.yaml exec -T db pg_dump -U postgres -Fc app > /srv/backups/app.dump

-T दूसरे कारण से भी महत्वपूर्ण है। एक TTY बाहर जाने वाले बाइट स्ट्रीम को फिर से लिखता है (rewrite), इसलिए इसके माध्यम से गुजरने वाला कंप्रेस्ड डंप क्षतिग्रस्त हो जाता है। किसी भी रीडायरेक्ट या पाइप किए गए आउटपुट के लिए -T की आवश्यकता होती है।

cron के दो और विवरण। -f को एक absolute path के साथ पास करें, क्योंकि cron जॉब को home directory से चलाता है जहाँ कोई compose file नहीं होती है, और फिर Compose no configuration file provided: not found के साथ रुक जाता है। और exec उस कमांड का exit code लौटाता है जिसे उसने चलाया था, इसलिए एक विफल pg_dump आपकी स्क्रिप्ट को set -e के तहत विफल कर देता है, बजाय इसके कि वह एक खाली बैकअप लिखे और सफलता की रिपोर्ट करे। बाकी दैनिक कमांड्स Compose कमांड चीट शीट में संकलित हैं, जिसे उन स्क्रिप्ट्स के पास रखना उपयोगी है।

कंटेनर के अंदर किए गए बदलाव क्यों गायब हो जाते हैं

आप exec का उपयोग करके कोई टूल इंस्टॉल करते हैं, कॉन्फ़िगरेशन फ़ाइल को एडिट करते हैं, समस्या ठीक करते हैं, और एक सप्ताह बाद वह सुधार गायब हो जाता है। यह कंटेनर की writable layer का डिज़ाइन के अनुसार व्यवहार है। इमेज टैग या सर्विस डेफ़िनिशन में किसी भी बदलाव के बाद docker compose up -d पुराने कंटेनर को नष्ट कर देता है और इमेज से एक नया कंटेनर बनाता है, और हर मैन्युअल एडिट पुराने कंटेनर के साथ ही चला जाता है।

docker compose restart अलग है। यह उसी कंटेनर को रोकता और शुरू करता है, इसलिए मैन्युअल एडिट इसमें बने रहते हैं। यही कारण है कि एक मैन्युअल सुधार हफ़्तों तक बना रह सकता है और फिर किसी असंबंधित अपडेट के दौरान गायब हो सकता है। Named volumes और bind mounts दोनों ऑपरेशन्स में सुरक्षित रहते हैं, क्योंकि उनका डेटा कंटेनर के बाहर रहता है, और bind mounts और named volumes यह बताता है कि जिस डेटा को आप सुरक्षित रखना चाहते हैं, उसके लिए किसे चुनना चाहिए।

इसलिए exec शेल को केवल पढ़ने और टेस्ट करने की जगह मानें। एक बार जब आप सुधार जान लें, तो उसे वहां लिखें जहां वह सुरक्षित रहे: पैकेज को Dockerfile में, और सेटिंग को compose फ़ाइल में। फिर इसे लागू करने के लिए docker compose up -d चलाएं, और दूसरे exec के साथ पुष्टि करें कि नए कंटेनर में वास्तव में वह बदलाव मौजूद है।

FAQ

docker compose exec और docker compose run में क्या अंतर है?

exec पहले से चल रहे container के भीतर मुख्य process के साथ एक command चलाता है और image के entrypoint को छोड़ देता है। run उसी service definition से एक नया container बनाता है, जिसमें वही image, environment, volumes और networks होते हैं; यह आपके command को entrypoint के माध्यम से भेजता है और पहले सभी depends_on services को start करता है। run service के ports को तब तक publish नहीं करता जब तक आप --service-ports न जोड़ें। live service का निरीक्षण करने के लिए exec का उपयोग करें। जब service बंद हो या आप उसे बाधित नहीं करना चाहते हों, तब run --rm का उपयोग करें।

docker compose exec यह क्यों बताता है कि service नहीं चल रही है?

exec एक मौजूदा container से जुड़ता है और नया container नहीं बना सकता, इसलिए बंद या क्रैश हुई service service "web" is not running error देती है। docker compose ps -a की जाँच करें, जो exited containers को Exited (1) जैसी status के साथ सूचीबद्ध करता है, और रुकने का कारण जानने के लिए docker compose logs web पढ़ें। फिर भी shell प्राप्त करने के लिए, docker compose run --rm --entrypoint sh web चलाएँ। यह broken start command को चलाए बिना उसी service definition से एक नया container बनाता है।

यदि image में bash न हो तो मैं shell कैसे खोलूँ?

docker compose exec web bash का exec: "bash": executable file not found in $PATH के साथ विफल होने का अर्थ है कि image में bash मौजूद नहीं है, जो Alpine पर आधारित किसी भी image के लिए सामान्य है। docker compose exec web sh का उपयोग करें, क्योंकि BusyBox में /bin/sh उपलब्ध होता है। Distroless और scratch images में कोई shell नहीं होता, इसलिए कोई भी exec command काम नहीं करेगी। यदि publisher प्रदान करता है, तो image के :debug tag पर स्विच करें, या docker run --rm -it --network "container:$CID" --pid "container:$CID" nicolaka/netshoot के साथ target के namespaces में एक debug container शुरू करें, जहाँ $CID का मान docker compose ps -q web से प्राप्त होता है।

cron में मेरा exec command "the input device is not a TTY" के साथ विफल क्यों होता है?

docker compose exec डिफ़ॉल्ट रूप से एक pseudo terminal का अनुरोध करता है और cron कोई terminal प्रदान नहीं करता, इसलिए command चलने से पहले ही अनुरोध विफल हो जाता है। इसे बंद करने के लिए -T जोड़ें: docker compose exec -T db pg_dump -U postgres app। किसी भी redirected या piped output के लिए भी -T का उपयोग करें, क्योंकि TTY byte stream को बदल देता है और binary dump को खराब कर सकता है। cron में, अपने compose file के absolute path के साथ -f भी पास करें, अन्यथा Compose no configuration file provided: not found के साथ बंद हो जाएगा।

क्या exec के साथ container के अंदर किए गए बदलाव restart के बाद सुरक्षित रहते हैं?

वे docker compose restart के बाद सुरक्षित रहते हैं, जो उसी container का पुन: उपयोग करता है। किसी भी image या configuration बदलाव के बाद docker compose up -d पर वे खो जाते हैं, क्योंकि यह image से container को फिर से बनाता है और उसकी writable layer को हटा देता है। named volumes या bind mounts में लिखा गया डेटा दोनों स्थितियों में सुरक्षित रहता है, क्योंकि वह container के बाहर स्थित होता है। exec के साथ diagnostic बदलाव करें, फिर स्थायी संस्करण को Dockerfile या compose file में डालें।