تفاوت .env، env_file و environment در Docker Compose
در Docker Compose سه سازوکار با نام env وجود دارد. تفاوت .env، env_file و environment، ترتیب اولویت آنها و محل درست نگهداری secrets را با مثال ببینید.
سه چیزی که افراد به آنها فایل env میگویند
Docker Compose سه سازوکار جداگانه با نامهایی بسیار مشابه دارد که ممکن است باعث سردرگمی شوند. فایل .env پیش از آنکه Compose فایل را تجزیه کند، جاینگهدارهای ${VARIABLE} را در خود compose.yaml مقداردهی میکند. ویژگی env_file: فایلی شامل جفتهای کلید/مقدار را در محیط کانتینر بارگذاری میکند. ویژگی environment: متغیرها را مستقیماً روی کانتینر تنظیم میکند و مقادیر آن در فایل compose نوشته میشوند. این سازوکارها قابل جایگزینی با یکدیگر نیستند. اگر دو مورد از آنها یک کلید یکسان را تنظیم کنند، ترتیب اولویت مستند تعیین میکند کدام مقدار برنده باشد.
این راهنما نحوه کار هر مورد را نشان میدهد و با فرمانی که میتوانید اجرا کنید، ترتیب اولویت را اثبات میکند. سپس به نکته مهمتری میپردازد: هر کسی که بتواند docker inspect را اجرا کند، میتواند متغیرهای محیطی را بخواند؛ بنابراین گذرواژهها نباید در آنها قرار بگیرند. اگر با فایلهای compose بهطور کلی آشنا نیستید، ابتدا مبانی Docker Compose روی VPS را بخوانید و سپس برای پیکربندی به این راهنما بازگردید.
فایل .env برای فایل compose است، نه کانتینر
یک دایرکتوری ایجاد کنید و دو فایل را در آن قرار دهید.
mkdir -p ~/envdemo && cd ~/envdemo
printf 'ALPINE_TAG=3.20\n' > .envservices:
demo:
image: alpine:${ALPINE_TAG}
command: printenv ALPINE_TAGاکنون از Compose بپرسید که واقعاً چه چیزی را تجزیه کرده است.
docker compose configخروجی image: alpine:3.20 را نشان میدهد. جاینگهدار حذف شده است، زیرا در زمان تجزیه، interpolation انجام شده است. Compose بهدنبال .env در دایرکتوری پروژه میگردد؛ این دایرکتوری همان دایرکتوری حاوی فایل compose است و هر مورد ${NAME} را که پیدا کند، جایگزین میکند.
سپس سرویس را اجرا کنید.
docker compose run --rm demoprintenv ALPINE_TAG با وضعیت 1 خارج میشود و چیزی چاپ نمیکند. این متغیر داخل کانتینر وجود ندارد. این رایجترین سوءبرداشت است: .env فایل compose را پیکربندی کرده است، نه فرایند را. وجود یک فایل .env حاوی POSTGRES_PASSWORD=hunter2 بهتنهایی هیچ اثری بر پایگاه داده شما ندارد، مگر اینکه بخشی از فایل compose به آن ارجاع دهد.
${NAME:-default} زمانی که متغیر تنظیم نشده یا خالی باشد، یک مقدار جایگزین فراهم میکند. ${NAME:?message} باعث میشود Compose از شروع کار خودداری کند و پیام شما را چاپ کند؛ این گزینه برای مقداری که پیشفرض ایمنی ندارد، انتخاب مناسبی است.
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 است؛ هر خط یک متغیر را تعریف میکند و # آغازگر توضیح است. این قالب، قالب shell نیست. در بیشتر موارد، علامتهای نقلقول بخشی از مقدار باقی میمانند و نیازی به پیشوندهای export نیست. اطراف علامت = فاصله نگذارید، زیرا KEY = value متغیری با نام KEY ایجاد میکند که مقدار آن با یک فاصله ابتدایی شروع میشود.
وجود نداشتن مسیر env_file خطا محسوب میشود و Compose متوقف میشود. اگر نبودن فایل ممکن است مجاز باشد، آن را اختیاری علامتگذاری کنید:
env_file:
- path: ./app.env
required: falseمحیط متغیرها را بهصورت درونخطی تنظیم میکند
services:
demo:
image: alpine:3.20
command: printenv GREETING
environment:
GREETING: from_environmentدو نحو پذیرفته میشود: شکل نگاشتی بالا و شکل فهرستی با استفاده از - GREETING=from_environment. عملکرد هر دو یکسان است. شکل فهرستی یک قابلیت اضافی دارد: کلیدی بدون مقدار، متغیر را از پوستهای که docker compose را در آن اجرا کردهاید، عبور میدهد.
environment:
- GREETINGGREETING=from_my_shell docker compose run --rm demoاین دستور from_my_shell را چاپ میکند. اگر آن را بدون تنظیم GREETING در پوسته اجرا کنید، Compose هیچ مقداری تنظیم نمیکند و هیچ هشداری نیز نمایش نمیدهد. آگاهی از خطاهای بیصدای عبور متغیرها اهمیت دارد، زیرا سرویسی که با یک متغیر رمزعبور خالی شروع میشود، اغلب با موفقیت اجرا میشود اما عملاً کاملاً بدون محافظت است.
کدام مورد برنده است
Docker ترتیب اولویت را از بیشترین به کمترین مستند کرده است: docker compose run -e در خط فرمان، سپس environment یا env_file که مقدار آن از shell یا یک فایل env درونیابی میشود، سپس environment ساده در فایل compose، بعد env_file، و در نهایت دستور ENV که در image قرار گرفته است.
خلاصه برای کار روزمره: environment: بر env_file: اولویت دارد و -e در خط فرمان بر هر دوی آنها اولویت دارد. این موضوع را در یک فایل بررسی کنید.
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 مقدار خط فرمان را بازنویسی نمیکند.
وقتی یک container طوری رفتار میکند که انگار پیکربندی شما هرگز اعمال نشده است، حدس نزنید. docker compose config فایل کاملاً resolveشده را چاپ میکند و docker compose config --environment متغیرهای درونیابی را که Compose از آنها استفاده میکند چاپ میکند. بیشتر گزارشهای «فایل env من نادیده گرفته میشود» در نهایت به مقداری مربوط میشوند که در دو سطح متفاوت، 2 بار تنظیم شده است.
چرا متغیرهای محیطی نشت میکنند
یک گذرواژه را در environment: تنظیم کنید؛ این مقدار در پیکربندی کانتینر روی دیسک ذخیره میشود و برای هر کاربری که عضو گروه docker باشد، قابل مشاهده است.
docker compose run -d --name leaky -e DB_PASSWORD=hunter2 demo sleep 300
docker inspect leaky --format '{{json .Config.Env}}'خروجی شامل "DB_PASSWORD=hunter2" بهصورت متن ساده است. سه مسیر دیگر نیز همین مقدار را آشکار میکنند. docker compose config آن را در ترمینال چاپ میکند و به همین دلیل ممکن است در یک انجمن پشتیبانی paste شود. هر فرایندی داخل کانتینر میتواند /proc/1/environ را بخواند و هر فرایند فرزند نیز این متغیر را به ارث میبرد. همچنین، کنترلکنندههای crash برنامه معمولاً کل محیط را در یک log یا گزارش خطا dump میکنند.
عضویت در گروه docker عملاً معادل دسترسی root روی میزبان است؛ بنابراین نمیتوان روی آن بهعنوان یک مرز privilege تکیه کرد. راهنمای حسابهای کاربری با کمترین سطح دسترسی در یک VPS توضیح میدهد چرا باید دسترسی به این گروه را در هر سرور اشتراکی محدود کرد.
secrets در Compose مقدار را در یک فایل نگه میدارند
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 در مسیر /run/secrets/db_password داخل container mount میشود. نام بعد از slash، نام secret از بلوک سطح بالای secrets: است.
پسوند _FILE قراردادی است که Docker Official Images، از جمله postgres، mysql و mariadb، از آن استفاده میکنند. این اسکریپتهای entrypoint وجود VARNAME_FILE را بررسی میکنند، فایل را میخوانند و از محتوای آن استفاده میکنند. این قابلیت، ویژگی Docker نیست. بنابراین فقط در imageهایی کار میکند که آن را پیادهسازی کردهاند. پیش از فرض اینکه SOMETHING_FILE رعایت میشود، مستندات image را بررسی کنید. برنامههایی که از آن پشتیبانی نمیکنند، معمولاً میتوانند فایل را هنگام راهاندازی خودشان بخوانند. همچنین میتوانید مسیر فایل را ارسال کنید تا entrypoint خودتان این کار را انجام دهد.
از داخل container در حال اجرا بررسی کنید:
docker compose exec db cat /run/secrets/db_password
docker compose exec db printenv POSTGRES_PASSWORDدستور اول password را چاپ میکند. دستور دوم چیزی چاپ نمیکند، زیرا مقدار هرگز وارد environment نشده است. هدف اصلی همین است: docker inspect در این container فقط مسیر بیخطری را نشان میدهد.
فایل مبدأ را روی host محافظت کنید، زیرا secret فقط بهاندازه فایلی که پشت آن قرار دارد خصوصی است:
chmod 600 db_password.txtحد میانی عملی در یک VPS
بسیاری از imageهای self-hosted از متغیرهای _FILE پشتیبانی نمیکنند؛ بنابراین متغیرهای محیطی تنها راه ورود مقادیر هستند. در یک VPS با یک مدیر، هدف واقعبینانه این است که مقادیر را از یک فایل قابلخواندن برای همه در پوشه پروژه خارج کنید و آنها را در 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 تعیینشده ایجاد میکند؛ بنابراین بازهای وجود ندارد که فایل برای همه قابلخواندن باشد. مالک فایل root است؛ در نتیجه کاربری غیر root روی سیستم نمیتواند آن را بخواند، اما هر کسی که بتواند docker را اجرا کند، همچنان میتواند مقدار را از کانتینر بخواند. *.env و .env را به .gitignore اضافه کنید و در عوض، یک app.env.example ثبت کنید که نام کلیدها را با مقادیر خالی نگه میدارد. گذرواژهای که commit شده باشد، باید تغییر داده شود.
تغییر دورهای یک مقدار یعنی راهاندازی مجدد سرویس. متغیرهای محیطی هنگام شروع فرایند کانتینر فقط یک بار خوانده میشوند؛ بنابراین ویرایش فایل تا زمانی که docker compose up -d --force-recreate db را اجرا نکنید، اثری ندارد. همین الگو در راهنمای اجرای n8n پشت HTTPS در یک VPS نیز استفاده شده است؛ در آن راهنما، کلید رمزنگاری خارج از فایل compose قرار دارد.
تفکیک پیکربندی بر اساس محیط
Compose بهطور پیشفرض .env را از دایرکتوری پروژه میخواند. با استفاده از --env-file آن را به مکان دیگری هدایت کنید.
docker compose --env-file .env.staging configفایلهای متعدد بهترتیب خوانده میشوند و فایلهای بعدی، مقادیر فایلهای قبلی را بازنویسی میکنند. مقادیر پیشفرض غیرمحرمانه را در فایلی نگه دارید که در مخزن ثبت شده است و اسرار را در فایلی قرار دهید که هرگز از سرور خارج نمیشود. همین موضوع درباره env_file: نیز صدق میکند؛ در صورت تکراری بودن یک کلید، مقدار آخرین فایل فهرستشده برنده است.
FAQ
چرا فایل .env من داخل container نادیده گرفته میشود؟
این فایل نادیده گرفته نمیشود. فایل .env فقط جاینماهای ${NAME} را در فایل compose جایگزین میکند. این فایل هرگز متغیرهایی را داخل container تنظیم نمیکند. برای انتقال مقدار به container، به آن ارجاع دهید: environment: { KEY: "${NAME}" }، یا بهجای آن از env_file: ./that-file.env استفاده کنید.
آیا environment بر env_file اولویت دارد یا برعکس؟
environment: اولویت دارد. ترتیب مستند Docker، ویژگی environment را بالاتر از ویژگی env_file قرار میدهد و هر دو پایینتر از docker compose run -e در خط فرمان هستند. اگر یک کلید در هر دو محل تنظیم شده باشد، مقدار موجود در env_file بدون هیچ پیامی استفاده نمیشود.
چگونه مقدار نهایی مورد استفاده Compose را ببینم؟
برای چاپ فایل compose کاملاً resolveشده، با اعمال همه جایگزینیها، docker compose config را اجرا کنید. برای containerای که از قبل در حال اجراست، docker inspect <container> --format '{{json .Config.Env}}' دقیقاً مقداری را نشان میدهد که فرایند آن دریافت کرده است.
آیا secrets در Compose رمزنگاری میشوند؟
خیر. یک secret مبتنی بر فایل، بهصورت یک فایل ساده در مسیر /run/secrets/<name> داخل container mount میشود و فایل مبدأ نیز روی دیسک host بدون رمزنگاری باقی میماند. مزیت آن محدوده دسترسی است، نه رمزنگاری: مقدار خارج از محیط container، خارج از خروجی docker inspect و خارج از crash dumpهایی باقی میماند که environment را چاپ میکنند.
آیا میتوانم در فایل env از نقلقول و فاصله استفاده کنم؟
از KEY=value with spaces استفاده کنید و نقلقولها را حذف کنید. Compose تمام باقیمانده خط را بهعنوان مقدار در نظر میگیرد؛ بنابراین نقلقولها معمولاً بهصورت نویسههای واقعی وارد مقدار میشوند. هرگز اطراف = فاصله نگذارید، زیرا در این حالت کلید یک فاصله انتهایی خواهد داشت و هیچ مقداری با آن تطبیق نمیکند.