اپلیکیشن روی لپتاپ شما بدون مشکل اجرا میشود، اما هنگام انتقال به سرور، نسخه Runtime، کتابخانههای سیستمی یا فرمان شروع برنامه مسئلهساز میشوند. داکرفایل برای حل همین ناهماهنگی نوشته میشود: یک فایل متنی که مراحل ساخت محیط اجرای اپلیکیشن را بهشکل قابلتکرار تعریف میکند.
با یک Dockerfile درست، اعضای تیم و سامانه استقرار میتوانند از سورس یکسان، Image مشابهی بسازند. البته این فایل بهتنهایی مدیریت Secret، دیسک پایدار، دامنه یا پایش سرویس را حل نمیکند. در ادامه، ساختار داکرفایل را میشناسید و چند نمونه را میسازید، آزمایش میکنید و برای محیط واقعی بهبود میدهید.
پاسخ سریع: داکرفایل چیست و از کجا شروع کنیم؟
Dockerfile فایلی متنی و معمولاً بدون پسوند است که به Docker میگوید Image اپلیکیشن را چگونه بسازد. این فایل Base Image، فایلهای ورودی، روش نصب وابستگیها، پورت برنامه و فرمان اجرای کانتینر را مشخص میکند.
یک داکرفایل حداقلی برای Node.js میتواند چنین باشد:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
این نمونه وقتی درست است که پروژه فایل Lock معتبر داشته باشد، server.js نقطه شروع برنامه باشد و سرویس داخل کانتینر روی پورت 3000 و آدرس 0.0.0.0 گوش دهد. فایل را از روی ساختار واقعی پروژه بنویسید، سپس Image را بسازید و همان Image را محلی اجرا کنید.
مستندات رسمی Docker نیز Dockerfile را ورودی ساخت Image تعریف میکنند و نام پیشفرض Dockerfile را برای فایل اصلی پروژه پیشنهاد میدهند: مرور Dockerfile در مستندات Docker.
پیش از نوشتن Dockerfile این مدل ذهنی را داشته باشید
در جریان کار Docker، چهار مفهوم به هم مرتبطاند:
|
مفهوم |
وظیفه |
|
سورس پروژه |
کد، فایل Lock و تنظیمات لازم برای Build |
|
Dockerfile |
دستور ساخت محیط اجرای پروژه |
|
Image |
خروجی ثابت و قابلانتقال Build |
|
Container |
نمونه در حال اجرای یک Image |
Dockerfile خود برنامه در حال اجرا نیست. اجرای docker build از روی آن یک Image میسازد و docker run از روی Image یک کانتینر ایجاد میکند.
هر دستور اصلی معمولاً یک لایه از Image میسازد. اگر لایهای تغییر کند، Docker آن لایه و لایههای بعدی را دوباره میسازد. پس ترتیب COPY و RUN مستقیماً روی Build Cache اثر دارد. جزئیات این رفتار در راهنمای Build Cache داکر آمده است.
اگر هنوز تفاوت Image، Container و ماشین مجازی برایتان روشن نیست، مقاله «کانتینر یا ماشین مجازی؟ تفاوتها و انتخاب درست» معیارهای انتخاب میان این دو مدل را توضیح میدهد.
دستورهای اصلی Dockerfile چه کاری انجام میدهند؟
برای شروع لازم نیست تمام دستورهای Dockerfile را حفظ کنید. موارد زیر نیازهای یک اپلیکیشن وب معمولی را پوشش میدهند.
FROM: انتخاب نقطه شروع
FROM node:22-alpine
هر مرحله Build با FROM آغاز میشود. Base Image باید Runtime و کتابخانههای موردنیاز برنامه را فراهم کند.
تگهایی مانند alpine حجم کمتری دارند، اما همیشه انتخاب مناسبتری نیستند. برخی پکیجهای باینری با کتابخانههای Alpine سازگار نیستند و به ابزار کامپایل یا Base Image دیگری نیاز دارند. برای چنین پروژههایی، یک Image مبتنی بر Debian Slim گاهی دردسر کمتری دارد.
استفاده از latest نتیجه Build را به نسخهای متغیر وابسته میکند. دستکم نسخه اصلی Runtime را مشخص کنید. اگر بازتولید دقیق Image مهم است، Base Image را با Digest قفل کنید و برای بهروزرسانی آن فرایند مشخصی داشته باشید.
WORKDIR: تعیین پوشه کاری
WORKDIR /app
دستورهای بعدی مانند RUN، COPY و CMD نسبت به این مسیر اجرا میشوند. استفاده از WORKDIR زنجیرههای مبهمی مانند cd /app && ... را حذف میکند.
COPY: انتقال فایلها به Image
COPY package*.json ./
COPY . .
COPY فقط به فایلهای داخل Build Context دسترسی دارد. نقطه انتهای فرمان زیر، پوشه فعلی را بهعنوان Context در اختیار Build میگذارد:
docker build -t example-app:local .
این دستور Image را با تگ example-app:local میسازد. اجرای دوباره، Image قبلی را حذف نمیکند؛ تگ را به خروجی جدید منتقل میکند و ممکن است لایههای Cache را دوباره به کار بگیرد.
RUN: اجرای کارهای زمان Build
RUN npm ci --omit=dev
RUN هنگام ساخت Image اجرا میشود، نه هنگام شروع کانتینر. نصب پکیج، کامپایل و تولید فایلهای خروجی معمولاً در این مرحله قرار میگیرند.
ENV و ARG: تنظیم Build و Runtime
ARG برای پارامترهای زمان Build است و ENV مقدار پیشفرض متغیر محیطی داخل کانتینر را تعریف میکند. هیچکدام محل امنی برای Token و رمز نیستند. Docker توصیه میکند Secretهای Build را با Secret Mount در اختیار مرحله لازم قرار دهید، زیرا ARG یا ENV ممکن است در تاریخچه یا اطلاعات Image باقی بمانند: مدیریت Build Secret در Docker.
مقادیر حساس Runtime را از سامانه استقرار یا Secret Manager تزریق کنید. فایل .env واقعی را داخل Image کپی نکنید.
EXPOSE: اعلام پورت داخلی برنامه
EXPOSE 3000
EXPOSE پورت را روی میزبان منتشر نمیکند؛ فقط اعلام میکند برنامه روی آن پورت گوش میدهد. برای تست محلی باید نگاشت پورت را هنگام اجرا تعیین کنید:
docker run --rm -p 127.0.0.1:3000:3000 example-app:local
این فرمان پورت کانتینر را فقط روی 127.0.0.1 میزبان منتشر میکند. گزینه --rm پس از توقف، کانتینر را حذف میکند، اما Image و سورس پروژه را تغییر نمیدهد. دادهای که فقط در لایه نوشتنی کانتینر ذخیره شده نیز از بین میرود.
CMD و ENTRYPOINT: تعیین فرایند اصلی
CMD ["node", "server.js"]
CMD فرمان پیشفرض کانتینر است و کاربر میتواند آن را هنگام docker run جایگزین کند. ENTRYPOINT بیشتر برای Imageهایی مناسب است که همیشه باید یک برنامه مشخص را اجرا کنند.
برای سرویس وب، فرم آرایهای یا Exec Form معمولاً روشنتر است:
CMD ["node", "server.js"]
این فرم برخلاف CMD node server.js یک Shell واسط ایجاد نمیکند و Signalها را مستقیمتر به فرایند اصلی میرساند.
نمونه آماده Dockerfile برای Node.js
فرض کنید ساختار پروژه چنین است:
example-app/
package.json ──├
package-lock.json ──├
server.js ──├
Dockerfile ──└
داکرفایل پیشنهادی:
FROM node:22-alpine
WORKDIR /app
COPY --chown=node:node package*.json ./
RUN npm ci --omit=dev
COPY --chown=node:node . .
USER node
EXPOSE 3000
CMD ["node", "server.js"]
ترتیب دو COPY عمدی است. تا زمانی که package.json و package-lock.json تغییر نکنند، Docker میتواند لایه نصب وابستگیها را از Cache بردارد. تغییر یک فایل سورس نباید نصب تمام پکیجها را تکرار کند.
USER node نیز برنامه را با کاربر از پیش تعریفشده Node اجرا میکند، نه root. اگر برنامه هنگام اجرا فایل مینویسد، مسیر آن را با مالکیت مناسب آماده کنید یا یک Volume در نظر بگیرید.
فایل .dockerignore را کنار Dockerfile قرار دهید:
.git
node_modules
npm-debug.log
.env
.env.*
Dockerfile*
README.md
coverage
این فایل مانع ارسال فایلهای غیرضروری به Build Context میشود. الگوها را کورکورانه کپی نکنید؛ اگر Build به فایلی نیاز دارد، آن را حذف نکنید.
حالا Image را بسازید:
docker build -t example-node-app:local .
باید تمام مراحل Build کامل شوند و Image با تگ انتخابی ساخته شود. اگر npm ci متوقف شد، سازگاری فایل Lock با package.json و دسترسی به Registry پکیجها را بررسی کنید.
سپس آن را اجرا کنید:
docker run --rm -p 127.0.0.1:3000:3000 example-node-app:local
برنامه باید از http://127.0.0.1:3000 در دسترس باشد. اگر کانتینر فعال است ولی پاسخی دریافت نمیکنید، بررسی کنید برنامه روی 0.0.0.0 گوش میدهد، نه فقط localhost داخل کانتینر.
نمونه آماده برای Python و Flask
در این نمونه، فایل requirements.txt باید شامل Flask و Gunicorn باشد و شیء Flask در فایل app.py با نام app تعریف شده باشد:
FROM python:3.13-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
RUN useradd --create-home appuser
COPY --chown=appuser:appuser . .
USER appuser
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]
این نمونه از Server توسعه Flask برای محیط عملیاتی استفاده نمیکند. اگر ماژول، متغیر برنامه یا پورت پروژه متفاوت است، مقدار app:app و فرمان شروع را تغییر دهید.
برای ساخت و اجرا:
docker build -t example-flask-app:local .
docker run --rm -p 127.0.0.1:8000:8000 example-flask-app:local
اگر Build موفق است ولی کانتینر بلافاصله متوقف میشود، لاگ کانتینر و مسیر ماژول را بررسی کنید.
نمونه Multi-stage برای اپلیکیشن Vite
ابزارهای Build فرانتاند نباید در Image نهایی باقی بمانند. Multi-stage Build یک محیط برای ساخت و محیط دیگری برای اجرا تعریف میکند:
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
مرحله اول وابستگیها را نصب و پوشه dist را تولید میکند. مرحله نهایی فقط فایلهای ساختهشده و Web Server را نگه میدارد؛ کامپایلر، سورس و node_modules وارد خروجی نمیشوند.
اگر Framework خروجی را در مسیری غیر از dist میسازد، مسیر COPY --from را تغییر دهید. برای Single-page Application نیز ممکن است به تنظیم Nginx برای بازگرداندن مسیرها به index.html نیاز داشته باشید.
Docker استفاده از Multi-stage Build را برای جداکردن ابزارهای ساخت از Runtime توصیه میکند: راهنمای Multi-stage Build.