مقالات › ابزار و DevOps

آموزش Docker برای توسعه‌دهندگان ‎.NET؛ کانتینر کردن ASP.NET Core و SQL Server

Docker را از دید یک توسعه‌دهنده‌ی دات‌نت یاد بگیرید: Image و Container، Dockerfile چندمرحله‌ای برای ASP.NET Core، docker compose همراه با SQL Server، Volume، پیکربندی و نکات Production.

با Docker دیگر «روی سیستم من کار می‌کند» بهانه نیست. یک کانتینر برنامه‌ی شما را دقیقاً همراه با Runtime و وابستگی‌هایی که لازم دارد بسته‌بندی می‌کند تا روی لپ‌تاپ شما، سیستم هم‌تیمی‌تان، سرور CI و Production یکسان اجرا شود. این راهنما Docker را از دید یک توسعه‌دهنده‌ی دات‌نت توضیح می‌دهد و با یک پیکربندی کامل ASP.NET Core + SQL Server تمام می‌شود.

مفاهیم اصلی در پنج دقیقه

  • Image — یک قالب فقط‌خواندنی: لایه‌ی سیستم‌عامل + Runtime دات‌نت + برنامه‌ی Publish‌شده‌ی شما. از روی یک Dockerfile ساخته می‌شود.
  • Container — نمونه‌ی در حال اجرای یک Image که از پروسه‌های دیگر ایزوله است.
  • Registry — مخزنی برای Imageها (Docker Hub، GitHub Container Registry یا یک Registry خصوصی).
  • Volume — فضای ذخیره‌سازی پایدار که با Restart کانتینر از بین نمی‌رود (دیتابیس، فایل‌های آپلودی).
  • Network — اجازه می‌دهد کانتینرها با نام با هم ارتباط برقرار کنند.

Imageها به‌صورت لایه‌ای ساخته می‌شوند. Docker هر لایه را کش می‌کند تا مراحل بدون تغییر تکرار نشوند؛ به همین دلیل ترتیب دستورها در Dockerfile مهم است.

Dockerfile چندمرحله‌ای برای ASP.NET Core

# ---------- مرحله‌ی Build ----------
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src

# ۱) اول فایل‌های پروژه را کپی و Restore کنید — تا تغییر وابستگی‌ها کش می‌ماند
COPY Portfolio.sln ./
COPY src/Portfolio.Api/Portfolio.Api.csproj src/Portfolio.Api/
COPY src/Portfolio.Application/Portfolio.Application.csproj src/Portfolio.Application/
COPY src/Portfolio.Infrastructure/Portfolio.Infrastructure.csproj src/Portfolio.Infrastructure/
RUN dotnet restore src/Portfolio.Api/Portfolio.Api.csproj

# ۲) بقیه را کپی و Publish کنید
COPY . .
RUN dotnet publish src/Portfolio.Api/Portfolio.Api.csproj -c Release -o /app/publish /p:UseAppHost=false

# ---------- مرحله‌ی اجرا ----------
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .

ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
USER app
ENTRYPOINT ["dotnet", "Portfolio.Api.dll"]

چرا دو مرحله؟

  • Image مربوط به SDK (حدود ۸۰۰ مگابایت) کامپایلرهایی دارد که فقط برای Build لازم‌اند.
  • Image مربوط به Runtime ASP.NET (حدود ۲۲۰ مگابایت یا با نسخه‌های Alpine/Chiseled خیلی کوچک‌تر) فقط چیزهایی را دارد که برای اجرا لازم است.
  • Image نهایی فقط Runtime و خروجی Publish شما را دارد: کوچک‌تر، سریع‌تر در انتشار و با سطح حمله‌ی کمتر.

USER app پروسه را با یک کاربر غیر root اجرا می‌کند (در Imageهای ‎.NET 8 به بعد موجود است)؛ یک بهبود امنیتی ساده.

یک فایل .dockerignore اضافه کنید تا Context ساخت کوچک بماند و Secretها وارد Image نشوند:

bin/
obj/
.git/
.vs/
node_modules/
**/appsettings.Development.json
*.user

ساخت و اجرا

docker build -t portfolio-api:1.0 .
docker run -d --name api -p 5000:8080 -e ASPNETCORE_ENVIRONMENT=Production portfolio-api:1.0
docker logs -f api

-p 5000:8080 پورت ۵۰۰۰ سیستم شما را به پورت ۸۰۸۰ داخل کانتینر نگاشت می‌کند.

کانتینر کردن بدون Dockerfile

از ‎.NET 8 به بعد، خود SDK می‌تواند مستقیم Image بسازد:

dotnet publish src/Portfolio.Api -c Release /t:PublishContainer -p ContainerImageTags=1.0

برای سرویس‌های ساده عالی است؛ Dockerfile برای Buildهای پیچیده کنترل بیشتری می‌دهد.

docker compose؛ API و SQL Server با هم

services:
  db:
    image: mcr.microsoft.com/mssql/server:2022-latest
    environment:
      ACCEPT_EULA: "Y"
      MSSQL_SA_PASSWORD: "${DB_PASSWORD}"
    ports:
      - "1433:1433"
    volumes:
      - sqldata:/var/opt/mssql
    healthcheck:
      test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P $$MSSQL_SA_PASSWORD -Q 'SELECT 1' || exit 1"]
      interval: 10s
      retries: 10

  api:
    build: .
    ports:
      - "5000:8080"
    environment:
      ASPNETCORE_ENVIRONMENT: Production
      ConnectionStrings__Default: "Server=db;Database=Portfolio;User Id=sa;Password=${DB_PASSWORD};TrustServerCertificate=True"
      Jwt__Key: "${JWT_KEY}"
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - uploads:/app/wwwroot/uploads

volumes:
  sqldata:
  uploads:
docker compose up -d --build
docker compose logs -f api
docker compose down          # توقف (Volumeها حفظ می‌شوند)

نکات کلیدی:

  • کانتینرها با نام سرویس به هم دسترسی دارند: Connection String از Server=db استفاده می‌کند.
  • پیکربندی از متغیرهای محیطی (با __ برای کلیدهای تودرتو) می‌آید و Secretها از فایل .env که هرگز Commit نمی‌شود.
  • Volumeها دیتابیس و فایل‌های آپلودی را هنگام ساخت دوباره‌ی کانتینرها حفظ می‌کنند.
  • Health Check همراه با depends_on: condition: service_healthy منتظر می‌ماند تا SQL Server واقعاً آماده شود.

اعمال Migrationهای EF Core

گزینه‌ها از ساده‌ترین تا مقاوم‌ترین:

  1. صدا زدن db.Database.Migrate() در شروع برنامه (برای برنامه‌های تک‌نمونه‌ای قابل قبول است).
  2. اجرای یک کانتینر یک‌باره: docker compose run --rm api dotnet Portfolio.Api.dll --migrate.
  3. تولید یک اسکریپت SQL Idempotent یا Migration Bundle در CI و اجرای آن به‌عنوان یک مرحله‌ی انتشار.

نکات گردش کار توسعه

  • حتی اگر API را از IDE اجرا می‌کنید، برای وابستگی‌ها (SQL Server، Redis، Seq) از Docker استفاده کنید؛ دیگر نیازی به نصب محلی نیست.
  • docker compose -f docker-compose.yml -f docker-compose.override.yml تنظیمات توسعه و Production را جدا می‌کند.
  • مرتب پاک‌سازی کنید: docker system prune (با احتیاط — بخوانید چه چیزی حذف می‌شود).

چک‌لیست Production

  • Build چندمرحله‌ای، Image اجرای کوچک و کاربر غیر root
  • .dockerignore که Secretها و خروجی‌های Build را حذف کند
  • پیکربندی و Secretها از طریق متغیر محیطی یا مخزن Secret
  • Health Check (app.MapHealthChecks("/health")) که Orchestrator از آن استفاده کند
  • نوشتن لاگ‌ها در stdout (جمع‌آوری توسط Docker یا Kubernetes)
  • داده‌ی پایدار در Volume یا سرویس مدیریت‌شده، هرگز داخل کانتینر
  • تگ نسخه‌دار برای Image (1.4.2)، نه فقط latest
  • قرار گرفتن پشت یک Reverse Proxy (Nginx، Traefik یا Load Balancer ابری) که HTTPS را مدیریت کند

وقتی پشت Proxy اجرا می‌کنید، Forwarded Headers را فعال کنید تا ASP.NET Core پروتکل اصلی و IP کاربر را بداند:

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});

جمع‌بندی

Docker برنامه‌ی دات‌نت و Runtime آن را در یک Image قابل حمل بسته‌بندی می‌کند. از Dockerfile چندمرحله‌ای برای Build با SDK و اجرا روی Image سبک ASP.NET استفاده کنید، کل محیط — API، SQL Server، Volumeها و شبکه — را با docker compose توصیف کنید، پیکربندی را از طریق متغیرهای محیطی تزریق کنید و یک چک‌لیست کوتاه Production را رعایت کنید. نتیجه: محیط‌های یکسان و تکرارپذیر از توسعه تا Production.

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

آیا برای انتشار ASP.NET Core به Docker نیاز دارم؟

نه. IIS، سرویس‌های لینوکس و Azure App Service بدون کانتینر هم کار می‌کنند. Docker وقتی می‌درخشد که محیط تکرارپذیر، چند سرویس یا Kubernetes می‌خواهید.

آیا دیتابیس در Production هم داخل کانتینر اجرا شود؟

برای پروژه‌های کوچک ممکن است. خیلی از تیم‌ها برای پشتیبان‌گیری، کارایی و اطمینان، دیتابیس مدیریت‌شده یا یک سرور اختصاصی را ترجیح می‌دهند.

کانتینر ویندوزی یا لینوکسی برای دات‌نت؟

برای ASP.NET Core از کانتینر لینوکسی استفاده کنید؛ کوچک‌تر، سریع‌تر و پرپشتیبانی‌تر است. کانتینرهای ویندوزی بیشتر برای برنامه‌های قدیمی ‎.NET Framework هستند.