Docker Compose چیست؟ تعریف ساده و کاربرد آن روی سرور
Docker Compose چند کانتینر را با شبکه و ولومهایشان در یک فایل YAML تعریف میکند. با یک مثال وباپ و Postgres، کلیدهای اصلی و زمانی که لازمش ندارید را ببینید.
Docker Compose چیست؟
Docker Compose ابزاری است که چند کانتینر را در یک فایل YAML تعریف میکند و همه را با هم اجرا و متوقف میکند. در این فایل مینویسید هر کانتینر از کدام ایمیج ساخته شود، به کدام شبکه وصل باشد و دادهاش را کجا نگه دارد. بعد کل مجموعه را مثل یک واحد مدیریت میکنید: یک فرمان همه را بالا میآورد و یک فرمان همه را پایین میبرد.
کانتینر (container) یک برنامهی بستهبندیشده است که با کتابخانهها و تنظیماتش، جدا از بقیهی سیستم اجرا میشود. ایمیج (image) قالب فقطخواندنی همان کانتینر است. YAML یک قالب متنی ساده برای نوشتن تنظیمات است که ساختارش را با تورفتگی خطها نشان میدهد. خود Compose کانتینری اجرا نمیکند. این کار را Docker Engine انجام میدهد. Compose فقط فایل شما را میخواند و آن را به درخواستهایی برای Docker تبدیل میکند.
این صفحه تعریف است، نه آموزش نصب. اگر میخواهید همین حالا روی سرور خودتان یک stack را قدمبهقدم بالا بیاورید، آموزش عملی Docker Compose روی VPS را بخوانید. اینجا یاد میگیرید هر بخش فایل چه معنایی دارد تا آن آموزش برایتان روشن باشد.
Docker Compose چه مشکلی را حل میکند؟
بیشتر برنامههای واقعی فقط یک کانتینر نیستند. یک وباپ معمولی دستکم به یک پایگاه داده نیاز دارد. گاهی یک Redis برای کش هم اضافه میشود. گاهی هم یک reverse proxy (پراکسی معکوس، یعنی برنامهای که درخواستهای بیرونی را به سرویس درست میرساند). هر کدام از اینها یک کانتینر جداست.
بدون Compose باید هر کانتینر را جدا اجرا و تنظیم کنید. باید یادتان بماند کدام پورت را باز کردهاید، کدام متغیر محیطی را دادهاید و کدام پوشه را به کدام کانتینر وصل کردهاید. شبکهی مشترک را هم باید دستی بسازید تا وباپ بتواند پایگاه داده را پیدا کند. همهی این تنظیمات فقط در تاریخچهی ترمینال شما میماند. اگر سرور را عوض کنید یا همکارتان بخواهد همان محیط را بسازد، باید همه را از حافظه بازسازی کنید.
Compose این دانستهها را به یک فایل متنی منتقل میکند. اسم رایج این فایل compose.yaml است و اسم قدیمیتر docker-compose.yml هم هنوز شناخته میشود. چون فایل متنی است، میتوانید آن را در git نگه دارید، تغییراتش را ببینید و روی سرور دیگری دوباره از آن استفاده کنید. نتیجه این است که محیط شما قابل تکرار میشود، چون تعریفش در یک جا نوشته شده و به حافظهی کسی وابسته نیست.
«یک واحد» دقیقاً یعنی چه؟
Compose به این مجموعه میگوید project (پروژه). اسم پروژه بهطور پیشفرض اسم پوشهای است که فایل در آن قرار دارد. Compose کانتینرها، شبکهها و ولومها را با برچسب این پروژه علامت میزند. به همین دلیل وقتی پروژه را پایین میآورید، Compose میداند کدام کانتینرها مال همین پروژهاند و به کانتینرهای دیگر سرور دست نمیزند.
یک فایل compose نمونه: وباپ و Postgres
فایل زیر دو سرویس دارد. db یک پایگاه دادهی PostgreSQL است. web ابزار Adminer است، یک رابط وب کوچک برای مدیریت پایگاه داده. Adminer را انتخاب کردهایم چون یک وباپ واقعی است که به Postgres وصل میشود و ایمیج آماده دارد. شما میتوانید برنامهی خودتان را به جای آن تصور کنید. این فایل برای توضیح است. نسخهی قابل اجرا، همراه با همهی قدمها، در آموزش عملی آمده است.
services:
web:
image: adminer:4.8.1
ports:
- "8080:8080"
environment:
ADMINER_DEFAULT_SERVER: db
depends_on:
db:
condition: service_healthy
networks:
- backend
restart: unless-stopped
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: change-me
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 3s
retries: 5
networks:
- backend
restart: unless-stopped
networks:
backend:
volumes:
pgdata:این فایل سه کلید سطح بالا (top-level) دارد: services، networks و volumes. هر چیز دیگری زیر یکی از این سه قرار میگیرد. در ادامه هر کدام را جدا میبینیم.
کلید services: هر کانتینر چه باشد
هر مدخل زیر services یک سرویس است. سرویس یعنی یک نوع کانتینر با تنظیمات مشخص. در این فایل اسم سرویسها web و db است. این اسمها را خودتان انتخاب میکنید.
imageمیگوید کانتینر از کدام ایمیج ساخته شود.postgres:16یعنی ایمیج رسمی Postgres با برچسب (tag)16. برچسب، نسخه را ثابت نگه میدارد. اگر برچسب ننویسید، Docker برچسبlatestرا برمیدارد و این برچسب با هر انتشار تازه عوض میشود.portsیک پورت سرور را به یک پورت داخل کانتینر وصل میکند."8080:8080"یعنی درخواستی که به پورت 8080 سرور میرسد به پورت 8080 کانتینرwebفرستاده میشود. عدد سمت چپ مال سرور است و عدد سمت راست مال کانتینر.environmentمتغیرهای محیطی را به کانتینر میدهد. ایمیج Postgres باPOSTGRES_PASSWORDرمز کاربر را تنظیم میکند. Adminer باADMINER_DEFAULT_SERVERمیفهمد بهطور پیشفرض به کدام سرور وصل شود.depends_onترتیب شروع را تعیین میکند. باcondition: service_healthy، Compose کانتینرwebرا تا وقتیdbسالم گزارش نشده شروع نمیکند.healthcheckمیگوید سلامت کانتینر چطور سنجیده شود. اینجاpg_isreadyاز خود Postgres میپرسد که آمادهی پذیرش اتصال هست یا نه.restart: unless-stoppedبه Docker میگوید اگر کانتینر از کار افتاد یا خود Docker دوباره شروع شد، کانتینر را دوباره اجرا کند. استثنا وقتی است که خودتان آن را متوقف کرده باشید.
چرا depends_on بهتنهایی کافی نیست؟ بدون شرط سلامت، Compose فقط صبر میکند تا کانتینر db شروع شود. شروع شدن کانتینر با آماده بودن Postgres یکی نیست. Postgres چند ثانیه وقت لازم دارد تا فایلهایش را آماده کند، پس وباپی که زودتر وصل شود با خطای اتصال روبهرو میشود. جزئیات این بررسی را در راهاندازی healthcheck برای Postgres در Docker Compose ببینید.
یک نکته دربارهی رمز: در این مثال رمز را مستقیم در فایل نوشتهایم تا مثال کوتاه بماند. در پروژهی واقعی این کار را نکنید، چون فایل compose معمولاً در git میرود و هر کس به مخزن دسترسی داشته باشد رمز را میبیند. راه درست را در نگهداری رمزها با فایل env و secrets در Docker Compose توضیح دادهایم.
کلید networks: کانتینرها چطور همدیگر را پیدا میکنند
کلید networks در سطح بالا، شبکهای به اسم backend تعریف میکند. هر سرویس با networks خودش به این شبکه وصل میشود. کانتینرهایی که روی یک شبکه هستند میتوانند با هم ارتباط بگیرند.
مهمترین نکتهی این بخش این است: روی این شبکه، اسم هر سرویس یک نام میزبان (hostname) است. Docker یک DNS (سامانهی نام دامنه) داخلی دارد که اسم db را به آدرس IP کانتینر Postgres ترجمه میکند. برای همین Adminer با مقدار db به پایگاه داده میرسد و لازم نیست آدرس IP را بدانید. آدرس IP کانتینر ممکن است با هر بار ساخت دوباره عوض شود، ولی اسم سرویس ثابت میماند.
اگر هیچ شبکهای ننویسید، Compose خودش یک شبکهی پیشفرض برای پروژه میسازد و همهی سرویسها را به آن وصل میکند. در این مثال شبکه را صریح نوشتهایم تا کلید را ببینید. وقتی چند شبکه لازم دارید، تعریف صریح ضروری میشود. مثلاً وقتی میخواهید پایگاه داده از پراکسی جلویی جدا بماند. این موضوع در توضیح کامل شبکهها در Docker Compose آمده است.
دقت کنید که سرویس db بخش ports ندارد. پس پورت 5432 روی سرور باز نشده و Postgres از اینترنت در دسترس نیست. فقط کانتینرهای همان شبکه به آن میرسند. این یکی از سادهترین تصمیمهای امنیتی در یک فایل compose است، و یکی از مفیدترینها.
کلید volumes: داده کجا میماند
کانتینر موقت است. وقتی کانتینر حذف میشود، هر چیزی که در لایهی قابلنوشتن آن ذخیره شده هم حذف میشود. پایگاه داده نمیتواند اینطور کار کند.
ولوم (volume) فضای ذخیرهای است که بیرون از چرخهی عمر کانتینر باقی میماند. کلید volumes در سطح بالا یک ولوم نامدار (named volume) به اسم pgdata تعریف میکند. خط pgdata:/var/lib/postgresql/data زیر سرویس db این ولوم را به پوشهای وصل میکند که Postgres دادههایش را در آن مینویسد. حالا اگر کانتینر db را حذف کنید و دوباره بسازید، جدولها و رکوردها سر جایشان هستند، چون داده در ولوم بوده و نه در خود کانتینر.
نوع دیگری هم وجود دارد: bind mount، یعنی وصل کردن یک پوشهی مشخص از سرور به کانتینر. فرق این دو و اینکه کدام برای پشتیبانگیری راحتتر است را در مقایسهی bind mount و ولوم نامدار در Compose ببینید.
یک خطر رایج هم هست. فرمان پایین آوردن پروژه بهطور پیشفرض ولومهای نامدار را نگه میدارد، ولی گزینهای دارد که آنها را هم پاک میکند. فرق متوقف کردن، پایین آوردن و پاک کردن ولوم را در تفاوت docker compose down و docker compose stop توضیح دادهایم. پیش از اینکه برای اولین بار پروژهای با دادهی واقعی را پاک کنید، آن را بخوانید.
docker-compose یا docker compose: کدام درست است؟
بسیاری از آموزشهای فارسی قدیمی هنوز docker-compose را با خط تیره مینویسند. آن ابزار نسخهی ۱ Compose است. برنامهای جدا بود که با Python نوشته شده بود و جدا نصب میشد. Docker از ژوئیهی ۲۰۲۳ دیگر برای آن بهروزرسانی منتشر نمیکند، پس هیچ وصلهی امنیتی تازهای هم برایش نمیآید.
نسخهی فعلی، Compose v2 است. این نسخه با زبان Go نوشته شده و بهصورت یک افزونه (plugin) داخل خود فرمان docker اجرا میشود. برای همین به جای docker-compose up مینویسید docker compose up، یعنی با فاصله و بدون خط تیره. وقتی Docker را از مخزن رسمی خودش نصب میکنید، بستهی docker-compose-plugin همراه آن میآید. فایل compose بین این دو نسخه تقریباً یکی است، ولی فرمان و روش نصب فرق دارد. آموزشی که هنوز میگوید docker-compose را با pip یا با دانلود یک فایل اجرایی جدا نصب کنید، شما را به نسخهای بدون پشتیبانی میبرد.
اینکه آیا خود Compose منسوخ شده، سؤال دیگری است و جوابش منفی است. فقط نسخهی ۱ کنار گذاشته شده است. استدلال کامل را در آیا Docker Compose منسوخ شده است؟ آوردهایم و اینجا تکرارش نمیکنیم.
آیا هنوز باید کلید version را بنویسم؟
نه. فایلهای قدیمی با خطی مثل version: "3.8" شروع میشوند. در دورهی نسخهی ۱، این عدد تعیین میکرد فایل با کدام قالب خوانده شود. امروز Compose از Compose Specification پیروی میکند. این مشخصه یک قالب واحد است و کلید version را منسوخ (obsolete) میداند، یعنی Compose از آن برای انتخاب قالب استفاده نمیکند. فایل نمونهی بالا هم به همین دلیل خط version ندارد.
اگر فایلی را از یک آموزش قدیمی کپی کردهاید، کافی است خط version را پاک کنید. بقیهی فایل معمولاً بدون تغییر کار میکند.
Docker Compose با docker run چه فرقی دارد؟
docker run یک کانتینر را با گزینههایی اجرا میکند که همان لحظه در خط فرمان مینویسید. هر چیزی که در فایل نمونه دیدیم، یعنی پورت، متغیر محیطی، ولوم، شبکه و سیاست ریاستارت، یک گزینهی معادل در docker run دارد. فرق اصلی در جای نگهداری تنظیمات است: با docker run تنظیمات در فرمانی است که تایپ کردهاید و با Compose در فایلی است که ذخیره کردهاید. برای یک کانتینر آزمایشی که یک بار اجرا میشود، docker run کافی است. وقتی دو کانتینر یا بیشتر باید با هم کار کنند و میخواهید فردا همان محیط را دوباره بسازید، فایل compose کار را سادهتر میکند.
ایمیجها از کجا میآیند و چرا روی سرور داخل ایران مشکل پیش میآید؟
فایل compose ایمیج را نمیسازد. فقط اسم آن را میآورد. وقتی پروژه را بالا میآورید و ایمیجی روی سرور نیست، Docker آن را از یک رجیستری (registry، یعنی مخزن ایمیجها) دانلود میکند. به این مرحله pull میگویند. رجیستری پیشفرض Docker Hub است. ایمیجهای postgres:16 و adminer:4.8.1 در فایل نمونه هر دو از Docker Hub میآیند، چون اسم رجیستری دیگری جلوی آنها نوشته نشده است.
خوانندهای که سرورش داخل ایران است باید این را بداند: Docker Hub به دلیل قوانین کنترل صادرات آمریکا، درخواستهایی را که از IPهای ایران میآیند رد میکند. پس فایلی که روی یک سرور خارج از ایران بدون مشکل اجرا میشود، ممکن است روی سرور داخل ایران در مرحلهی pull شکست بخورد. در این حالت فایل compose شما ایرادی ندارد. مشکل در دسترسی سرور به رجیستری است.
چه وقت به Docker Compose نیاز ندارید؟
Compose ابزار خوبی است، ولی همهجا لازم نیست. این چند حالت را در نظر بگیرید.
- یک کانتینر تنها. اگر فقط یک سرویس دارید که به چیز دیگری وصل نیست،
docker runکافی است. با این حال بسیاری از مدیران سرور حتی برای یک کانتینر هم فایل compose نگه میدارند، چون تنظیمات را ثبت میکند. - برنامهای که اصلاً در کانتینر اجرا نمیشود. برای یک برنامهی تکفایلی که با systemd اجرا میشود، اضافه کردن Docker فقط یک لایهی دیگر برای نگهداری میسازد.
- چند سرور. Compose روی یک میزبان کار میکند. کانتینرها را بین چند سرور پخش نمیکند و اگر سرور از کار بیفتد، آنها را جای دیگری اجرا نمیکند. ابزارهای ارکستراسیون (orchestration) مثل Kubernetes برای همین کار ساخته شدهاند.
- پایگاه دادهای که ترجیح میدهید مستقیم روی سرور باشد. گذاشتن Postgres در کانتینر برای همه انتخاب درستی نیست. مزایا و معایبش را در پایگاه داده در Docker یا مستقیم روی سرور بررسی کردهایم.
قدم بعدی: اجرای واقعی روی VPS
حالا معنای هر بخش فایل را میدانید. برای اجرای واقعی یک stack روی VPS، از نصب Docker تا بالا آوردن و بررسی سرویسها، راهنمای قدمبهقدم Docker Compose روی سرور مجازی را دنبال کنید. وقتی فرمانها را یاد گرفتید، فهرست سریع فرمانهای Docker Compose را برای مراجعهی روزانه کنار دستتان نگه دارید.
FAQ: پرسشهای رایج دربارهی Docker Compose
Docker Compose به زبان ساده چیست؟
Docker Compose ابزاری است که چند کانتینر را همراه با شبکهها و ولومهایشان در یک فایل YAML تعریف میکند. با این فایل، همهی کانتینرهای یک برنامه، مثلاً یک وباپ و پایگاه دادهاش، با یک فرمان با هم بالا میآیند و با یک فرمان با هم پایین میروند. چون تعریف در یک فایل متنی است، میتوانید همان محیط را روی سرور دیگری دوباره بسازید.
آیا Docker Compose جای Docker را میگیرد؟
نه. Compose به Docker Engine نیاز دارد و بدون آن کاری انجام نمیدهد. Docker Engine کانتینرها را میسازد و اجرا میکند. Compose فقط فایل شما را میخواند و به Docker میگوید چه چیزی، با چه تنظیماتی و به چه ترتیبی ساخته شود.
فرق docker-compose با خط تیره و docker compose با فاصله چیست؟
docker-compose نسخهی ۱ و قدیمی Compose است که با Python نوشته شده بود و از ژوئیهی ۲۰۲۳ بهروزرسانی نمیگیرد. docker compose نسخهی ۲ است که با Go نوشته شده و بهصورت افزونه داخل فرمان docker اجرا میشود. اگر Docker را از مخزن رسمی نصب کنید، افزونه با بستهی docker-compose-plugin میآید. آموزشهای تازه باید از docker compose استفاده کنند.
آیا باید خط version را در فایل compose بنویسم؟
نه. Compose Specification، یعنی مشخصهای که Compose امروزی از آن پیروی میکند، کلید version را منسوخ میداند و Compose از آن برای انتخاب قالب فایل استفاده نمیکند. اگر فایل قدیمیتان با version: "3.8" یا عددی شبیه آن شروع میشود، آن خط را پاک کنید. بقیهی فایل معمولاً بدون تغییر کار میکند.
چرا فایل compose من روی سرور خارج کار میکند ولی روی سرور داخل ایران نه؟
ایمیجهای یک فایل compose از یک رجیستری دانلود میشوند و رجیستری پیشفرض Docker Hub است. Docker Hub به دلیل قوانین کنترل صادرات آمریکا درخواستهای IPهای ایران را رد میکند. برای همین همان فایل، روی سرور داخل ایران ممکن است در مرحلهی دانلود ایمیج (pull) شکست بخورد. ایراد از خود فایل نیست، بلکه از دسترسی سرور به رجیستری است.