متغیرهای محیطی و فایل `.env` در Docker Compose: مدیریت پیکربندی‌ها

فهرست مطالب

متغیرهای محیطی و فایل .env در Docker Compose: مدیریت پیکربندی‌ها

در دنیای توسعه نرم‌افزار مدرن، مدیریت پیکربندی‌ها یکی از چالش‌های اساسی و حیاتی محسوب می‌شود. با افزایش پیچیدگی برنامه‌ها و مهاجرت به معماری‌های میکروسرویس، نیاز به روشی انعطاف‌پذیر، امن و کارآمد برای تفکیک پیکربندی از کد بیش از پیش احساس می‌شود. این نیاز به ویژه در محیط‌های کانتینری و ابزارهایی مانند Docker Compose که برای ارکستراسیون برنامه‌های چند کانتینری به کار می‌روند، اهمیت دوچندانی پیدا می‌کند.

تصور کنید یک برنامه وب دارید که از یک پایگاه داده، یک کش‌کننده (مانند Redis) و یک سرویس احراز هویت خارجی استفاده می‌کند. هر یک از این اجزا نیاز به پیکربندی‌های خاص خود دارند، مانند نام کاربری و رمز عبور پایگاه داده، آدرس سرور Redis، کلیدهای API برای سرویس احراز هویت و غیره. اگر این مقادیر مستقیماً در کد برنامه یا فایل docker-compose.yml سخت‌کد شوند، بروزرسانی، تغییر محیط (از توسعه به تولید) و حفظ امنیت اطلاعات حساس به یک کابوس تبدیل خواهد شد.

Docker Compose، به عنوان ابزاری قدرتمند برای تعریف و اجرای برنامه‌های چند کانتینری، راه حل‌های هوشمندانه‌ای را برای مدیریت پیکربندی ارائه می‌دهد که متغیرهای محیطی و فایل .env در قلب این راه حل‌ها قرار دارند. این رویکرد به شما امکان می‌دهد تا پارامترهای پیکربندی را از منطق برنامه و ساختار استقرار جدا کنید و انعطاف‌پذیری بی‌سابقه‌ای در مدیریت محیط‌های مختلف و حفظ امنیت اطلاعات حساس به ارمغان آورید.

در این مقاله جامع، ما به تفصیل به بررسی مبانی متغیرهای محیطی، نقش آن‌ها در Docker Compose و کاربرد قدرتمند فایل .env خواهیم پرداخت. از درک اولیه نحوه کار این مکانیسم‌ها تا بررسی روش‌های مختلف تزریق متغیرها، سناریوهای کاربردی، بهترین شیوه‌ها و ملاحظات امنیتی، همه و همه را پوشش خواهیم داد. هدف این است که به جامعه تخصصی توسعه‌دهندگان و مهندسان DevOps دیدگاهی عمیق و کاربردی در مورد بهینه‌سازی مدیریت پیکربندی با Docker Compose ارائه دهیم.

درک مبانی متغیرهای محیطی

متغیرهای محیطی، (Environment Variables) همانطور که از نامشان پیداست، متغیرهایی هستند که در محیط اجرای یک فرآیند (process) تعریف می‌شوند. این متغیرها اطلاعات پیکربندی پویا را برای فرآیندها و برنامه‌هایی که در آن محیط اجرا می‌شوند، فراهم می‌کنند. هر سیستم عامل، چه ویندوز، لینوکس یا macOS، مکانیزمی برای تعریف، مشاهده و استفاده از متغیرهای محیطی دارد.

در هسته خود، یک متغیر محیطی صرفاً یک جفت کلید-مقدار (key-value pair) است. به عنوان مثال، PATH=/usr/local/bin:/usr/bin یا HOME=/home/user. این مقادیر می‌توانند توسط برنامه‌ها در زمان اجرا خوانده شوند و بر رفتار آن‌ها تأثیر بگذارند. برای مثال، متغیر PATH به سیستم عامل می‌گوید که برای یافتن فایل‌های اجرایی در کدام دایرکتوری‌ها جستجو کند.

نقش متغیرهای محیطی در توسعه نرم‌افزار

تاریخچه استفاده از متغیرهای محیطی به روزهای اولیه سیستم‌های یونیکس باز می‌گردد، جایی که از آن‌ها برای پیکربندی پوسته و برنامه‌های کاربردی استفاده می‌شد. با گذشت زمان و پیچیده‌تر شدن برنامه‌ها، نقش متغیرهای محیطی در توسعه نرم‌افزار بسیار پررنگ‌تر شد. امروزه، آن‌ها به یک اصل اساسی در طراحی برنامه‌های Twelve-Factor App تبدیل شده‌اند، که بر جداسازی کامل پیکربندی از کد تأکید دارد.

این جداسازی مزایای متعددی دارد:

  • قابلیت حمل (Portability): یک کد واحد می‌تواند در محیط‌های مختلف (توسعه، آزمایش، تولید) با پیکربندی‌های متفاوت بدون نیاز به تغییر در کد منبع یا بازسازی (rebuild) برنامه، اجرا شود.
  • امنیت (Security): اطلاعات حساس مانند رمز عبور پایگاه داده، کلیدهای API و اعتبارنامه‌ها می‌توانند از کد منبع جدا شده و به صورت امن‌تر مدیریت شوند. این امر خطر افشای این اطلاعات در مخازن کد (مانند Git) را کاهش می‌دهد.
  • انعطاف‌پذیری (Flexibility): تغییر یک پارامتر پیکربندی، تنها با بروزرسانی یک متغیر محیطی انجام می‌شود و نیازی به تغییر یا استقرار مجدد برنامه نیست.
  • قابلیت نگهداری (Maintainability): جداسازی پیکربندی از کد، خوانایی و نگهداری هر دو بخش را بهبود می‌بخشد.

در یک برنامه مدرن، متغیرهای محیطی می‌توانند برای موارد زیر استفاده شوند:

  • تعیین پورت‌های گوش دادن (listening ports)
  • تنظیمات اتصال به پایگاه داده (نام میزبان، پورت، نام کاربری، رمز عبور)
  • کلیدهای API برای سرویس‌های خارجی
  • سطوح لاگ‌برداری (logging levels)
  • نام و آدرس سرویس‌های داخلی دیگر
  • فلاگ‌های فعال/غیرفعال کردن ویژگی‌ها (feature flags)

وقتی صحبت از محیط‌های کانتینری می‌شود، متغیرهای محیطی حتی حیاتی‌تر می‌شوند. یک کانتینر باید قادر باشد بدون هیچ گونه تغییر داخلی، در هر محیطی از توسعه‌دهنده محلی گرفته تا سرورهای ابری، اجرا شود. اینجاست که Docker و Docker Compose با قابلیت‌های خود برای مدیریت متغیرهای محیطی، به کمک می‌آیند تا این اصل را محقق سازند.

Docker Compose و نیاز به پیکربندی پویا

Docker Compose ابزاری برای تعریف و اجرای برنامه‌های چند کانتینری Docker است. با استفاده از یک فایل YAML به نام docker-compose.yml، شما می‌توانید تمامی سرویس‌های مورد نیاز برنامه خود را تعریف کنید – از جمله ایمیج‌های Docker، پورت‌های exposed، والیوم‌ها، شبکه‌ها و مهم‌تر از همه، متغیرهای محیطی.

فایل docker-compose.yml به شما امکان می‌دهد تا تمام خدمات برنامه خود را در یک مکان واحد تعریف کنید و با یک دستور ساده (docker-compose up)، تمامی این خدمات را به صورت یکپارچه بالا بیاورید. این امر فرآیند توسعه، آزمایش و استقرار برنامه‌های پیچیده را به شدت ساده می‌کند.

چرا سخت‌کد کردن مقادیر در docker-compose.yml اشتباه است؟

فرض کنید می‌خواهید رمز عبور پایگاه داده خود را مستقیماً در فایل docker-compose.yml بنویسید:

version: '3.8'
services:
  db:
    image: postgres:13
    environment:
      POSTGRES_DB: mydatabase
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mysecretpassword # مشکل اینجا است!
  app:
    image: myapp:latest
    ports:
      - "8000:8000"
    environment:
      DATABASE_HOST: db
      DATABASE_NAME: mydatabase
      DATABASE_USER: myuser
      DATABASE_PASSWORD: mysecretpassword # و اینجا!

این رویکرد، در نگاه اول، ممکن است ساده به نظر برسد، اما مشکلات جدی را به همراه دارد:

  1. افشای اطلاعات حساس (Security Risk): رمز عبور و سایر اطلاعات حساس مستقیماً در فایل پیکربندی که ممکن است به سیستم کنترل نسخه (مانند Git) commit شود، ذخیره می‌شوند. این امر امنیت برنامه شما را به شدت به خطر می‌اندازد. هر کسی که به مخزن کد دسترسی پیدا کند، می‌تواند این اطلاعات را مشاهده کند.
  2. عدم انعطاف‌پذیری محیطی (Lack of Environmental Flexibility): یک رمز عبور ممکن است برای محیط توسعه (development) مناسب باشد، اما قطعاً برای محیط تولید (production) مناسب نیست. با سخت‌کد کردن، شما مجبورید برای هر محیط، فایل docker-compose.yml جداگانه‌ای ایجاد و نگهداری کنید یا هر بار قبل از استقرار، فایل را ویرایش کنید که هر دو رویکرد مستعد خطا هستند.
  3. دشواری در بروزرسانی (Update Difficulty): اگر رمز عبور تغییر کند، شما باید تمام فایل‌های docker-compose.yml مربوطه را پیدا کرده و به صورت دستی بروزرسانی کنید. این فرآیند زمان‌بر و مستعد فراموشی است.
  4. نقض اصل “Twelve-Factor App”: این رویکرد به وضوح اصل جداسازی پیکربندی از کد را نقض می‌کند که یک اصل بنیادین برای ساخت برنامه‌های ابری و مقیاس‌پذیر است.

برای حل این مشکلات، Docker Compose از مکانیزم قدرتمند متغیرهای محیطی استفاده می‌کند. به جای سخت‌کد کردن مقادیر، شما می‌توانید از متغیرهای محیطی به عنوان جایگزین‌ها (placeholders) در فایل docker-compose.yml استفاده کنید. سپس، مقادیر واقعی این متغیرها را می‌توان از منابع خارجی (مانند فایل .env یا متغیرهای پوسته سیستم) به Docker Compose تزریق کرد.

این جداسازی نه تنها امنیت را افزایش می‌دهد، بلکه انعطاف‌پذیری و قابلیت نگهداری پیکربندی‌های شما را در طول چرخه حیات توسعه و استقرار به شدت بهبود می‌بخشد. در بخش‌های بعدی، به تفصیل به نحوه پیاده‌سازی این رویکرد خواهیم پرداخت و فایل .env را به عنوان یک ابزار کلیدی در این فرآیند معرفی خواهیم کرد.

معرفی فایل .env در Docker Compose

فایل .env یک فایل متنی ساده است که برای تعریف متغیرهای محیطی در یک پروژه استفاده می‌شود. این فایل به صورت گسترده‌ای در اکوسیستم توسعه نرم‌افزار، به ویژه برای پروژه‌هایی که از پلتفرم‌های مختلف یا ابزارهای مدیریت محیطی استفاده می‌کنند، پذیرفته شده است. در Docker Compose، فایل .env نقش بسیار مهمی در مدیریت پیکربندی‌های پویا ایفا می‌کند.

فایل .env چیست؟

یک فایل .env شامل لیستی از جفت‌های کلید-مقدار است که هر کدام در یک خط جداگانه قرار دارند. این فایل معمولاً در ریشه پروژه (همان دایرکتوری که فایل docker-compose.yml در آن قرار دارد) قرار می‌گیرد و به صورت پیش‌فرض توسط Docker Compose شناسایی و پردازش می‌شود.

قوانین و نحو (Syntax) فایل .env

قوانین نوشتاری برای فایل .env بسیار ساده هستند:

  • هر خط یک جفت کلید-مقدار را تعریف می‌کند.
  • کلید (نام متغیر) باید با حروف الفبا یا زیرخط (_) شروع شود و می‌تواند شامل حروف، اعداد و زیرخط باشد.
  • مقدار می‌تواند شامل هر رشته‌ای باشد.
  • خطوط خالی نادیده گرفته می‌شوند.
  • خطوطی که با # شروع می‌شوند، به عنوان کامنت در نظر گرفته می‌شوند و نادیده گرفته می‌شوند.
  • اگر مقدار شامل فاصله یا کاراکترهای خاص باشد، باید در کوتیشن (تک ' یا دوتایی ") قرار گیرد.
  • کاراکتر بک‌اسلش (\) برای فرار (escaping) از کاراکترهای خاص استفاده می‌شود.

مثال از یک فایل .env:

# پیکربندی پایگاه داده
DB_HOST=localhost
DB_PORT=5432
DB_USER=admin
DB_PASSWORD=secure_password_123

# تنظیمات برنامه
APP_DEBUG=true
APP_SECRET="super-secret-key-with spaces"
API_KEY=xyz123abc456

نحوه استفاده Docker Compose از فایل .env

وقتی شما دستور docker-compose up را در دایرکتوری ریشه پروژه خود اجرا می‌کنید، Docker Compose به صورت خودکار به دنبال یک فایل با نام .env در همان دایرکتوری می‌گردد. اگر این فایل را پیدا کند، متغیرهای تعریف شده در آن را بارگذاری کرده و آن‌ها را به عنوان متغیرهای محیطی در دسترس فرآیند docker-compose و سپس در دسترس سرویس‌های تعریف شده در docker-compose.yml قرار می‌دهد.

فرض کنید فایل .env بالا را دارید و فایل docker-compose.yml شما به این صورت است:

version: '3.8'
services:
  db:
    image: postgres:13
    environment:
      POSTGRES_DB: mydatabase
      POSTGRES_USER: ${DB_USER}       # استفاده از متغیر محیطی
      POSTGRES_PASSWORD: ${DB_PASSWORD} # استفاده از متغیر محیطی
  app:
    image: myapp:latest
    ports:
      - "8000:8000"
    environment:
      DATABASE_HOST: ${DB_HOST}
      DATABASE_NAME: mydatabase
      DATABASE_USER: ${DB_USER}
      DATABASE_PASSWORD: ${DB_PASSWORD}
      APP_DEBUG_MODE: ${APP_DEBUG}
      API_SECRET_KEY: ${API_KEY}

در این مثال، ${DB_USER}، ${DB_PASSWORD} و سایر متغیرها، در زمان اجرای docker-compose up، با مقادیر مربوطه از فایل .env جایگزین می‌شوند. این یک روش بسیار تمیز و امن برای تفکیک پیکربندی از فایل docker-compose.yml است.

تعیین فایل .env سفارشی

در برخی موارد، ممکن است بخواهید از یک فایل .env با نام متفاوت یا در مکان دیگری استفاده کنید. Docker Compose این امکان را فراهم می‌کند. می‌توانید از پرچم --env-file در خط فرمان docker-compose استفاده کنید:

docker-compose --env-file ./config/prod.env up -d

این دستور به Docker Compose می‌گوید که متغیرهای محیطی را از فایل ./config/prod.env بارگذاری کند، نه از فایل پیش‌فرض .env در دایرکتوری فعلی. این قابلیت برای مدیریت پیکربندی‌های مختلف برای محیط‌های مختلف (مانند توسعه، آزمایش و تولید) بسیار مفید است.

ترتیب اولویت (Order of Precedence)

درک ترتیب اولویت برای متغیرهای محیطی در Docker Compose بسیار مهم است، زیرا این امر تعیین می‌کند که کدام مقدار در نهایت برای یک متغیر خاص استفاده خواهد شد:

  1. متغیرهایی که مستقیماً از طریق پرچم --env-file در خط فرمان مشخص شده‌اند (بالاترین اولویت).
  2. متغیرهای محیطی که در پوسته (shell) شما تعریف شده‌اند (محیطی که دستور docker-compose را اجرا می‌کنید).
  3. متغیرهایی که در فایل .env در دایرکتوری فعلی پروژه تعریف شده‌اند.
  4. متغیرهایی که در بخش environment فایل docker-compose.yml برای یک سرویس خاص تعریف شده‌اند (پایین‌ترین اولویت در هنگام تفسیر خود فایل docker-compose.yml، اما برای متغیرهای داخلی کانتینر، بالاتر از 1-3).

این ترتیب به شما انعطاف‌پذیری زیادی در override کردن (بازنویسی) مقادیر متغیرها در سطوح مختلف می‌دهد. برای مثال، می‌توانید یک مقدار پیش‌فرض در .env داشته باشید، اما آن را در محیط production با یک متغیر shell یا فایل .env جداگانه override کنید.

روش‌های تزریق متغیرهای محیطی در Docker Compose

Docker Compose چندین روش برای تزریق متغیرهای محیطی به سرویس‌های شما ارائه می‌دهد که هر کدام برای سناریوهای خاصی مناسب هستند. درک این روش‌ها و ترتیب اولویت آن‌ها برای مدیریت کارآمد پیکربندی‌ها ضروری است.

1. از فایل .env (پیش‌فرض پروژه)

همانطور که قبلاً بحث شد، Docker Compose به صورت خودکار فایل .env را در دایرکتوری ریشه پروژه شما بارگذاری می‌کند. این روش بهترین گزینه برای متغیرهای پیکربندی است که بین توسعه‌دهندگان به اشتراک گذاشته می‌شوند و شامل اطلاعات بسیار حساس نیستند (یا نسخه عمومی و غیرحساس آن‌ها را در .env قرار می‌دهید و نسخه حساس را با .gitignore مخفی می‌کنید).

مثال:

.env:

POSTGRES_VERSION=13
APP_PORT=8000

docker-compose.yml:

version: '3.8'
services:
  db:
    image: postgres:${POSTGRES_VERSION}
  app:
    image: myapp:latest
    ports:
      - "${APP_PORT}:${APP_PORT}"

هنگامی که docker-compose up را اجرا می‌کنید، ${POSTGRES_VERSION} به 13 و ${APP_PORT} به 8000 تبدیل می‌شوند.

2. از متغیرهای محیطی پوسته (Shell Environment)

متغیرهای محیطی که در پوسته شما (جایی که دستور docker-compose را اجرا می‌کنید) تعریف شده‌اند، بر مقادیر موجود در فایل .env اولویت دارند. این روش برای override کردن موقت مقادیر برای آزمایش یا برای تزریق اطلاعات بسیار حساس در زمان اجرا (که هرگز نباید در یک فایل commit شوند) مفید است.

مثال:

.env:

DB_PASSWORD=dev_password

docker-compose.yml:

version: '3.8'
services:
  db:
    image: postgres:13
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}

در پوسته:

export DB_PASSWORD=prod_password
docker-compose up

در این حالت، سرویس db از prod_password استفاده خواهد کرد، زیرا متغیر پوسته بر متغیر .env اولویت دارد.

3. استفاده از بخش environment در docker-compose.yml

شما می‌توانید متغیرهای محیطی را مستقیماً در بخش environment یک سرویس در فایل docker-compose.yml تعریف کنید. این متغیرها به طور مستقیم به کانتینر تزریق می‌شوند.

مثال:

version: '3.8'
services:
  app:
    image: myapp:latest
    environment:
      DEBUG: "true"
      LOG_LEVEL: info
      # می‌توان به متغیرهای تعریف شده در .env یا پوسته نیز ارجاع داد
      DATABASE_HOST: ${DB_HOST:-database} # با مقدار پیش‌فرض

متغیرهای تعریف شده در این بخش، در زمان اجرای کانتینر، در داخل آن در دسترس خواهند بود. اگر مقادیر از .env یا پوسته با همین نام وجود داشته باشند، مقادیر .env یا پوسته ابتدا در خود فایل docker-compose.yml تفسیر شده و سپس مقدار نهایی به کانتینر تزریق می‌شود. در واقع، تفسیر ${VAR} در docker-compose.yml قبل از هر چیز دیگری انجام می‌شود. مقادیر تعریف شده به صورت مستقیم در environment برای متغیرهایی که نیازی به تغییر خارجی ندارند (مثلاً DEBUG: “true” برای محیط توسعه) مناسب هستند.

4. استفاده از بخش env_file در docker-compose.yml

برای تزریق متغیرها از یک فایل .env به صورت سرویس-خاص، می‌توانید از بخش env_file استفاده کنید. این به شما امکان می‌دهد تا چندین فایل .env را برای سرویس‌های مختلف داشته باشید، یا از فایل‌های .env که در جای دیگری ذخیره شده‌اند، استفاده کنید.

مثال:

database.env:

DB_NAME=production_db
DB_USER=prod_user
DB_PASSWORD=super_secret_prod_password

docker-compose.yml:

version: '3.8'
services:
  db:
    image: postgres:13
    env_file:
      - ./database.env # بارگذاری متغیرها از این فایل
  app:
    image: myapp:latest
    environment:
      DATABASE_URL: postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
    env_file:
      - ./database.env # اگر app هم به این متغیرها نیاز دارد

در این روش، متغیرهای موجود در database.env به طور مستقیم به محیط کانتینرهای db و app تزریق می‌شوند. نکته مهم این است که env_file برای تزریق مستقیم به کانتینرها است و محتوای آن مستقیماً در تفسیر اولیه docker-compose.yml (مثل ${VAR}) استفاده نمی‌شود مگر اینکه این متغیرها در پوسته یا .env پروژه هم تعریف شده باشند. در واقع، متغیرهایی که از env_file بارگذاری می‌شوند، اولویت کمتری نسبت به متغیرهای تعریف شده در environment و متغیرهای پوسته و .env پروژه دارند، اما در داخل کانتینرها در دسترس خواهند بود.

ترکیب روش‌ها و ترتیب اولویت نهایی

برای درک کامل، اجازه دهید ترتیب اولویت را به صورت جامع‌تر مرور کنیم (از بالاترین به پایین‌ترین اولویت برای متغیرهایی که در نهایت به کانتینر تزریق می‌شوند):

  1. متغیرهای تعریف شده در پوسته (Shell Environment Variables) در زمان اجرای docker-compose.
  2. متغیرهای تعریف شده در فایل .env (در دایرکتوری ریشه پروژه یا مشخص شده با --env-file).
  3. متغیرهای تعریف شده در بخش environment فایل docker-compose.yml.
  4. متغیرهای تعریف شده در فایل‌هایی که توسط env_file در docker-compose.yml مشخص شده‌اند.
  5. متغیرهای پیش‌فرض موجود در ایمیج Docker (اگر توسط هیچ یک از موارد بالا override نشده باشند).

این سلسله مراتب به شما اجازه می‌دهد تا انعطاف‌پذیری زیادی در مدیریت پیکربندی‌ها داشته باشید. برای مثال، می‌توانید مقادیر پیش‌فرض را در .env پروژه نگه دارید، آن‌ها را در docker-compose.yml با مقادیر ثابت override کنید یا با استفاده از متغیرهای پوسته، آن‌ها را برای محیط‌های خاص یا آزمایش‌ها به صورت موقت تغییر دهید.

سناریوهای کاربردی و مثال‌های عملی

برای روشن‌تر شدن مفاهیم، به چند سناریوی کاربردی می‌پردازیم که در آن‌ها متغیرهای محیطی و فایل .env نقش کلیدی ایفا می‌کنند.

1. پیکربندی اعتبارنامه‌های پایگاه داده

یکی از رایج‌ترین و مهم‌ترین کاربردهای متغیرهای محیطی، مدیریت اعتبارنامه‌های پایگاه داده است. این اطلاعات بسیار حساس هستند و هرگز نباید در کد منبع یا فایل‌های پیکربندی commit شوند.

.env:

POSTGRES_USER=myuser
POSTGRES_PASSWORD=securepassword
POSTGRES_DB=mydb

docker-compose.yml:

version: '3.8'
services:
  db:
    image: postgres:13
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - db_data:/var/lib/postgresql/data

  app:
    image: myapp:latest
    ports:
      - "8000:8000"
    depends_on:
      - db
    environment:
      DATABASE_HOST: db
      DATABASE_NAME: ${POSTGRES_DB}
      DATABASE_USER: ${POSTGRES_USER}
      DATABASE_PASSWORD: ${POSTGRES_PASSWORD}
      DATABASE_PORT: 5432

volumes:
  db_data:

در این مثال، سرویس db و app هر دو از متغیرهای محیطی که از فایل .env بارگذاری شده‌اند، استفاده می‌کنند. این روش امنیت را افزایش می‌دهد و تغییر اعتبارنامه‌ها را بسیار آسان می‌کند.

2. مدیریت کلیدهای API و توکن‌ها

بسیاری از برنامه‌ها از APIهای خارجی برای خدماتی مانند پرداخت، ارسال ایمیل یا سرویس‌های ابری استفاده می‌کنند. این APIها معمولاً نیاز به کلیدهای اختصاصی دارند که باید به صورت امن مدیریت شوند.

.env (با استفاده از .gitignore برای جلوگیری از commit شدن):

STRIPE_SECRET_KEY=sk_test_XXXXXXXXXXXXXXXXXXXXXXXX
SENDGRID_API_KEY=SG.YYYYYYYYYYYYYYYYYYYYYYYYYY

docker-compose.yml:

version: '3.8'
services:
  app:
    image: myapp:latest
    ports:
      - "8000:8000"
    environment:
      STRIPE_KEY: ${STRIPE_SECRET_KEY}
      EMAIL_SERVICE_KEY: ${SENDGRID_API_KEY}
      # سایر تنظیمات

با این روش، کلیدهای API هرگز به مخزن Git راه پیدا نمی‌کنند و در یک مکان مرکزی و امن نگهداری می‌شوند.

3. پیکربندی پورت‌های دینامیک

گاهی اوقات ممکن است بخواهید پورت‌های Exposed برای یک سرویس را به صورت دینامیک تعیین کنید، به خصوص اگر چندین توسعه‌دهنده به صورت همزمان روی یک پروژه کار می‌کنند و نیاز به پورت‌های مختلفی دارند تا با یکدیگر تداخل نداشته باشند.

.env:

APP_PORT_HOST=8001 # پورت برای دسترسی از هاست
APP_PORT_CONTAINER=8000 # پورت داخلی کانتینر

docker-compose.yml:

version: '3.8'
services:
  app:
    image: myapp:latest
    ports:
      - "${APP_PORT_HOST}:${APP_PORT_CONTAINER}"
    # ... سایر تنظیمات

این به توسعه‌دهندگان امکان می‌دهد تا هر یک پورت APP_PORT_HOST را به دلخواه خود در فایل .env محلی خود تغییر دهند.

4. تمایز بین محیط‌های توسعه و تولید

یکی از قدرتمندترین کاربردهای .env، مدیریت پیکربندی‌های مختلف برای محیط‌های توسعه (Development) و تولید (Production) است.

روش 1: استفاده از فایل‌های .env جداگانه با --env-file

.env.development:

DEBUG_MODE=true
DB_HOST=localhost
LOG_LEVEL=debug

.env.production:

DEBUG_MODE=false
DB_HOST=my_production_db_server.com
LOG_LEVEL=info

docker-compose.yml:

version: '3.8'
services:
  app:
    image: myapp:latest
    environment:
      DEBUG: ${DEBUG_MODE}
      DATABASE_HOST: ${DB_HOST}
      LOGGING_LEVEL: ${LOG_LEVEL}

برای توسعه:

docker-compose --env-file .env.development up

برای تولید:

docker-compose --env-file .env.production up

این روش کاملاً شفاف و قابل مدیریت است.

روش 2: استفاده از چندین فایل Compose و .env

شما می‌توانید فایل‌های docker-compose.yml را نیز برای محیط‌های مختلف جدا کنید و از ترکیب آن‌ها بهره ببرید.

docker-compose.yml (تنظیمات پایه):

version: '3.8'
services:
  app:
    image: myapp:latest
    ports:
      - "8000:8000"
    environment:
      COMMON_SETTING: value
      DB_USER: ${DB_USER} # از .env پروژه

docker-compose.override.yml (تنظیمات توسعه):

version: '3.8'
services:
  app:
    environment:
      DEBUG_MODE: "true"
      DB_HOST: localhost
    volumes:
      - ./app:/app # برای Hot-reloading در توسعه

docker-compose.prod.yml (تنظیمات تولید):

version: '3.8'
services:
  app:
    environment:
      DEBUG_MODE: "false"
      DB_HOST: production_db_host
    deploy: # تنظیمات استقرار برای تولید
      replicas: 3

.env.development:

DB_USER=dev_user
DB_PASSWORD=dev_pass

.env.production:

DB_USER=prod_user
DB_PASSWORD=prod_pass

برای توسعه:

docker-compose -f docker-compose.yml -f docker-compose.override.yml --env-file .env.development up

برای تولید:

docker-compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.production up

این رویکرد به شما امکان می‌دهد تا یک فایل پایه داشته باشید و سپس با فایل‌های override، تنظیمات محیطی خاص را اعمال کنید، در حالی که متغیرهای حساس از طریق .env مجزا مدیریت می‌شوند.

5. تنظیمات خاص برنامه (Application-Specific Settings)

متغیرهای محیطی فقط برای اعتبارنامه‌ها نیستند، بلکه برای هر تنظیماتی که ممکن است در طول زمان یا بین محیط‌ها تغییر کند، مفید هستند. مثلاً، تنظیمات مربوط به کش (caching)، سطوح لاگ‌برداری، یا آدرس سرویس‌های داخلی.

.env:

CACHE_ENABLED=true
CACHE_EXPIRATION_SECONDS=3600
LOGGER_LEVEL=INFO
SERVICE_A_URL=http://service_a:3000

docker-compose.yml:

version: '3.8'
services:
  app:
    image: myapp:latest
    environment:
      ENABLE_CACHE: ${CACHE_ENABLED}
      CACHE_TIMEOUT: ${CACHE_EXPIRATION_SECONDS}
      LOG_LEVEL: ${LOGGER_LEVEL}
      SERVICE_ENDPOINT_A: ${SERVICE_A_URL}
  service_a:
    image: my_service_a:latest
    # ...

این رویکرد به شما امکان می‌دهد تا بدون نیاز به بازسازی ایمیج‌ها، تنظیمات برنامه را تغییر دهید.

بهترین شیوه‌ها و ملاحظات امنیتی

استفاده از متغیرهای محیطی و فایل .env در Docker Compose راه حلی قدرتمند برای مدیریت پیکربندی است، اما برای اطمینان از امنیت و پایداری، باید از بهترین شیوه‌ها پیروی کرد و ملاحظات امنیتی را در نظر داشت.

1. هرگز فایل .env را در سیستم کنترل نسخه (VCS) commit نکنید.

این مهمترین قانون است. فایل .env حاوی اطلاعات حساس و محیطی خاص شما است. commit کردن آن در Git (یا هر VCS دیگری) می‌تواند منجر به افشای اطلاعات محرمانه مانند رمز عبور، کلیدهای API و اعتبارنامه‌ها شود. همیشه مطمئن شوید که .env (یا هر فایل .env.* که حاوی اطلاعات حساس است) به فایل .gitignore شما اضافه شده است.

مثال .gitignore:

# Docker
.env
.env.development.local
.env.production.local

برای اشتراک‌گذاری متغیرهای محیطی عمومی و غیرحساس (مثل پورت‌ها یا نام‌های سرویس) با تیم، می‌توانید یک فایل .env.example ایجاد کنید و آن را commit کنید. این فایل شامل ساختار متغیرهای مورد نیاز با مقادیر نمونه یا placeholders است که توسعه‌دهندگان می‌توانند از آن برای ایجاد فایل .env محلی خود استفاده کنند.

2. از نام‌های توصیفی برای متغیرها استفاده کنید.

نام‌گذاری متغیرها باید واضح و گویا باشد تا هدف و کاربرد آن‌ها را به راحتی بتوان درک کرد. استفاده از پیشوندها (مانند DB_ برای متغیرهای پایگاه داده یا API_ برای کلیدهای API) می‌تواند به سازماندهی و خوانایی کمک کند.

مثال: DB_PASSWORD بهتر از PASSWORD است.

3. متغیرهای حساس و غیرحساس را جدا کنید (اختیاری).

برای پروژه‌های بسیار بزرگ یا حساس، ممکن است بخواهید متغیرهای بسیار حساس را (که حتی نباید در فایل .env در سیستم توسعه‌دهنده نگهداری شوند) از متغیرهای کمتر حساس جدا کنید. این کار می‌تواند با استفاده از مکانیسم‌های تزریق متغیر در زمان اجرا (مثلاً از طریق Secret Management System در Production) یا با استفاده از فایل‌های .env جداگانه که فقط در محیط‌های مورد نیاز استفاده می‌شوند، انجام شود.

4. اعتبارسنجی متغیرهای محیطی را انجام دهید.

برنامه شما باید قبل از شروع به کار، وجود و صحت متغیرهای محیطی ضروری را بررسی کند. اگر یک متغیر حیاتی تعریف نشده باشد، برنامه باید با یک پیام خطای واضح از کار بیفتد، نه اینکه با پیکربندی ناقص شروع به کار کند. بسیاری از فریمورک‌ها و کتابخانه‌ها ابزارهایی برای اعتبارسنجی متغیرهای محیطی ارائه می‌دهند.

5. برای محیط تولید، از راهکارهای مدیریت اسرار (Secret Management) پیشرفته‌تر استفاده کنید.

فایل .env یک راه حل عالی برای محیط‌های توسعه و تست است، اما برای محیط‌های تولید در مقیاس بزرگ و با نیازهای امنیتی بالا، ممکن است کافی نباشد. در محیط‌های Production، توصیه می‌شود از سیستم‌های مدیریت اسرار اختصاصی استفاده کنید که قابلیت‌های پیشرفته‌تری مانند رمزنگاری، چرخش خودکار (rotation)، حسابرسی (auditing) و کنترل دسترسی دقیق (granular access control) را ارائه می‌دهند. نمونه‌هایی از این سیستم‌ها عبارتند از:

  • Docker Secrets: برای Docker Swarm
  • Kubernetes Secrets: برای Kubernetes
  • Vault by HashiCorp: یک راهکار مستقل و قدرتمند برای مدیریت اسرار
  • AWS Secrets Manager / Azure Key Vault / Google Secret Manager: سرویس‌های ابری برای مدیریت اسرار

این ابزارها متغیرهای محیطی را در حافظه کانتینر در زمان اجرا به جای فایل‌های روی دیسک نگهداری می‌کنند و راه‌های امن‌تری برای تزریق اسرار فراهم می‌کنند.

6. از مقادیر پیش‌فرض استفاده کنید (Optional Defaults).

در docker-compose.yml می‌توانید از نحو ${VARIABLE:-default_value} استفاده کنید تا اگر یک متغیر محیطی تعریف نشده بود، یک مقدار پیش‌فرض به آن اختصاص داده شود. این کار می‌تواند برنامه شما را در برابر پیکربندی‌های ناقص مقاوم‌تر کند.

APP_ENV: ${APP_ENV:-development} # اگر APP_ENV تعریف نشده باشد، از 'development' استفاده کن

7. محدود کردن دسترسی به فایل .env.

در محیط‌های توسعه، مطمئن شوید که فایل .env شما دارای مجوزهای دسترسی مناسبی است (مثلاً chmod 600 .env در لینوکس) تا فقط کاربرانی که نیاز دارند بتوانند آن را بخوانند. این یک لایه امنیتی اضافی در سطح سیستم فایل فراهم می‌کند.

با رعایت این بهترین شیوه‌ها و ملاحظات امنیتی، می‌توانید از مزایای کامل متغیرهای محیطی و فایل .env در Docker Compose بهره‌مند شوید و در عین حال امنیت و پایداری برنامه‌های خود را حفظ کنید.

پیشرفته: ترکیب با Shell Scripting و Templating

تا اینجا به روش‌های استاندارد مدیریت متغیرهای محیطی در Docker Compose پرداختیم. اما در سناریوهای پیچیده‌تر، نیاز به انعطاف‌پذیری بیشتری در تولید یا مدیریت پیکربندی‌ها احساس می‌شود. اینجاست که ترکیب .env با Shell Scripting (اسکریپت‌نویسی پوسته) و ابزارهای Templating می‌تواند بسیار قدرتمند باشد.

1. تولید دینامیک فایل .env

گاهی اوقات، مقادیر برخی از متغیرهای محیطی ممکن است به صورت پویا در زمان استقرار تولید شوند. مثلاً، یک توکن امنیتی موقت یا یک رمز عبور تصادفی برای یک سرویس جدید. در چنین مواردی، می‌توانید از یک اسکریپت پوسته برای تولید فایل .env قبل از اجرای docker-compose up استفاده کنید.

مثال: تولید یک رمز عبور تصادفی برای پایگاه داده

generate_env.sh:

#!/bin/bash

# اگر فایل .env.local وجود ندارد، آن را ایجاد کن
if [ ! -f .env.local ]; then
  touch .env.local
fi

# بررسی کن آیا DB_PASSWORD از قبل تعریف شده است یا خیر
if grep -q "DB_PASSWORD=" .env.local; then
  echo "DB_PASSWORD already exists in .env.local, skipping generation."
else
  # تولید یک رمز عبور تصادفی
  RANDOM_PASSWORD=$(openssl rand -base64 12)
  echo "DB_PASSWORD=${RANDOM_PASSWORD}" >> .env.local
  echo "Generated new DB_PASSWORD."
fi

# می‌توانید سایر متغیرهای پیش‌فرض را نیز اینجا اضافه کنید
if ! grep -q "APP_PORT=" .env.local; then
  echo "APP_PORT=8000" >> .env.local
fi

سپس، می‌توانید اسکریپت را اجرا کرده و بعد docker-compose را با فایل تولید شده بالا بیاورید:

./generate_env.sh
docker-compose --env-file .env.local up -d

این رویکرد به ویژه در محیط‌های CI/CD (Continuous Integration/Continuous Deployment) برای تولید اسرار یک بار مصرف یا مدیریت نسخه‌سازی دینامیک ایمیج‌ها مفید است.

2. استفاده از ابزارهای Templating (مانند envsubst یا Jinja2)

در برخی موارد، ممکن است نیاز به ایجاد فایل‌های پیکربندی پیچیده‌تری داشته باشید که نه تنها از متغیرهای محیطی استفاده می‌کنند، بلکه شامل منطق شرطی یا حلقه‌ها نیز هستند. در این سناریوها، ابزارهای Templating می‌توانند بسیار مفید باشند.

envsubst یک ابزار استاندارد یونیکس است که متغیرهای پوسته را در یک رشته یا فایل جایگزین می‌کند. این ابزار به ویژه برای تولید فایل‌های پیکربندی سرویس‌ها (مانند فایل‌های Nginx یا Apache) بر اساس متغیرهای محیطی کانتینر مفید است.

مثال با envsubst:

فرض کنید یک فایل قالب برای Nginx دارید: nginx.conf.template

server {
    listen ${NGINX_PORT};
    server_name ${APP_DOMAIN};

    location / {
        proxy_pass http://${APP_SERVICE_HOST}:${APP_SERVICE_PORT};
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

شما می‌توانید یک سرویس Nginx در docker-compose.yml تعریف کنید که این فایل را در زمان اجرا تولید می‌کند:

version: '3.8'
services:
  nginx:
    image: nginx:latest
    ports:
      - "${NGINX_PORT}:${NGINX_PORT}"
    environment:
      NGINX_PORT: ${NGINX_PORT}
      APP_DOMAIN: ${APP_DOMAIN}
      APP_SERVICE_HOST: app
      APP_SERVICE_PORT: 8000
    volumes:
      - ./nginx.conf.template:/etc/nginx/nginx.conf.template:ro
      - ./create_nginx_conf.sh:/docker-entrypoint-initdb.d/create_nginx_conf.sh:ro
  app:
    image: myapp:latest
    environment:
      # ...

create_nginx_conf.sh (یک اسکریپت کوچک برای جایگزینی متغیرها):

#!/bin/sh
envsubst '$NGINX_PORT $APP_DOMAIN $APP_SERVICE_HOST $APP_SERVICE_PORT' < /etc/nginx/nginx.conf.template > /etc/nginx/conf.d/default.conf
exec nginx -g 'daemon off;'

در این مثال، اسکریپت create_nginx_conf.sh در زمان راه‌اندازی کانتینر Nginx اجرا می‌شود، متغیرهای محیطی را از قالب nginx.conf.template می‌خواند و فایل پیکربندی نهایی default.conf را ایجاد می‌کند.

3. منطق شرطی و ترکیب فایل‌های Compose

گاهی اوقات، برای محیط‌های مختلف (مثلاً توسعه و تولید)، تنها تغییر در متغیرها کافی نیست، بلکه ساختار سرویس‌ها نیز ممکن است تغییر کند. Docker Compose با قابلیت ترکیب چندین فایل docker-compose.yml این امکان را فراهم می‌کند. می‌توانید یک فایل پایه داشته باشید و سپس فایل‌های محیطی خاص را برای override کردن یا افزودن سرویس‌ها استفاده کنید.

docker-compose.yml (تنظیمات پایه):

version: '3.8'
services:
  app:
    image: myapp:latest
    environment:
      SHARED_VARIABLE: ${SHARED_VALUE}
      APP_DEBUG: ${DEBUG_MODE:-false} # پیش‌فرض false
  db:
    image: postgres:13
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}

docker-compose.dev.yml (فایل override برای توسعه):

version: '3.8'
services:
  app:
    ports:
      - "8000:8000"
    volumes:
      - ./app:/app # Hot-reloading
    environment:
      APP_DEBUG: "true" # override کردن DEBUG_MODE
  db:
    ports:
      - "5432:5432" # برای دسترسی از هاست به پایگاه داده توسعه

.env.dev:

SHARED_VALUE=dev-shared
DB_USER=dev_user
DB_PASSWORD=dev_secret

برای اجرای توسعه:

docker-compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.dev up

این رویکرد به شما امکان می‌دهد تا یک Base Configuration داشته باشید و سپس با ترکیب فایل‌های Compose و فایل‌های .env خاص، محیط‌های کاملاً متفاوتی را ایجاد کنید.

استفاده از Shell Scripting و Templating در کنار فایل .env به شما انعطاف‌پذیری فوق‌العاده‌ای در مدیریت پیچیده‌ترین سناریوهای پیکربندی Docker Compose می‌دهد. این روش‌ها به شما امکان می‌دهند تا اتوماسیون را افزایش داده و اطمینان حاصل کنید که استقرار شما در هر محیطی به درستی پیکربندی شده است، حتی زمانی که نیاز به تولید مقادیر دینامیک یا ساختارهای پیکربندی پیچیده‌تر دارید.

نتیجه‌گیری

در این مقاله جامع، به اهمیت حیاتی متغیرهای محیطی و فایل .env در Docker Compose برای مدیریت پیکربندی‌های پویا و امن پرداختیم. از درک مبانی متغیرهای محیطی و نیاز به جداسازی پیکربندی از کد در معماری‌های مدرن، تا بررسی عمیق فایل .env و روش‌های مختلف تزریق متغیرها، همه جنبه‌های این موضوع را پوشش دادیم.

ما آموختیم که چگونه فایل .env به عنوان یک ابزار قدرتمند، به توسعه‌دهندگان امکان می‌دهد تا اطلاعات حساس و تنظیمات محیطی را از docker-compose.yml و کد منبع جدا کنند. این جداسازی نه تنها امنیت را با جلوگیری از commit شدن اطلاعات محرمانه در سیستم کنترل نسخه افزایش می‌دهد، بلکه انعطاف‌پذیری بی‌سابقه‌ای را در مدیریت محیط‌های مختلف (توسعه، آزمایش، تولید) و بروزرسانی آسان پیکربندی‌ها فراهم می‌کند.

با بررسی سناریوهای کاربردی مانند مدیریت اعتبارنامه‌های پایگاه داده، کلیدهای API، پورت‌های دینامیک و تمایز بین محیط‌های مختلف، دیدیم که چگونه این ابزارها به حل چالش‌های روزمره در توسعه و استقرار برنامه‌های چند کانتینری کمک می‌کنند. علاوه بر این، با بحث در مورد بهترین شیوه‌ها و ملاحظات امنیتی، بر اهمیت عدم commit کردن فایل .env، اعتبارسنجی متغیرها و استفاده از سیستم‌های مدیریت اسرار پیشرفته‌تر برای محیط‌های تولید تأکید کردیم.

در نهایت، با ورود به مباحث پیشرفته‌تر نظیر ترکیب Shell Scripting و ابزارهای Templating با .env، نشان دادیم که چگونه می‌توان در سناریوهای پیچیده‌تر، فرآیند تولید و مدیریت پیکربندی را به صورت کاملاً دینامیک و خودکار انجام داد. این روش‌ها به ویژه برای پروژه‌های بزرگ، محیط‌های CI/CD و نیاز به سفارشی‌سازی عمیق مفید هستند.

مدیریت پیکربندی یکی از ستون‌های اصلی توسعه و استقرار نرم‌افزار مدرن است. با تسلط بر استفاده از متغیرهای محیطی و فایل .env در Docker Compose، شما گامی بزرگ در جهت ساخت برنامه‌های مقاوم، امن، قابل نگهداری و مقیاس‌پذیر برمی‌دارید. این مهارت‌ها نه تنها بهره‌وری شما را افزایش می‌دهند، بلکه اطمینان می‌دهند که برنامه‌های شما در هر محیطی، به درستی و با امنیت کامل اجرا خواهند شد. توصیه می‌شود این اصول را در تمامی پروژه‌های Docker Compose خود به کار گیرید تا از مزایای کامل آن‌ها بهره‌مند شوید.

“تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”

قیمت اصلی 2.290.000 ریال بود.قیمت فعلی 1.590.000 ریال است.

"تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"

"با شرکت در این دوره جامع و کاربردی، به راحتی مهارت‌های برنامه‌نویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر می‌سازد تا به سرعت الگوریتم‌های پیچیده را درک کرده و اپلیکیشن‌های هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفه‌ای و امکان دانلود و تماشای آنلاین."

ویژگی‌های کلیدی:

بدون نیاز به تجربه قبلی برنامه‌نویسی

زیرنویس فارسی با ترجمه حرفه‌ای

۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان