یک اشتباه رایج در Dockerfile برای ASP.NET Core
یکی از اشتباههایی که هنگام Dockerize کردن برنامههای ASP.NET Core زیاد دیده میشود، استفاده از یک Image عمومی مثل Ubuntu و نصب کردن همه ابزارهای موردنیاز داخل آن است.
مثلاً:
FROM ubuntu
RUN apt-get update
RUN apt-get install ...
این روش لزوماً اشتباه نیست، اما برای یک ASP.NET Core Application معمولاً انتخاب مناسبی نیست.
چرا؟
چون برای اجرای یک برنامه ASP.NET Core نیازی نداریم تمام ابزارهای توسعه و Build را داخل Image نهایی داشته باشیم.
مشکل چیست؟
فرض کنید Application ما با .NET ساخته شده است.
برای Build کردن پروژه به ابزارهایی مثل .NET SDK نیاز داریم، اما بعد از اینکه Application Build شد، برای اجرای آن دیگر به SDK احتیاجی نداریم.
پس چرا SDK را داخل Production Image نگه داریم؟
اینجاست که Multi-stage Build در Docker کاربرد پیدا میکند.
Multi-stage Build چیست؟
در Multi-stage Build میتوانیم فرآیند Build و اجرای Application را از یکدیگر جدا کنیم.
در مرحله اول، از Image مربوط به .NET SDK استفاده میکنیم:
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app/publish
اینجا Image مربوط به SDK فقط وظیفه Build و Publish کردن Application را دارد.
بعد یک Stage جدید برای اجرای Application ایجاد میکنیم:
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApi.dll"]
در این مرحله دیگر SDK را همراه Application به Production نمیبریم.
در واقع:
Source Code
│
▼
┌─────────────────┐
│ .NET SDK │
│ Build │
└────────┬────────┘
│
│ dotnet publish
▼
Published App
│
▼
┌─────────────────┐
│ .NET ASP.NET │
│ Runtime │
└────────┬────────┘
│
▼
Production
SDK و Runtime چه تفاوتی دارند؟
به شکل ساده:
SDK
برای توسعه و Build کردن Application استفاده میشود.
مثلاً:
dotnet restore
dotnet build
dotnet test
dotnet publish
ASP.NET Core Runtime
برای اجرای Application استفاده میشود.
بنابراین در Production معمولاً لازم نیست SDK را همراه Application نگه داریم.
چرا Multi-stage Build بهتر است؟
1. Image نهایی کوچکتر میشود
Image مربوط به SDK ابزارهای زیادی برای توسعه و Build دارد.
وقتی فقط فایلهای Publish شده را به Runtime Image منتقل میکنیم، بخش زیادی از این ابزارها اصلاً وارد Image نهایی نمیشوند.
در نتیجه حجم Image میتواند کمتر شود.
2. Deployment تمیزتر میشود
Production Container فقط چیزهایی را دارد که برای اجرای Application لازم است.
این موضوع باعث میشود ساختار Deployment سادهتر و قابل مدیریتتر باشد.
3. Attack Surface کاهش پیدا میکند
هر ابزار و Package اضافی که داخل Production Image قرار میگیرد، بخشی از محیطی است که باید مدیریت و بهروزرسانی شود.
با جدا کردن Build Environment از Runtime Environment، چیزهای غیرضروری را از Production حذف میکنیم.
البته کوچکتر بودن Image به تنهایی به معنی امن بودن Container نیست، اما حذف ابزارهای غیرضروری یکی از قدمهای خوب برای کاهش Attack Surface است.
یک Dockerfile کاملتر برای ASP.NET Core
برای یک پروژه واقعی میتوانیم چیزی شبیه این داشته باشیم:
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApi.dll"]
نکته مهم اینجاست که:
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
برای Build است.
اما:
FROM mcr.microsoft.com/dotnet/aspnet:9.0
برای Runtime و اجرای Application است.
پس قرار نیست Image مربوط به SDK وارد Production شود.
یک نکته مهم
Multi-stage Build فقط مخصوص ASP.NET Core نیست.
همین الگو را میتوان برای بسیاری از Applicationهایی که فرآیند Build و Runtime متفاوتی دارند استفاده کرد.
ایده اصلی بسیار ساده است:
چیزی که برای Build لازم داریم، لزوماً نباید در Production وجود داشته باشد.
اگر Dockerfile پروژه ASP.NET Core شما یک Image سنگین دارد، یکی از اولین چیزهایی که باید بررسی کنید همین موضوع است.
Build Environment را از Runtime Environment جدا کنید. 🐳



