Docker Compose-এ .env, env_file ও environment-এর পার্থক্য
Docker Compose-এ .env, env_file ও environment কীভাবে আলাদা, কোনটির মান কার্যকর হয় এবং কেন password রাখা নিরাপদ নয়, বাস্তব command ও precedence উদাহরণসহ জানুন।
যে তিনটি জিনিসকে মানুষ env file বলে
Docker Compose-এ বিভ্রান্তিকরভাবে একই রকম নামের তিনটি পৃথক ব্যবস্থা রয়েছে। .env file, Compose YAML পড়ার পরে এবং কোনো validation বা কাজ চালানোর আগে, compose.yaml-এর ভেতরের ${VARIABLE} placeholder-এর মান বসায়। env_file: attribute key/value জোড়ার একটি file container-এর environment-এ লোড করে। environment: attribute compose file-এ সরাসরি লেখা variable container-এ সেট করে। এগুলো পরস্পরের বিকল্প নয়। দুটি ব্যবস্থায় একই key সেট হলে কোনটির মান কার্যকর হবে, তা নথিভুক্ত precedence order দ্বারা নির্ধারিত হয়।
এই guide-এ প্রতিটি ব্যবস্থা কার্যকর অবস্থায় দেখানো হবে। আপনি চালাতে পারেন এমন একটি command দিয়ে precedence প্রমাণ করা হবে। এরপর আরও গুরুত্বপূর্ণ বিষয়টি ব্যাখ্যা করা হবে: যে কেউ docker inspect চালাতে পারলে environment variable পড়তে পারে। তাই password এগুলোর মধ্যে রাখা উচিত নয়। আপনি যদি সাধারণভাবে compose file সম্পর্কে নতুন হন, VPS-এ Docker Compose-এর মৌলিক বিষয় দিয়ে শুরু করুন। Configuration সম্পর্কে জানতে পরে এখানে ফিরে আসুন।
.env ফাইল compose file-এর জন্য, container-এর জন্য নয়
একটি directory তৈরি করুন এবং এর মধ্যে দুটি file রাখুন।
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 configOutput-এ image: alpine:3.20 দেখা যাচ্ছে। Placeholder আর নেই, কারণ Compose file load করার সময়ই interpolation সম্পন্ন হয়েছে। Compose project directory-তে .env খোঁজে: আপনি এটি সেট করলে --project-directory-এ দেওয়া directory ব্যবহার করে; তা না হলে -f-এর মাধ্যমে দেওয়া প্রথম file-এর directory ব্যবহার করে; সেটিও না থাকলে বর্তমান directory ব্যবহার করে। YAML value-এ পাওয়া ${NAME} placeholder-গুলো এটি substitute করে। Key কখনো interpolate হয় না, আর দ্বিগুণ $$ substitution না করে একটি literal dollar sign তৈরি করে।
এখন service চালান।
docker compose run --rm demoprintenv ALPINE_TAG status 1 নিয়ে exit করে এবং কিছুই print করে না। Container-এর ভেতরে variable-টি নেই। এটিই সবচেয়ে প্রচলিত ভুল ধারণা: .env compose file কনফিগার করেছে, process-কে নয়। একটি .env file-এ POSTGRES_PASSWORD=hunter2 থাকলেও database-এর জন্য তা একেবারেই কোনো কাজ করে না, যদি compose file-এর কোনো অংশ সেটিকে reference না করে।
Variable unset বা empty হলে ${NAME:-default} একটি fallback value দেয়। ${NAME:?message} Compose-কে start করতে অস্বীকার করতে এবং আপনার দেওয়া message print করতে বাধ্য করে। নিরাপদ default নেই—এমন value-এর জন্য এটিই সঠিক পছন্দ।
env_file কনটেইনারে ভেরিয়েবল লোড করে
env_file: attribute এক বা একাধিক ফাইলের নাম নির্ধারণ করে। ওই ফাইলগুলোর বিষয়বস্তু কনটেইনারের environment variable-এ পরিণত হয়।
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 প্রিন্ট করে। ফাইলের format হলো প্রতি লাইনে একটি করে plain KEY=value line, যেখানে # দিয়ে comment শুরু হয়। এটি shell নয়, যদিও Compose এতে shell-এর মতো কয়েকটি নিয়ম প্রয়োগ করে। চারপাশের quote বাদ দেওয়া হয়। তাই KEY="value" এবং KEY=value একই ফল দেয়। Double-quoted এবং unquoted value-তে ${...} placeholder interpolate করা হয়, কিন্তু single-quoted value হুবহু ব্যবহার করা হয়। export prefix গ্রহণযোগ্য, তবে এটি প্রয়োজনীয় নয়। = sign-এর চারপাশের space বাদ দেওয়া হয়। যে বিষয়টি খেয়াল রাখতে হবে তা হলো inline comment: unquoted value-তে space-এর পরে # থাকলে comment শুরু হয়। তাই # থাকা যেকোনো value quote করুন।
env_file path না পাওয়া গেলে এটি error হিসেবে গণ্য হয় এবং Compose থেমে যায়। ফাইলটি বৈধভাবে অনুপস্থিত থাকতে পারলে এটিকে optional হিসেবে চিহ্নিত করুন:
env_file:
- path: ./app.env
required: falseenvironment inline-এ variable সেট করে
services:
demo:
image: alpine:3.20
command: printenv GREETING
environment:
GREETING: from_environmentদুটি syntax গ্রহণযোগ্য: উপরের mapping form এবং - GREETING=from_environment ব্যবহার করা list form। এগুলো একইভাবে কাজ করে। উভয় form-এ আরও একটি সুবিধা আছে: কোনো value ছাড়া key দিলে docker compose চালানো shell environment থেকে variable-টি pass through হয়। list form-এ এটি একটি bare - GREETING, আর mapping form-এ colon-এর পরে কিছু না থাকা GREETING:।
environment:
- GREETINGGREETING=from_my_shell docker compose run --rm demoএটি from_my_shell print করে। shell-এ GREETING সেট না করে এটি চালালে Compose কোনো value সেট করে না এবং কোনো warning-ও দেয় না। এ ধরনের নীরব pass-through failure জানা জরুরি, কারণ empty password variable নিয়ে start হওয়া service প্রায়ই সফলভাবে চালু হয়, কিন্তু কার্যত সম্পূর্ণ উন্মুক্ত থাকে।
কোনটি কার্যকর হয়
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 দেখায় Compose কোন interpolation variables ব্যবহার করছে। “আমার env file উপেক্ষা করা হচ্ছে” ধরনের অধিকাংশ সমস্যার কারণ হলো একই মান দুটি ভিন্ন স্তরে নির্ধারিত থাকা।
পরিবেশ ভেরিয়েবল কীভাবে ফাঁস হয়
environment:-এ একটি password সেট করলে সেটি disk-এ container-এর configuration-এ সংরক্ষিত হয় এবং 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 হিসেবে থাকে। আরও তিনটি path একই value প্রকাশ করে। docker compose config সেটি terminal-এ দেখায়। এভাবেই value-টি কোনো support forum-এ paste হয়ে যেতে পারে। Parent process ইচ্ছাকৃতভাবে replacement environment দিয়ে child process শুরু না করলে child process-গুলো defaultভাবে environment variable inherit করে। একই user হিসেবে PID 1-এর মতো চলা কোনো process, অথবা CAP_SYS_PTRACE ধারণকারী কোনো process, /proc/1/environ থেকে সেটি পড়তে পারে। যে container-এ সবকিছু root হিসেবে চলে, সেখানে এটি প্রতিটি process-এর ক্ষেত্রেই প্রযোজ্য। PID 1 root হিসেবে চললেও কোনো application unprivileged user হিসেবে চললে ওই file-এ permission error পাবে। তবে application-টির নিজের environment-এ variable-টি তখনও থাকবে। এছাড়া application-এর crash handler-গুলো নিয়মিত পুরো environment কোনো log বা error report-এ dump করে।
docker group-এর সদস্যপদ host-এ কার্যত root privilege-এর সমতুল্য। তাই এটিকে নির্ভরযোগ্য privilege boundary হিসেবে ব্যবহার করা যায় না। VPS-এ least privilege user account-এর guide-এ ব্যাখ্যা করা হয়েছে, shared box-এ এই group-এর সদস্যপদ সীমিত রাখা কেন গুরুত্বপূর্ণ।
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-এর পরের নামটি শীর্ষ-স্তরের secrets: block থেকে নেওয়া secret-এর নাম।
_FILE suffix-টি postgres, mysql এবং mariadb-সহ Docker Official Images-এ ব্যবহৃত একটি প্রচলিত রীতি। ওই entrypoint script-গুলো VARNAME_FILE পরীক্ষা করে, ফাইলটি পড়ে এবং তার বিষয়বস্তু ব্যবহার করে। এটি Docker-এর কোনো feature নয়। তাই image-টি এটি বাস্তবায়ন করলেই কেবল কাজ করবে। SOMETHING_FILE কার্যকর হবে ধরে নেওয়ার আগে image-এর documentation পরীক্ষা করুন। এটি সমর্থন করে না এমন application-গুলো সাধারণত startup-এর সময় নিজেরাই ফাইলটি পড়তে পারে। অন্যথায় path-টি pass করে নিজের 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-টির গোপনীয়তা ওই file-টির সুরক্ষার ওপরই নির্ভর করে:
chmod 600 db_password.txtVPS-এ ব্যবহারিক মধ্যপন্থা
অনেক self hosted image _FILE variable সমর্থন করে না, তাই environment variable-ই একমাত্র উপায়। একটি একক administrator VPS-এ বাস্তবসম্মত লক্ষ্য হলো আপনার project directory-র এমন file-এ value পড়ে থাকা বন্ধ করা, যা সবাই পড়তে পারে, এবং সেগুলোকে 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 শুরুতেই নির্ধারিত mode-সহ file তৈরি করে। তাই এমন কোনো সময় থাকে না, যখন file-টি সবাই পড়তে পারে। এর মালিক root। ফলে server-এর non root user এটি পড়তে পারে না। তবে যে কেউ docker চালাতে পারে, সে container থেকে value পড়তে পারে। *.env এবং .env .gitignore-এ যোগ করুন। এরপর key name-গুলো empty value-সহ রাখা একটি app.env.example commit করুন। Commit করা password পরিবর্তন করতে হবে।
কোনো value পরিবর্তন করলে service restart করতে হয়। Container process শুরু হওয়ার সময় environment variable একবার পড়া হয়। তাই file সম্পাদনা করলেও docker compose up -d --force-recreate db চালানো পর্যন্ত কোনো পরিবর্তন কার্যকর হয় না। VPS-এ HTTPS-এর পেছনে n8n guide-এও একই পদ্ধতি ব্যবহার করা হয়েছে। সেখানে encryption key compose file-এর বাইরে রাখা হয়।
পরিবেশভেদে কনফিগারেশন বিভক্ত করা
Compose ডিফল্টভাবে প্রজেক্ট ডিরেক্টরি থেকে .env পড়ে। --env-file ব্যবহার করে এটিকে অন্য স্থানে নির্দেশ করুন।
docker compose --env-file .env.staging configএকাধিক ফাইল ক্রমানুসারে পড়া হয়, এবং পরের ফাইলগুলো আগের ফাইলের মান প্রতিস্থাপন করে। গোপন নয় এমন ডিফল্ট মানগুলো কমিট করা একটি ফাইলে রাখুন এবং গোপন মানগুলো এমন একটি ফাইলে রাখুন, যা কখনো সার্ভারের বাইরে যাবে না। env_file:-এর ক্ষেত্রেও একই নিয়ম প্রযোজ্য; একই key একাধিকবার থাকলে তালিকার সর্বশেষ ফাইলের মান কার্যকর হয়।
FAQ
আমার .env ফাইল container-এর ভিতরে উপেক্ষা করা হচ্ছে কেন?
এটি উপেক্ষা করা হচ্ছে না। .env ফাইলটি compose file-এর ${NAME} placeholder-এর মান বসায়। এটি Compose-এর নিজস্ব COMPOSE_PROJECT_NAME বা COMPOSE_FILE-এর মতো variable-ও সেট করতে পারে। তবে এটি কখনো container-এর ভিতরের variable সেট করে না। মানটি container-এ পাঠাতে environment: { KEY: "${NAME}" }-এ তা reference করুন, অথবা এর পরিবর্তে env_file: ./that-file.env ব্যবহার করুন।
environment কি env_file-কে override করে, নাকি উল্টোটি?
environment: প্রাধান্য পায়। Docker-এর নথিভুক্ত precedence অনুযায়ী environment attribute, env_file attribute-এর উপরে থাকে। কমান্ড লাইনের docker compose run -e উভয়ের চেয়েই বেশি প্রাধান্য পায়। কোনো key উভয় জায়গায় সেট করা থাকলে env_file-এর মান নীরবে ব্যবহার করা হয় না।
Compose যে চূড়ান্ত মান ব্যবহার করবে, তা কীভাবে দেখব?
সম্পূর্ণ resolved compose file দেখাতে docker compose config চালান। এতে সব interpolation প্রয়োগ করা থাকে। ইতিমধ্যে চলমান container-এর ক্ষেত্রে তার process যে মান পেয়েছে, docker inspect <container> --format '{{json .Config.Env}}' ঠিক সেটিই দেখায়।
Compose secret কি encrypted থাকে?
না। File-based secret একটি plain file হিসেবে /run/secrets/<name>-এ container-এ mount করা হয়। Source file-ও host disk-এ unencrypted অবস্থায় থাকে। এর সুবিধা encryption নয়, scope নিয়ন্ত্রণ। মানটি container environment-এর বাইরে, docker inspect output-এর বাইরে এবং environment মুদ্রণকারী crash dump-এর বাইরেও থাকে।
env file-এ quotes এবং spaces ব্যবহার করা যাবে কি?
হ্যাঁ। Compose বাইরের quotes সরিয়ে দেয়। তাই KEY="value with spaces" এবং KEY=value with spaces—দুটিই value with spaces দেয়। মানের মধ্যে interpolation করা যাবে না এমন $ থাকলে single quotes ব্যবহার করুন। # থাকা যেকোনো মান quote করুন, কারণ unquoted অবস্থায় এটি comment শুরু করে। =-এর চারপাশের spaces ছেঁটে ফেলা হয়। তাই KEY = value এখনও KEY সেট করে।