Skip to Content

چگونه یک اپلیکیشن را Dockerize کنیم؟ از Contract اجرا تا Image امن و آزمون Production

اپلیکیشن را با contract اجرا، Dockerfile چندمرحله‌ای، user غیرroot، config بیرونی، healthcheck، volume، secret، CI و آزمون امن Dockerize کنید.

نویسنده مدیر کل تاریخ انتشار
این پست را به اشتراک بگذارید

Dockerize کردن یعنی تعریف یک build تکرارپذیر و runtime قابل‌جایگزینی؛ نه فقط نوشتن FROM و اجرای برنامه با root. قبل از Dockerfile باید معلوم باشد برنامه چگونه start و stop می‌شود، به چه config و dependency نیاز دارد، کجا state می‌نویسد و چه زمانی واقعاً آماده دریافت ترافیک است.

پاسخ سریع: contract اجرا و dependencyها را inventory کنید، build و runtime را جدا و context را کوچک کنید، نسخه base/dependency را pin و process را non-root اجرا کنید. config و secret را بیرون image، داده را در storage ماندگار و log را روی stdout/stderr قرار دهید. سپس build، vulnerability، signal، health، Backup و rollback را در CI و staging آزمون کنید.

مرحله صفر: آیا Docker انتخاب درستی است؟

مشکل موردنظر را مشخص کنید: dependency conflict، تفاوت محیط، release غیرقابل‌تکرار یا جداسازی سرویس؟ اگر پاسخ فقط «مدرن شدن» است، هزینه network، storage و patch image توجیه نشده است. راهنمای تصمیم استفاده از Docker معیارهای انتخاب را توضیح می‌دهد.

Application Contract را بنویسید

  • فرمان start و exit code
  • port و protocol ورودی
  • environment/config لازم
  • dependencyهای network و startup
  • مسیرهای read/write و داده ماندگار
  • health/readiness و shutdown behavior
  • migration و rollback داده

اگر برنامه فقط با SSH و تغییر دستی بالا می‌آید، ابتدا آن رفتار را قابل‌اسکریپت و idempotent کنید. container نباید برای start به terminal تعاملی یا دانلود مبهم از اینترنت وابسته باشد.

Build Context را کوچک کنید

Docker daemon/buildkit فایل‌های context را برای build می‌خواند. repository بزرگ، backup، secret و dependency محلی نباید بی‌دلیل وارد context شوند. .dockerignore را مانند مرز امنیت و performance ببینید؛ اما صرف ignore کردن secret افشاشده، rotation آن را بی‌نیاز نمی‌کند.

.git
.env
*.log
tmp/
backups/
node_modules/

این فقط نمونه است و باید با stack تطبیق داده شود؛ ممکن است بعضی مسیرها برای build شما لازم باشند. قبل از استفاده مطمئن شوید فایل ضروری حذف نشده و الگوی wildcard secret موردنیاز runtime را وارد image نمی‌کند.

Base Image را آگاهانه انتخاب کنید

نسخه runtime، معماری CPU، کتابخانه native، timezone و certificate CA را در نظر بگیرید. image بسیار مینیمال اگر ابزار debug و compatibility لازم را حذف کند، عملیات را دشوار می‌کند. image بزرگ و نامحدود نیز سطح package و انتقال را بالا می‌برد. منبع رسمی/معتبر و چرخه patch مهم‌تر از کوچک‌ترین اندازه است.

Tag و Digest

tag برای خوانایی مفید است، اما می‌تواند به محتوای دیگری جابه‌جا شود. build و promotion production باید digest artifact را ثبت کند. pin کامل digest نیز نیازمند bot یا فرایند update است؛ ثابت ماندن همیشگی base image یعنی patchهای امنیتی دریافت نمی‌شوند.

Dependencyها را تکرارپذیر نصب کنید

lockfile و نسخه package manager را وارد build کنید و مرحله dependency را طوری بچینید که cache قابل‌استفاده باشد. دانلود «آخرین نسخه» در هر build خروجی را غیرقابل‌تکرار می‌کند. credential registry خصوصی را با build secret بدهید، نه ARG یا layer دائمی.

Multi-stage Build

مرحله build compiler و ابزار توسعه را دارد و مرحله runtime فقط artifact لازم را دریافت می‌کند. این کار اندازه و سطح ابزار runtime را کم می‌کند. مرز COPY --from باید دقیق باشد تا cache، source یا secret build وارد image نهایی نشود. artifact و library native را در architecture مقصد آزمون کنید.

ترتیب Layerها

فایل‌های کم‌تغییر مانند manifest dependency را پیش از source پرتغییر کپی کنید تا cache build بهتر شود. چند فرمان بی‌ارتباط را فقط برای کم‌کردن layer به یک shell پیچیده تبدیل نکنید؛ خوانایی، exit-on-error و cache مهم‌اند. فایل موقت نصب باید در همان مرحله و به روش package manager مدیریت شود.

User غیرroot

process runtime را با UID/GID مشخص و دسترسی حداقلی اجرا کنید. فقط مسیرهای write لازم را مالک کنید؛ کل filesystem را writable یا 777 نکنید. port پایین، package install یا تغییر host نباید در runtime نیاز باشد. root داخل container با root host یکسان نیست، اما همچنان ریسک escape و mount را افزایش می‌دهد.

Filesystem تا حد امکان Read-only

کد و binary نباید در runtime تغییر کنند. temp، upload و cache لازم را در مسیر مشخص و با quota مدیریت کنید. read-only root filesystem تغییر ناخواسته را آشکار می‌کند، ولی باید ابتدا تمام writeهای واقعی برنامه را inventory کنید تا production غافلگیر نشود.

یک Process یا چند Process؟

قاعده مهم، یک lifecycle و مسئولیت قابل مدیریت است؛ نه منع مطلق چند process. وب و worker با scale/restart متفاوت معمولاً container جدا می‌خواهند. init و signal handling برای child processها مهم است. supervisor داخل container اگر لازم باشد باید health و shutdown روشن داشته باشد.

ENTRYPOINT و CMD

فرمان باید signal را به process اصلی برساند و quoting آن قابل‌پیش‌بینی باشد. shell wrapper باید در خطا exit code درست بدهد و child را با روش مناسب جایگزین کند. migration طولانی را کور در هر replica startup اجرا نکنید؛ concurrency و rollback migration باید طراحی شود.

Graceful Shutdown

برنامه باید signal توقف را بگیرد، پذیرش request تازه را قطع و کار جاری را در مهلت محدود تمام کند. اگر فوراً kill شود، request، queue یا file write نیمه‌تمام می‌ماند. termination grace با زمان واقعی shutdown هماهنگ و در staging آزمون شود.

Config و Secret بیرون Image

image یکسان باید در محیط‌ها با config متفاوت اجرا شود. secret را در Dockerfile، layer، Git یا log قرار ندهید. environment variable همیشه محرمانه نیست و ممکن است در inspect یا crash report دیده شود. secret file/store با permission، rotation و audit متناسب انتخاب کنید.

Port و Network

برنامه داخل container روی interface مناسب گوش دهد، اما فقط port لازم روی host publish شود. دیتابیس و cache معمولاً network داخلی می‌خواهند. localhost داخل container به container دیگر اشاره نمی‌کند. DNS نام سرویس را استفاده کنید و IP container را hard-code نکنید.

State و Volume

آپلود، دیتابیس و artifact تولیدشده که باید بمانند از writable layer جدا شوند. owner و permission volume باید با user runtime سازگار باشد. mount کردن کل host یا socket runtime دسترسی بزرگی می‌دهد. volume باید inventory، Backup، encryption و Restore Test داشته باشد.

Log استاندارد

برنامه log را روی stdout/stderr ساختاریافته و بدون secret بدهد؛ collector مسئول انتقال و retention باشد. multiline stack trace و request ID باید قابل پردازش باشند. rotation driver ضروری است تا Disk host پر نشود. log file داخلی بدون volume/collector با حذف container از بین می‌رود.

Healthcheck درست

healthcheck باید سریع، timeoutدار و کم‌هزینه باشد. liveness فقط توان ادامه process را می‌سنجد؛ readiness آمادگی دریافت ترافیک را. وابسته کردن liveness به API بیرونی می‌تواند هنگام خرابی آن، همه replicaها را restart کند. failure mode را عمداً تست کنید.

Resource Limit

CPU و memory limit دامنه خرابی را محدود می‌کنند، اما مقدار تصادفی OOM یا throttling می‌سازد. baseline و load test لازم است. مجموع limitها و ظرفیت host را ببینید. event OOM و throttling باید مانیتور شود؛ restart loop درمان کمبود ظرفیت نیست.

Build و Run محلی کنترل‌شده

docker build -t example-app:test .
docker image inspect example-app:test
docker run --rm example-app:test

نام image نمونه است و اجرای واقعی احتمالاً port، config، network و secret لازم دارد. secret را روی command line یا history قرار ندهید. اجرای بدون وابستگی باید failure واضح و exit code درست بدهد، نه اینکه بی‌صدا با config production پیش‌فرض بالا بیاید.

آزمون Image

  • شروع با config معتبر و شکست واضح با config ناقص
  • اجرای non-root و permission مسیرها
  • signal و graceful shutdown
  • health/readiness و dependency failure
  • عدم وجود secret و ابزار build در image نهایی
  • vulnerability و license dependency
  • حجم، معماری و reproducibility

Compose برای Integration Test

برنامه، دیتابیس و cache را با network و volume موقت در CI بالا بیاورید. readiness را به sleep ثابت ترجیح دهید. test data نباید به production متصل شود. بعد از آزمون، resourceها را با نام project محدود پاک‌سازی کنید؛ فرمان حذف گسترده volume روی host مشترک خطرناک است.

Migration دیتابیس

migration باید job کنترل‌شده، backup و forward/backward compatibility داشته باشد. اجرای هم‌زمان توسط همه replicaها race می‌سازد. rollback image پس از schema ناسازگار ممکن نیست. expand/contract و rollout مرحله‌ای برای تغییر بزرگ مناسب‌تر است.

CI/CD و Registry

image را یک‌بار در CI بسازید، test و scan کنید و همان digest را به staging و production promote کنید. build دوباره برای production ممکن است dependency دیگری بگیرد. registry باید access control، retention و artifact لازم برای rollback داشته باشد.

Production Readiness

  1. image digest و manifest release ثبت شده است.
  2. secret، network و volume تعریف شده‌اند.
  3. Backup و Restore داده آزموده است.
  4. health، log، metric و Alert فعال‌اند.
  5. resource limit و ظرفیت با load test سنجیده شده‌اند.
  6. rollback کد و migration تمرین شده است.
  7. Runbook deploy و incident مالک دارد.

راهنمای مانیتورینگ سرور سیگنال‌های host، container و تجربه کاربر را برای production مشخص می‌کند.

اشتباه‌های رایج

  • اجرای root و mount گسترده host
  • secret در Dockerfile یا env commitشده
  • latest در production
  • داده در writable layer
  • نبود graceful shutdown
  • healthcheck وابسته به همه سرویس‌ها
  • migration در startup همه replicaها
  • build جداگانه برای هر محیط

چه زمانی کمک تخصصی لازم است؟

اگر اپلیکیشن stateful، چند runtime یا migration حساس دارد، یک Dockerfile ظاهراً working برای production کافی نیست. داکرایز اپلیکیشن می‌تواند image، user، secret، volume، health، CI و rollout قابل بازگشت را یکپارچه طراحی کند.

پرسش‌های متداول

آیا هر سرویس باید container جدا باشد؟

سرویس‌هایی با lifecycle، scale و failure مستقل معمولاً جدا هستند؛ معیار مسئولیت عملیاتی است، نه قانون شکلی.

آیا .env امن است؟

خیر به‌صورت ذاتی؛ ممکن است commit یا inspect شود. permission و secret management لازم است.

چرا برنامه در container به localhost دیتابیس وصل نمی‌شود؟

localhost همان container است؛ برای دیتابیس جدا از نام سرویس و network مشترک استفاده کنید.

آیا image را روی سرور production بسازیم؟

بهتر است artifact یک‌بار در CI ساخته، آزموده و همان digest promote شود تا production ابزار build و خروجی متفاوت نداشته باشد.

Docker چیست و چه زمانی واقعاً به آن نیاز داریم؟ راهنمای تصمیم بدون پیچیدگی اضافه
Docker اپلیکیشن و وابستگی‌ها را در image قابل‌تکرار بسته‌بندی می‌کند؛ مزایا، محدودیت‌ها و معیار انتخاب آن برای تیم، Production و MVP را بررسی کنید.