Docker طی سالهای اخیر به یکی از مهمترین ابزارهای توسعه و استقرار نرمافزار تبدیل شده است. نیاز به ساخت محیطهای قابلاعتماد، سریع و یکسان بین سیستم توسعه و محیط نهایی، بسیاری از تیمها را به سمت معماری مبتنی بر کانتینر هدایت کرده است. در پروژههای .NET این اهمیت چند برابر میشود، زیرا معمولاً از چند سرویس مختلف شامل API، دیتابیس، کش، سرویس پیام و ابزارهای جانبی تشکیل شدهاند و هماهنگکردن همه این اجزا در حالت معمولی میتواند پیچیده و زمانبر باشد.
کانتینرها به توسعهدهنده این امکان را میدهند که برنامه را همراه با تمام وابستگیهایش در یک بسته استاندارد قرار دهد؛ بستهای که میتوان آن را روی هر ماشین، در هر محیط و روی هر سیستمعاملی به طور کاملاً یکسان اجرا کرد. این موضوع باعث میشود تفاوتهای محیطی، نسخههای متفاوت ابزارها و تنظیمات ناهمسان در روند توسعه و اجرا از میان برداشته شوند.
در محیط توسعه، Docker کمک میکند کل سیستم با یک دستور بالا بیاید و هر سرویس دقیقاً با همان پیکربندی مورد نیاز اجرا شود. در محیط عملیاتی نیز، مدیریت نسخهها، بازتولید محیطها، اجرای تستها و حتی استقرار سرویسها سادهتر و قابل پیشبینیتر میشود.
هدف این مقاله ارائه یک مسیر عملی و واقعی برای استفاده از Docker در پروژههای .NET است. از ایجاد یک WebAPI ساده شروع میکنیم، سپس آن را Dockerize میکنیم، دیتابیس SQL Server را داخل Docker بالا میآوریم و در نهایت دو سرویس را در یک شبکه مشترک به هم متصل میکنیم تا یک سیستم کامل در محیط توسعه اجرا شود. تمرکز اصلی روی یک سناریوی واقعی در محیط لوکال است؛ جایی که توسعهدهنده باید بهسرعت سرویسها را بالا بیاورد، تست کند و بدون پیچیدگی از آنها استفاده کند.
شروع کار با پروژه WebAPI
برای شروع، یک پروژه .NET WebAPI ایجاد میشود. این پروژه پایه اجرا در Docker خواهد بود و ابتدا در حالت عادی روی سیستم میزبان اجرا میشود تا از صحت عملکرد آن مطمئن شویم.
dotnet new webapi -n DemoApi
cd DemoApi
برای اطمینان از عملکرد صحیح پروژه، یک بار آن را به صورت معمول اجرا میکنیم:
dotnet run
اگر همهچیز درست اجرا شده باشد، صفحهی Swagger در آدرس پیشفرض API در دسترس است. در این مرحله پروژه از نظر منطقی آماده است و میتوان آن را به دنیای کانتینر منتقل کرد.
Dockerize کردن WebAPI با Dockerfile چندمرحلهای
برای اجرای WebAPI داخل کانتینر، در ریشهی پروژه یک فایل Dockerfile ایجاد میشود.
این فایل مشخص میکند Image نهایی چگونه ساخته شود و از چه مراحل و پایهای استفاده کند. بهترین
روش در محیطهای واقعی استفاده از Multi-stage build است؛ در این روش، مرحلهی build
و publish از مرحلهی runtime جدا میشود و Image نهایی سبکتر و مناسب اجرا خواهد بود.
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY *.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 8080
ENV ASPNETCORE_URLS=http://0.0.0.0:8080
ENTRYPOINT ["dotnet", "DemoApi.dll"]
در این ساختار، مرحلهی اول سورس را بازیابی، وابستگیها را restore کرده و خروجی publish تولید میکند. مرحلهی دوم تنها شامل فایلهای منتشرشده است و بر پایهی image runtime سبکتر اجرا میشود. پورت ۸۰۸۰ به عنوان پورت سرویس انتخاب شده و با متغیر ASPNETCORE_URLS داخل کانتینر در دسترس قرار گرفته است.
ساخت Image و اجرای کانتینر API
پس از آماده شدن Dockerfile، Image پروژه ساخته میشود:
docker build -t demo-api .
سپس میتوان کانتینر را از روی این Image اجرا کرد:
docker run -p 8080:8080 demo-api
در این حالت سرویس شما در آدرس زیر در دسترس خواهد بود:
http://localhost:8080/swagger
تا اینجا WebAPI با تنظیمات مشخصشده در Dockerfile داخل یک کانتینر اجرا میشود و از طریق پورت map شده روی سیستم میزبان قابل دسترسی است.
اضافهکردن SQL Server و اجرای سیستم با Docker Compose
در گام بعدی، پایگاه داده نیز وارد ماجرا میشود تا یک محیط کاملتر برای توسعه محلی فراهم گردد. برای هماهنگکردن چند سرویس در کنار هم، از Docker Compose استفاده میشود.
در کنار پروژه، یک فایل به نام docker-compose.yml ایجاد میشود:
version: '3.9'
services:
db:
image: mcr.microsoft.com/mssql/server:2022-latest
container_name: demo-sql
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=StrongPass@123
ports:
- "1433:1433"
volumes:
- sql_data:/var/opt/mssql
api:
build: .
container_name: demo-api
depends_on:
- db
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ConnectionStrings__Default=Server=db,1433;Database=DemoDb;User Id=sa;Password=StrongPass@123;
ports:
- "8080:8080"
volumes:
sql_data:
- سرویس db روی پورت ۱۴۳۳ هاست map شده است.
- API روی شبکه داخلی Docker میتواند با hostname برابر db به SQL Server وصل شود.
- Volume دادهها را پایدار نگه میدارد.
پیکربندی Connection String در برنامه
برای اینکه برنامه بتواند ConnectionString را دریافت کند، ابتدا در appsettings.json یک مقدار پیشفرض تعریف میشود.
{
"ConnectionStrings": {
"Default": "Server=(localdb)\\\\MSSQLLocalDB;Database=DemoDb;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
سپس در Program.cs مقدار خوانده و استفاده میشود:
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("Default");
builder.Services.AddDbContext(options =>
{
options.UseSqlServer(connectionString);
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
زمانی که برنامه داخل کانتینر اجرا میشود، مقدار پیشفرض ConnectionString توسط متغیر محیطی ConnectionStrings__Default که در docker-compose.yml تنظیم شده است override میشود.
اجرای سیستم کامل در محیط لوکال
برای بالا آوردن کل سیستم:
docker compose up -d
مشاهده وضعیت سرویسها:
docker compose ps
مشاهده لاگهای WebAPI:
docker compose logs -f api
ورود به کانتینر:
docker exec -it demo-api bash
همچنین به دلیل map شدن پورت ۱۴۳۳ روی هاست، از خود ویندوز نیز میتوان به SQL Server داخل Docker متصل شد:
Server=localhost,1433;User Id=sa;Password=StrongPass@123;
جمعبندی
کار با Docker در پروژههای .NET نه تنها یک انتخاب مدرن است، بلکه یک مسیر طبیعی برای ایجاد محیطهای پایدار، قابلاعتماد و قابلتکرار در چرخه توسعه محسوب میشود.
در این مقاله، جریان کامل اجرای یک پروژه .NET در محیط Docker بررسی شد: از ایجاد WebAPI و Dockerize کردن آن، تا راهاندازی SQL Server و اتصال هر دو سرویس در یک شبکه داخلی از طریق Docker Compose.
ساختاری که ایجاد شد پایهای قدرتمند برای توسعه مستقل، تستهای یکپارچه، ساخت CI/CD و حتی استقرار در محیطهای بزرگتر است.
این مسیر، اولین قدم در استفاده جدی از کانتینرهاست و میتواند مبنایی برای گامهای بعدی باشد؛ از جمله اضافهکردن سرویسهای دیگر، بهبود شبکهبندی، مدیریت پیشرفته تنظیمات و حرکت به سمت Kubernetes.