Docker Compose में build बनाम image का सही उपयोग
Docker Compose में build और image के बीच का अंतर समझें। यदि आपका VPS Dockerfile बदलावों को अनदेखा कर रहा है, तो यह गाइड आपको सही कॉन्फ़िगरेशन और समाधान प्रदान करती है।
Docker Compose build बनाम image: संक्षिप्त उत्तर
Docker Compose फ़ाइल में, image: किसी registry से pull करने के लिए एक image का नाम निर्दिष्ट करता है, और build: Compose को इस मशीन पर Dockerfile से एक image build करने का निर्देश देता है। केवल image: सेट करें और Compose उस tag को pull करके उसे run कर देगा। केवल build: सेट करें और Compose यहाँ image build करेगा, जिसे project के नाम और service के नाम से व्युत्पन्न एक नाम दिया जाएगा। दोनों को सेट करें और Compose स्थानीय रूप से build करेगा, फिर परिणाम को image: में दिए गए नाम के साथ tag करेगा; इसी तरह आप एक image build करते हैं और उसे अपने चुने हुए नाम के तहत push करते हैं।
यही मुख्य अंतर है। नीचे दी गई जानकारी सर्वर पर इसके संचालन के अर्थ को स्पष्ट करती है। यह माना गया है कि Docker Engine और Compose plugin पहले से स्थापित हैं; VPS पर Docker चलाना उस भाग को कवर करता है।
तीनों रूप पूर्ण रूप में
एक प्रकाशित tag को pull करें और उसे run करें। इसमें किसी भी चरण पर Dockerfile शामिल नहीं है।
services:
web:
image: nginx:1.27
restart: unless-stopped
ports:
- "80:80"वर्तमान निर्देशिका में मौजूद Dockerfile से build करें। FROM में नामित base image के अलावा कुछ भी pull नहीं किया जाता है।
services:
web:
build: .
restart: unless-stopped
ports:
- "80:80"स्थानीय रूप से build करें और परिणाम को tag करें। docker compose push फिर उस सटीक tag को registry में भेज सकता है।
services:
web:
build:
context: .
dockerfile: Dockerfile
image: registry.example.com/acme/web:1.4.2
restart: unless-stopped
ports:
- "80:80"context वह निर्देशिका है जिसे builder को भेजा जाता है। dockerfile को उस context के सापेक्ष resolve किया जाता है, इसलिए dockerfile: docker/prod.Dockerfile के साथ context: . का उपयोग सामान्य और सही है। प्रत्येक service container के पीछे image का नाम और image ID देखने के लिए docker compose images चलाएं, जो यह पुष्टि करने का सबसे तेज़ तरीका है कि आपने वास्तव में इन तीनों रूपों में से कौन सा लिखा है।
Dockerfile में बदलाव करने के बाद docker compose up फिर से बिल्ड क्यों नहीं करता है?
क्योंकि up यह जाँचता है कि इमेज मौजूद है या नहीं, यह नहीं कि वह नवीनतम है या नहीं।
जब Compose किसी ऐसी सर्विस को शुरू करता है जिसमें build: सेक्शन होता है, तो वह स्थानीय इमेज स्टोर में इमेज को खोजता है। यदि उस नाम की इमेज पहले से वहां मौजूद है, तो Compose उसी का उपयोग करता है। यह आपके Dockerfile को नहीं पढ़ता, आपकी सोर्स फाइलों की तुलना नहीं करता, और न ही किसी टाइमस्टैम्प को देखता है। Compose स्पेसिफिकेशन इस नियम को pull_policy एट्रिब्यूट के रूप में परिभाषित करता है, और डिफ़ॉल्ट व्यवहार केवल तभी इमेज बिल्ड करता है जब वह गायब हो। इमेज का मौजूद होना ही पर्याप्त माना जाता है।
इसलिए आप app.py को एडिट करते हैं, docker compose up -d चलाते हैं, और Compose को कंटेनर को running के रूप में रिपोर्ट करते हुए देखते हैं, जबकि वह पुराना कोड ही सर्व कर रहा होता है। कुछ भी फेल नहीं हुआ, इसलिए किसी ने आपको चेतावनी नहीं दी। Compose के साथ "मेरे बदलाव प्रभावी नहीं हुए" की यह सबसे आम रिपोर्ट है। इसकी पहचान वह स्टेटस शब्द है जिसे Compose कंटेनर नाम के बगल में प्रिंट करता है: जिस कंटेनर को Compose ने बदला है, वह recreated या started रिपोर्ट करता है, और जिस कंटेनर को Compose ने नहीं छेड़ा है, वह running रिपोर्ट करता है।
दो जाँचें इसे स्पष्ट कर देती हैं। docker compose images प्रत्येक कंटेनर द्वारा उपयोग की जा रही इमेज ID को प्रिंट करता है, इसलिए डिप्लॉय से पहले इसे नोट करें और बाद में तुलना करें। docker image ls में एक CREATED कॉलम होता है, और आपके अंतिम कमिट से पहले बनाई गई इमेज एक पुरानी (stale) इमेज है, चाहे डिप्लॉय स्क्रिप्ट ने कुछ भी प्रिंट किया हो।
कौन से flags rebuild को बाध्य करते हैं
docker compose up -d --buildपहले build करता है, फिर उन सभी containers को recreate करता है जिनकी image बदल गई है। अधिकांश लोग इसी flag की तलाश में रहते हैं।docker compose build webएक service को build करता है और कुछ भी start नहीं करता। इसके बादdocker compose up --no-deps -d webका उपयोग करें ताकि केवल उस container को replace किया जा सके और बाकी stack चलता रहे।docker compose build --no-cache webहर cached layer को हटा देता है और पहली instruction से rebuild करता है।docker compose build --pullFROMमें base image का नया version pull करने का प्रयास करता है, ताकिnode:22जैसा moving tag मार्च में download की गई copy के बजाय उसकी वर्तमान सामग्री को प्राप्त कर सके।docker compose up -d --force-recreatecontainers को उसी image से recreate करता है जिसका वे पहले से उपयोग कर रहे हैं। यह कभी भी build नहीं करता। जब आप वास्तव में--buildका उपयोग करना चाहते हों, तब इसका उपयोग करना एक आम गलती है।
आप इस निर्णय को file के भीतर भी ले जा सकते हैं। Compose specification के शब्दों में, pull_policy: build का अर्थ है कि Compose image को build करता है, और यदि वह पहले से मौजूद है तो उसे rebuild करता है। तब हर up के लिए build की कीमत चुकानी पड़ती है, जो laptop पर तो ठीक है लेकिन server पर शायद ही कभी वांछित होता है।
services:
web:
build: .
image: registry.example.com/acme/web:dev
pull_policy: buildएक और interaction के बारे में जानना उपयोगी है। docker compose pull उन services के लिए भी images pull करने का प्रयास करता है जिनमें build section होता है, और यदि वह pull विफल हो जाता है तो यह आपको बताता है कि इसके बजाय image को build किया जाना चाहिए। उन services को चुपचाप छोड़ने के लिए --ignore-buildable पास करें।
बिल्ड कैश यह कैसे तय करता है कि आपका डिप्लॉयमेंट समय कितना होगा
Dockerfile का प्रत्येक निर्देश एक लेयर बनाता है, और जब वह निर्देश और उसके इनपुट अपरिवर्तित रहते हैं, तो बिल्डर एक कैश की गई लेयर का पुन: उपयोग करता है। COPY के लिए, इनपुट कॉपी की जाने वाली फाइलों की सामग्री होती है। एक बार जब कोई लेयर कैश मिस कर देती है, तो उसके बाद की हर लेयर को फिर से बनाया जाता है, क्योंकि प्रत्येक लेयर उस फाइलसिस्टम पर आधारित होती है जिसे पिछली लेयर ने तैयार किया था।
यह एक नियम तय करता है कि आपका डिप्लॉयमेंट सेकंड में होगा या मिनटों में। Dockerfile को उस क्रम में व्यवस्थित करें जो शायद ही कभी बदलता है, और अंत में वह रखें जो हर कमिट पर बदलता है।
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]npm ci, COPY . . के ऊपर स्थित होता है, इसलिए सोर्स फाइल को एडिट करने पर इंस्टॉल लेयर कैश में बनी रहती है और बिल्ड कॉपी स्टेप से फिर से शुरू होता है। उन दो लाइनों को आपस में बदल दें, तो एक अक्षर का बदलाव भी हर डिपेंडेंसी को फिर से इंस्टॉल कर देगा, क्योंकि COPY . . उस लेयर को अमान्य कर देता है जिस पर npm ci आधारित है। यही संरचना pip install -r requirements.txt और go mod download पर भी लागू होती है।
जब आपको संदेह हो कि कोई पुरानी लेयर आपके फिक्स को छिपा रही है, तो --no-cache सही टूल है। यह एक खराब डिफ़ॉल्ट है, क्योंकि यह उस पुन: उपयोग को समाप्त कर देता है जिसे Dockerfile के क्रमबद्ध होने से प्राप्त किया जाता है।
एक चीज जिसे इमेज सेट करती है और Compose ओवरराइड कर सकता है: Dockerfile का CMD वह है जिसे इमेज डिफ़ॉल्ट रूप से चलाती है, और सर्विस में एक command: की (key) उसे बदल देती है। कमांड और एंट्रीपॉइंट कैसे इंटरैक्ट करते हैं यहाँ महत्वपूर्ण है, क्योंकि एक Compose ओवरराइड एक नई बनी इमेज को बिल्कुल पुरानी इमेज की तरह व्यवहार करने के लिए मजबूर कर सकता है।
Build context और .dockerignore
context: . का अर्थ है कि Compose उस डायरेक्टरी को पैक करता है और पहली instruction चलने से पहले उसे builder के पास भेज देता है। इसके अंतर्गत सब कुछ चला जाता है, जिसमें .git और आपके source के पास मौजूद कोई भी डेटा डायरेक्टरी शामिल है। यदि कोई build, जो अन्यथा अपरिवर्तित है, transferring-context चरण पर रुक जाती है, तो इसका मतलब है कि context बहुत बड़ा है।
context के रूट पर मौजूद एक .dockerignore फाइल उस ट्रांसफर से पाथ्स (paths) को बाहर रखती है। इसका सिंटैक्स .gitignore के समान है।
.git
node_modules
*.log
data/
.envइसके दो फायदे हैं। ट्रांसफर छोटा हो जाता है, इसलिए हर build तेजी से शुरू होती है। और COPY . . अब .env को इमेज में कॉपी नहीं कर सकता, जहाँ से इमेज को pull करने वाला कोई भी व्यक्ति उसे पढ़ सकता है।
समय के साथ धीमी होने वाली build का एक कारण bind mount है। एक named volume आपके प्रोजेक्ट डायरेक्टरी के बाहर रहता है, लेकिन ./data:/var/lib/postgresql/data जैसा bind mount build context के अंदर स्थित होता है, इसलिए जैसे-जैसे डेटाबेस बढ़ता है, आपकी builds हर हफ्ते धीमी होती जाती हैं। .dockerignore में एक लाइन इसे ठीक कर देती है। Named volumes बनाम bind mounts इस विषय के व्यापक पहलुओं को कवर करता है।
Build arguments में भी इसी जोखिम का छोटा रूप होता है। args: के माध्यम से पास की गई वैल्यूज इमेज हिस्ट्री में किसी भी व्यक्ति को दिखाई देती हैं जिसके पास इमेज है, इसलिए वहाँ केवल वर्ज़न नंबर रखें, कभी भी टोकन न रखें। Compose में Env फाइल्स और secrets में बताया गया है कि क्रेडेंशियल्स को कहाँ रखना चाहिए।
क्या आपको VPS पर build करना चाहिए या कहीं और build करके pull करना चाहिए?
जिस सर्वर पर आपका traffic आता है, उसी पर build करना डिफ़ॉल्ट तरीका है क्योंकि यह सबसे छोटा रास्ता है: git pull, और फिर docker compose up -d --build। यह एक छोटे सर्वर पर ठीक है जिस पर अभी कोई निर्भर नहीं है। यह दो ऐसे कारणों से समस्या बन जाता है जिन्हें आप माप सकते हैं, और एक ऐसे कारण से जो केवल खराब स्थिति में सामने आता है।
Memory. एक build प्रक्रिया आपकी live application के साथ-साथ compilers और bundlers चलाती है, और अधिकांश stacks में ये सबसे अधिक memory लेने वाले हिस्से होते हैं। 1 GB के VPS पर, एक JavaScript bundler या Rust compile अक्सर सर्वर की सबसे बड़ी प्रक्रिया होती है। जब kernel की memory खत्म हो जाती है, तो वह सबसे बड़ी प्रक्रिया को kill कर देता है: या तो build Killed और exit status 137 के साथ रुक जाता है, या फिर आपकी database प्रक्रिया kill हो जाती है और deploy के बीच में ही साइट down हो जाती है। dmesg -T | grep -i oom kill होने वाली प्रक्रिया के नाम के साथ line print करता है, जिससे आप अनुमान लगाने के बजाय यह जान सकते हैं कि दोनों में से क्या हुआ है।
Disk. हर build पीछे layers छोड़ देता है, और builder अपनी cache को आपकी images से अलग रखता है। docker system df दोनों को दिखाता है, और build cache की row लगातार बढ़ती रहती है। dangling images के लिए docker image prune और cached layers के लिए docker builder prune का उपयोग करके जगह खाली करें। disk भर जाने पर केवल build ही नहीं रुकता। database भी लिखना बंद कर देता है, और वह विफलता एक धीमे deploy की तुलना में कहीं अधिक महंगी पड़ती है।
Reproducibility. सर्वर पर बनाई गई image केवल उसी सर्वर पर मौजूद होती है। rollback करने का मतलब है पुराने commit को checkout करना और फिर से build करना, और इस बात की कोई गारंटी नहीं है कि वह build वही परिणाम देगा जो पहले था, क्योंकि base tag बदल चुका होता है और package mirrors भी बदल जाते हैं। कहीं और build करना और एक tag push करना rollback को एक साधारण बदलाव बना देता है: image: को पिछले tag पर point करें और docker compose up -d चलाएं।
जो व्यवस्था सबसे बेहतर काम करती है वह स्पष्ट है। आपकी continuous integration प्रक्रिया build चलाती है और registry.example.com/acme/web:<git-sha> को push करती है, और VPS पर मौजूद Compose file में image: होता है, जिसमें build: key बिल्कुल नहीं होती। इसके बाद deployment केवल दो commands का काम रह जाता है जिनमें लगभग कोई memory खर्च नहीं होती।
docker compose pull
docker compose up -dसर्वर पर एक बार docker login registry.example.com चलाएं और Compose उसके बाद से private tags को pull कर सकेगा।
build section को delete करने के बजाय development के लिए रखें, इसे किसी ऐसी file में रखें जिसका नाम आप खुद तय करें।
# compose.dev.yaml
services:
web:
build:
context: .
pull_policy: builddocker compose -f compose.yaml -f compose.dev.yaml up -d --buildउस file का नाम compose.dev.yaml रखें, न कि compose.override.yaml। Compose किसी भी override file को मौजूद होने पर अपने आप load कर लेता है, इसलिए यदि कोई override file गलती से सर्वर पर copy हो गई, तो वह चुपचाप वहां फिर से build करना शुरू कर देगी। कई Compose files को layer करना बताता है कि merge प्रक्रिया प्रत्येक key को कैसे हल करती है।
अन्यत्र निर्माण करने पर आर्किटेक्चर संबंधी समस्या
एक image उसी CPU architecture को धारण करती है जिसके लिए उसे build किया गया है। यदि आप Apple Silicon लैपटॉप पर build करके push करते हैं, और फिर उस tag को x86_64 VPS पर pull करते हैं, तो Docker चेतावनी देता है कि अनुरोधित image platform होस्ट के detected platform से मेल नहीं खाता है। इसके बाद प्रक्रिया exec format error के साथ समाप्त हो जाती है, जो देखने में corrupt binary जैसा लगता है, लेकिन वास्तव में ऐसा नहीं है। target के लिए स्पष्ट रूप से build करें:
docker buildx build --platform linux/amd64 \
-t registry.example.com/acme/web:1.4.2 --push .यदि आपका लैपटॉप x86 है और आप x86 के बजाय ARM VPS चलाते हैं, तो यही बेमेल स्थिति विपरीत दिशा में भी होती है। CI को उस architecture पर build करने देने से जहाँ आप deploy करते हैं, यह समस्या समाप्त हो जाती है।
डिप्लॉयमेंट के बाद क्या जाँचें
docker compose imagesहर चल रहे container के पीछे के image और tag को प्रिंट करता है। बदला हुआ image ID इस बात का प्रमाण है कि नया build service में है।docker compose configvariable substitution के बाद मर्ज की गई फ़ाइल को प्रिंट करता है, ताकि आप किसी भी चीज़ को चलाने से पहले उस अंतिम image नाम को पढ़ सकें जिसका उपयोग Compose करेगा।docker compose logs -f webस्वैप के बाद पहले आधे मिनट के लिए। जो container शुरू होकर बंद हो जाता है, वह चालू रहने के बजाय लूप में restart होता रहता है, और जब तक आप न देखें, यह लूप शांत रहता है।docker image lsएक CREATED कॉलम दिखाता है। आपके अंतिम commit से पुरानी image को कभी भी rebuild नहीं किया गया था।
यदि आप अभी भी उस फ़ाइल को असेंबल कर रहे हैं जिसके विरुद्ध ये जाँचें चलती हैं, तो VPS पर Compose फ़ाइल की मूल बातें आसपास की keys को कवर करती हैं, और Compose कमांड चीट शीट बाकी सब-कमांड्स की सूची देती है।
FAQ
क्या मैं एक ही service में build और image का उपयोग कर सकता हूँ?
हाँ, और जिस प्रोजेक्ट को आप स्वयं build करते हैं, उसके लिए यह सामान्य सेटअप है। Compose build: सेक्शन से build करता है और परिणाम को image: के मान के साथ tag करता है। वह tag ही है जिसे docker compose push एक registry पर भेजता है और जिसे दूसरी मशीन pull करती है। image: key के बिना भी Compose build तो करता है, लेकिन यह image का नाम प्रोजेक्ट और service के आधार पर रखता है, और यह चेतावनी देता है कि attribute न होने के कारण image को push नहीं किया जा सकता।
docker compose up मेरे Dockerfile में किए गए बदलावों को क्यों नहीं उठाता?
क्योंकि up केवल यह जाँचता है कि उस नाम की image मौजूद है या नहीं। जब एक image मौजूद होती है, तो Compose उसे start कर देता है और कभी भी उसकी तुलना आपके Dockerfile या source files से नहीं करता। docker compose up -d --build चलाएँ, या किसी एक service को बदलने के लिए docker compose build web के बाद docker compose up --no-deps -d web चलाएँ। service पर pull_policy: build सेट करने से हर up फिर से build होता है, जो development मशीन के लिए उपयुक्त है।
--build और --force-recreate में क्या अंतर है?
--build image को फिर से build करता है, और फिर उन containers को recreate करता है जिनकी image बदल गई है। --force-recreate containers को उसी image से recreate करता है जो उनके पास पहले से है, इसलिए यह कभी भी code में किए गए बदलावों को नहीं उठा सकता। यदि आपका बदलाव source या Dockerfile में है, तो --build वह flag है जिसकी आपको आवश्यकता है। --force-recreate का उपयोग container को ही reset करने के लिए किया जाता है, उदाहरण के लिए, उसी image को रखते हुए उसकी writable layer को साफ़ करने के लिए।
क्या मुझे अपनी Docker images को VPS पर build करना चाहिए या कहीं और?
कहीं और build करें और एक बार जब सर्वर traffic serve करने लगे, तो tag को pull करें। एक build आपकी application के साथ memory के लिए प्रतिस्पर्धा करता है, और एक छोटा VPS उस प्रतिस्पर्धा को हल करने के लिए kernel को सबसे बड़ी process को kill करने देता है, जो build हो सकती है या आपकी database। builds डिस्क पर cache भी छोड़ते हैं जिसे कोई और आपके लिए साफ़ नहीं करता। बिना users वाले छोटे प्रोजेक्ट के लिए सर्वर पर build करना ठीक है, और यदि आप build: सेक्शन को केवल development वाले Compose file में रखते हैं, तो बाद में इसे स्थानांतरित करने में बहुत कम मेहनत लगती है।
मैं Docker build cache को अपनी डिस्क भरने से कैसे रोकूँ?
यह देखने के लिए docker system df चलाएँ कि आपकी images और build cache कितनी जगह ले रहे हैं। docker builder prune cached layers को हटा देता है और docker image prune पिछली builds द्वारा छोड़ी गई dangling images को हटा देता है। किसी भी एक में -a जोड़ने से प्रक्रिया अधिक आक्रामक हो जाती है और आपकी अगली build को शून्य से शुरू करने के लिए मजबूर करती है। सर्वर पर docker system prune -af --volumes को schedule न करें, क्योंकि --volumes किसी भी ऐसे volume को हटा देता है जिसका उपयोग वर्तमान में कोई container नहीं कर रहा है, और maintenance के लिए रोकी गई stack का database ठीक उसी तरह के volume में होता है।