Docker Compose में .env, env_file और environment का अंतर
Docker Compose में .env, env_file और environment के अंतर, precedence order और secrets रखने की सही जगह समझें। कौन-सा विकल्प जीतता है, command से जांचें।
तीन चीजें जिन्हें लोग env file कहते हैं
Docker Compose में तीन अलग-अलग तंत्र हैं जिनके नाम भ्रमित करने वाले हैं। .env फाइल compose.yaml के भीतर मौजूद ${VARIABLE} प्लेसहोल्डर्स को भरती है। यह प्रक्रिया तब होती है जब Compose YAML को पढ़ लेता है, लेकिन उसे वैलिडेट करने या चलाने से पहले। env_file: एट्रिब्यूट key/value पेयर्स की एक फाइल को कंटेनर के एनवायरनमेंट में लोड करता है। environment: एट्रिब्यूट सीधे compose फाइल में लिखे गए वेरिएबल्स को कंटेनर पर सेट करता है। ये आपस में बदले नहीं जा सकते। जब इनमें से दो एक ही key को सेट करते हैं, तो कौन सा मान प्रभावी होगा, यह एक निर्धारित प्राथमिकता क्रम (precedence order) द्वारा तय होता है।
यह गाइड प्रत्येक के कार्य करने के तरीके को दिखाती है, एक ऐसी कमांड के साथ प्राथमिकता को सिद्ध करती है जिसे आप चला सकते हैं, और फिर उस हिस्से को कवर करती है जो अधिक महत्वपूर्ण है: एनवायरनमेंट वेरिएबल्स को कोई भी पढ़ सकता है जो docker inspect चला सकता है, इसलिए पासवर्ड इनमें नहीं होने चाहिए। यदि आप सामान्य रूप से compose फाइलों के लिए नए हैं, तो VPS पर Docker Compose की बुनियादी जानकारी से शुरुआत करें और कॉन्फ़िगरेशन के लिए यहाँ वापस आएं।
.env फ़ाइल compose फ़ाइल के लिए है, container के लिए नहीं
एक directory बनाएँ और उसमें दो फ़ाइलें रखें।
mkdir -p ~/envdemo && cd ~/envdemo
printf 'ALPINE_TAG=3.20\n' > .envservices:
demo:
image: alpine:${ALPINE_TAG}
command: printenv ALPINE_TAGअब Compose से पूछें कि उसने वास्तव में क्या parse किया है।
docker compose configआउटपुट image: alpine:3.20 दिखाता है। placeholder हट गया है, क्योंकि फ़ाइल load करते समय Compose ने interpolation किया था। Compose project directory में .env को ढूँढता है: यदि आपने --project-directory सेट किया है तो वह directory, अन्यथा -f के साथ pass की गई पहली फ़ाइल की directory, और यदि वह भी न हो तो आपकी वर्तमान directory। यह YAML values में मिलने वाले ${NAME} placeholders को प्रतिस्थापित (substitute) करता है। Keys कभी भी interpolate नहीं होती हैं, और दोहरे $$ का उपयोग करने पर substitution के बजाय एक literal dollar sign प्राप्त होता है।
फिर service चलाएँ।
docker compose run --rm demoprintenv ALPINE_TAG status 1 के साथ exit हो जाता है और कुछ भी print नहीं करता है। variable container के अंदर मौजूद नहीं है। यह सबसे आम गलतफहमी है: .env ने compose फ़ाइल को configure किया, न कि process को। यदि किसी .env फ़ाइल में POSTGRES_PASSWORD=hunter2 है, तो वह आपके database के लिए कुछ नहीं करेगी जब तक कि compose फ़ाइल का कोई हिस्सा उसे reference न करे।
${NAME:-default} तब fallback प्रदान करता है जब variable unset या empty हो। ${NAME:?message} Compose को start होने से रोकता है और आपका message print करता है, जो ऐसी value के लिए सही विकल्प है जिसका कोई सुरक्षित default नहीं है।
env_file कंटेनर में वेरिएबल्स लोड करता है
env_file: एट्रिब्यूट एक या अधिक फाइलों के नाम बताता है जिनकी सामग्री कंटेनर के एनवायरनमेंट वेरिएबल्स बन जाती है।
printf 'GREETING=from_env_file\nAPP_MODE=production\n' > app.envservices:
demo:
image: alpine:3.20
command: printenv GREETING
env_file:
- ./app.envdocker compose run --rm demoयह from_env_file प्रिंट करता है। फाइल का फॉर्मेट सादा KEY=value लाइनें है, प्रति लाइन एक, जिसमें # से कमेंट शुरू होता है। यह शेल नहीं है, हालांकि Compose इसे कुछ शेल-जैसे नियमों के साथ पार्स करता है। आसपास के कोट्स हटा दिए जाते हैं, इसलिए KEY="value" और KEY=value एक ही परिणाम देते हैं। डबल-कोट्स और बिना कोट्स वाले वैल्यूज में ${...} प्लेसहोल्डर्स इंटरपोलेट होते हैं, जबकि सिंगल-कोट्स वाले वैल्यूज को अक्षरशः उपयोग किया जाता है। एक export प्रीफिक्स स्वीकार्य है लेकिन इसकी आवश्यकता नहीं है। = चिह्न के आसपास के स्पेस हटा दिए जाते हैं। ध्यान रखने वाली बात इनलाइन कमेंट है: बिना कोट्स वाले वैल्यू में, स्पेस के बाद # आने पर कमेंट शुरू हो जाता है, इसलिए किसी भी ऐसे वैल्यू को कोट करें जिसमें # शामिल हो।
एक गायब env_file पाथ एक एरर है और Compose रुक जाता है। यदि फाइल का अनुपस्थित होना वैध हो सकता है, तो इसे वैकल्पिक के रूप में चिह्नित करें:
env_file:
- path: ./app.env
required: falseenvironment इनलाइन variables सेट करता है
services:
demo:
image: alpine:3.20
command: printenv GREETING
environment:
GREETING: from_environmentदो syntaxes स्वीकार किए जाते हैं: ऊपर दिया गया mapping form और - GREETING=from_environment का उपयोग करने वाला list form। ये दोनों एक समान व्यवहार करते हैं। दोनों में एक अतिरिक्त ट्रिक है: बिना value वाली key उस shell से variable को पास कर देती है जहाँ से आपने docker compose चलाया था। List form में यह एक खाली - GREETING होता है, और mapping form में यह colon के बाद कुछ भी न होने वाला GREETING: होता है।
environment:
- GREETINGGREETING=from_my_shell docker compose run --rm demoयह from_my_shell प्रिंट करता है। इसे shell में GREETING सेट किए बिना चलाएँ, तो Compose कुछ भी सेट नहीं करेगा और कोई warning भी नहीं देगा। Silent pass-through failures के बारे में जानना महत्वपूर्ण है, क्योंकि जो service खाली password variable के साथ start होती है, वह अक्सर सफलतापूर्वक start हो जाती है और पूरी तरह असुरक्षित रहती है।
कौन-सा प्रभावी होता है
Docker प्राथमिकता क्रम को उच्चतम से निम्नतम तक दस्तावेज़ित करता है: कमांड लाइन पर docker compose run -e, फिर environment या env_file, जिनका मान आपके shell या env file से इंटरपोलेट होता है, फिर compose file में सामान्य environment, फिर env_file, और अंत में image में शामिल ENV directive।
दैनिक कार्य के लिए संक्षेप में: environment:, env_file: पर प्रभावी होता है, और कमांड लाइन पर -e दोनों पर प्रभावी होता है। इसे एक file में सिद्ध करें।
services:
demo:
image: alpine:3.20
command: printenv GREETING
env_file:
- ./app.env
environment:
GREETING: from_environmentdocker compose run --rm demo
docker compose run --rm -e GREETING=from_cli demo printenv GREETINGपहला from_environment प्रिंट करता है, इसलिए environment: ने app.env में मौजूद मान को अधिलेखित कर दिया। दूसरा from_cli प्रिंट करता है। compose file में कोई भी चीज़ कमांड लाइन के मान को अधिलेखित नहीं करती।
जब कोई container ऐसा व्यवहार करे जैसे आपका configuration लागू ही नहीं हुआ, तो अनुमान न लगाएँ। docker compose config पूरी तरह resolved file प्रिंट करता है, और docker compose config --environment वे interpolation variables प्रिंट करता है जिनसे Compose काम कर रहा है। “मेरी env file को अनदेखा किया जा रहा है” वाली अधिकांश समस्याओं में एक ही मान दो अलग स्तरों पर सेट होता है।
Environment variables leak क्यों होते हैं
यदि आप environment: में password set करते हैं, तो वह disk पर container की configuration में store हो जाता है, जिसे docker group का कोई भी user देख सकता है।
docker compose run -d --name leaky -e DB_PASSWORD=hunter2 demo sleep 300
docker inspect leaky --format '{{json .Config.Env}}'Output में "DB_PASSWORD=hunter2" plain text में दिखाई देता है। तीन अन्य paths भी इसी value को उजागर करते हैं। docker compose config इसे terminal पर print कर देता है, और इसी तरह यह support forum पर paste हो जाता है। Child processes डिफ़ॉल्ट रूप से variable को inherit करती हैं, जब तक कि parent जानबूझकर उन्हें एक अलग environment के साथ start न करे। जो process PID 1 के समान user के रूप में चलती है, या जिसके पास CAP_SYS_PTRACE है, वह इसे /proc/1/environ से पढ़ सकती है। ऐसे container में जहाँ सब कुछ root के रूप में चलता है, वहाँ हर process ऐसा कर सकती है। यदि कोई application unprivileged user के रूप में चल रही हो जबकि PID 1 root के रूप में चल रहा हो, तो उसे उस file पर permission error मिलेगा, हालाँकि वह अभी भी अपने स्वयं के environment में variable को रखती है। इसके अलावा, application crash handlers नियमित रूप से पूरे environment को log या error report में dump कर देते हैं।
docker group की membership host पर प्रभावी रूप से root के समान है, इसलिए यह ऐसी privilege boundary नहीं है जिस पर आप भरोसा कर सकें। VPS पर least privilege user accounts के बारे में guide यह बताती है कि किसी भी shared box पर इस group को restrict करना क्यों उचित है।
Compose secrets में मान को फ़ाइल में रखें
Compose फ़ाइल-आधारित secrets का समर्थन करता है। मान को environment में डालने के बजाय container के अंदर फ़ाइल के रूप में mount किया जाता है।
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./db_password.txtSecret को container के अंदर /run/secrets/db_password पर mount किया जाता है। Slash के बाद का नाम top-level secrets: block से लिया गया secret name है।
_FILE suffix Docker Official Images में इस्तेमाल होने वाली एक convention है। इसमें postgres, mysql और mariadb भी शामिल हैं। ये entrypoint scripts VARNAME_FILE की जाँच करती हैं, फ़ाइल पढ़ती हैं और उसकी सामग्री का उपयोग करती हैं। यह Docker की सुविधा नहीं है। इसलिए यह केवल उन्हीं images में काम करता है जो इसे लागू करती हैं। यह मानने से पहले कि SOMETHING_FILE को स्वीकार किया जाएगा, image का documentation जाँचें। जो applications इसका समर्थन नहीं करतीं, वे अक्सर startup के समय फ़ाइल को स्वयं पढ़ सकती हैं। दूसरा विकल्प है कि path पास करें और अपना entrypoint यह कार्य करे।
चल रहे container के अंदर से सत्यापित करें:
docker compose exec db cat /run/secrets/db_password
docker compose exec db printenv POSTGRES_PASSWORDपहला command password प्रदर्शित करता है। दूसरा कुछ भी प्रदर्शित नहीं करता, क्योंकि मान environment में कभी प्रवेश नहीं करता। यही इसका उद्देश्य है: इस container पर docker inspect केवल सुरक्षित path दिखाता है।
Host पर source file को सुरक्षित रखें, क्योंकि secret उतना ही निजी है जितनी उसके पीछे मौजूद फ़ाइल:
chmod 600 db_password.txtVPS पर व्यावहारिक मध्य मार्ग
कई self hosted images _FILE variables का समर्थन नहीं करतीं, इसलिए environment variables ही एकमात्र विकल्प हैं। एकल administrator VPS पर वास्तविक लक्ष्य यह है कि values आपके project directory की ऐसी file में न रहें जिसे सभी पढ़ सकें, और वे git से बाहर रहें।
sudo install -o root -g root -m 600 /dev/null /etc/myapp/app.env
sudo nano /etc/myapp/app.env env_file:
- /etc/myapp/app.envinstall -m 600 file बनाते समय ही उसका mode सेट करता है। इसलिए ऐसा कोई समय नहीं रहता जब उसे हर व्यक्ति पढ़ सके। इसका स्वामित्व root के पास होता है, इसलिए उस server का non root user इसे नहीं पढ़ सकता। हालांकि, जो भी docker चला सकता है, वह container से value पढ़ सकता है। *.env और .env को .gitignore में जोड़ें। इसके बजाय एक app.env.example commit करें, जिसमें key names हों और values खाली हों। Commit किया गया password बदला हुआ password होता है।
किसी value को rotate करने का अर्थ service को restart करना है। Environment variables को container process शुरू होने पर केवल एक बार पढ़ा जाता है। इसलिए file में बदलाव तब तक प्रभावी नहीं होता, जब तक आप docker compose up -d --force-recreate db नहीं चलाते। यही pattern VPS पर HTTPS के पीछे n8n guide में भी इस्तेमाल किया गया है, जहां encryption key compose file के बाहर रहती है।
प्रत्येक environment के लिए configuration अलग करना
Compose डिफ़ॉल्ट रूप से project directory से .env पढ़ता है। --env-file का उपयोग करके इसे किसी अन्य स्थान पर निर्देशित करें।
docker compose --env-file .env.staging configकई files क्रम से पढ़ी जाती हैं और बाद वाली files, पहले वाली files की settings को override करती हैं। गैर-गोपनीय defaults को committed file में रखें और secrets को ऐसी file में रखें जो कभी server से बाहर न जाए। यही बात env_file: पर भी लागू होती है। किसी duplicate key के लिए सूची में सबसे अंत में दी गई file की value प्रभावी होती है।
FAQ
कंटेनर के अंदर मेरी .env फाइल को अनदेखा क्यों किया जा रहा है?
इसे अनदेखा नहीं किया जा रहा है। .env फाइल compose फाइल में ${NAME} प्लेसहोल्डर्स को प्रतिस्थापित करती है, और यह Compose के अपने वेरिएबल्स जैसे COMPOSE_PROJECT_NAME या COMPOSE_FILE को भी सेट कर सकती है। यह कंटेनर के अंदर वेरिएबल्स को कभी सेट नहीं करती है। मान (value) को कंटेनर में लाने के लिए, इसे रेफर करें: environment: { KEY: "${NAME}" }, या इसके बजाय env_file: ./that-file.env का उपयोग करें।
क्या environment, env_file को ओवरराइड करता है, या इसके विपरीत?
environment: प्रभावी रहता है। Docker के दस्तावेजीकरण के अनुसार, environment एट्रिब्यूट को env_file एट्रिब्यूट से ऊपर रखा गया है, और दोनों ही कमांड लाइन पर docker compose run -e से नीचे हैं। यदि कोई की (key) दोनों जगहों पर सेट है, तो env_file में मौजूद मान को चुपचाप अनदेखा कर दिया जाता है।
मैं यह कैसे देखूँ कि Compose अंतिम रूप से किस मान का उपयोग करेगा?
पूरी तरह से रिजॉल्व की गई compose फाइल को प्रिंट करने के लिए docker compose config चलाएं, जिसमें सभी इंटरपोलेशन लागू हों। जो कंटेनर पहले से चल रहा है, उसके लिए docker inspect <container> --format '{{json .Config.Env}}' यह दिखाता है कि उसकी प्रोसेस को वास्तव में क्या प्राप्त हुआ है।
क्या Compose secrets एन्क्रिप्टेड होते हैं?
नहीं। फाइल-आधारित secret को कंटेनर के अंदर /run/secrets/<name> पर एक सादी फाइल के रूप में माउंट किया जाता है, और सोर्स फाइल होस्ट डिस्क पर बिना एन्क्रिप्शन के रहती है। इसका लाभ स्कोप है, एन्क्रिप्शन नहीं: मान कंटेनर एनवायरनमेंट से बाहर रहता है, docker inspect आउटपुट से बाहर रहता है, और उन क्रैश डंप से भी बाहर रहता है जो एनवायरनमेंट को प्रिंट करते हैं।
क्या मैं env फाइल में कोट्स और स्पेस का उपयोग कर सकता हूँ?
हाँ। Compose आसपास के कोट्स को हटा देता है, इसलिए KEY="value with spaces" और KEY=value with spaces दोनों का परिणाम value with spaces होता है। सिंगल कोट्स का उपयोग तब करें जब मान में $ हो जिसे इंटरपोलेट नहीं किया जाना चाहिए, और # युक्त किसी भी मान को कोट करें, क्योंकि बिना कोट के वह एक कमेंट शुरू कर देता है। = के आसपास के स्पेस को हटा दिया जाता है, इसलिए KEY = value अभी भी KEY को ही सेट करता है।