У попередніх статтях ми вивчили фундаментальні концепції Docker: образи, контейнери, Dockerfile, кешування, реєстри. Ми розглядали приклади на різних технологіях, але тепер настав час зосередитися на тому, що найбільше цікавить .NET розробників — контейнеризація C# додатків.
Microsoft активно підтримує Docker та контейнеризацію .NET. Офіційні образи .NET на Microsoft Container Registry (MCR) оптимізовані, регулярно оновлюються та покривають всі сценарії: від розробки до production. У 2026 році контейнеризація .NET додатків — це не екзотика, а стандартна практика для розгортання на серверах, хмарних платформах (Azure, AWS, GCP) та Kubernetes кластерах.
У цій статті ми пройдемо повний цикл контейнеризації .NET додатків: від найпростішої консольної програми до повноцінного ASP.NET Core Web API з Swagger, health checks та production-ready конфігурацією. Ви навчитеся вибирати правильні базові образи, оптимізувати Dockerfile для швидкої збірки та мінімального розміру, налаштовувати порти та змінні оточення, та застосовувати найкращі практики безпеки.
Microsoft надає чотири основні типи образів для різних сценаріїв використання:
1. SDK (Software Development Kit)
mcr.microsoft.com/dotnet/sdk:8.0
Містить:
Розмір: ~700 МБ (Debian), ~500 МБ (Alpine)
Використання:
Приклад:
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet build -c Release
2. ASP.NET Core Runtime
mcr.microsoft.com/dotnet/aspnet:8.0
Містить:
Розмір: ~210 МБ (Debian), ~120 МБ (Alpine)
Використання:
Приклад:
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyWebApi.dll"]
3. .NET Runtime
mcr.microsoft.com/dotnet/runtime:8.0
Містить:
Розмір: ~190 МБ (Debian), ~110 МБ (Alpine)
Використання:
Приклад:
FROM mcr.microsoft.com/dotnet/runtime:8.0-alpine
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyConsoleApp.dll"]
4. Runtime Dependencies
mcr.microsoft.com/dotnet/runtime-deps:8.0
Містить:
Розмір: ~110 МБ (Debian), ~15 МБ (Alpine)
Використання:
Приклад:
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["./MyApp"]
| Образ | Розмір (Debian) | Розмір (Alpine) | SDK | Runtime | ASP.NET | Використання |
|---|---|---|---|---|---|---|
dotnet/sdk:8.0 | ~700 МБ | ~500 МБ | ✅ | ✅ | ✅ | Збірка, розробка |
dotnet/aspnet:8.0 | ~210 МБ | ~120 МБ | ❌ | ✅ | ✅ | Web API, MVC |
dotnet/runtime:8.0 | ~190 МБ | ~110 МБ | ❌ | ✅ | ❌ | Console, Worker |
dotnet/runtime-deps:8.0 | ~110 МБ | ~15 МБ | ❌ | ❌ | ❌ | Self-contained |
dotnet/sdk (потрібен компілятор)dotnet/aspnet (містить Kestrel)dotnet/runtime (без ASP.NET)dotnet/runtime-deps (мінімальний розмір)Формат тегів:
mcr.microsoft.com/dotnet/[тип]:[версія]-[варіант]
Приклади:
# Повна версія на Debian
mcr.microsoft.com/dotnet/sdk:8.0.3
# Мінорна версія (автоматично оновлюється до 8.0.x)
mcr.microsoft.com/dotnet/sdk:8.0
# Alpine варіант (мінімальний розмір)
mcr.microsoft.com/dotnet/sdk:8.0-alpine
# Ubuntu варіант
mcr.microsoft.com/dotnet/sdk:8.0-jammy
# Конкретна версія Alpine
mcr.microsoft.com/dotnet/sdk:8.0.3-alpine3.19
Рекомендації:
8.0 або 8.0-alpine (автоматичні оновлення патчів)8.0.3-alpine (конкретна версія для стабільності)8.0 (завжди остання патч-версія)Microsoft надає образи на базі трьох дистрибутивів Linux:
1. Debian (за замовчуванням)
FROM mcr.microsoft.com/dotnet/aspnet:8.0
# Базується на Debian 12 (Bookworm)
Переваги:
Недоліки:
Коли використовувати:
2. Alpine Linux
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
# Базується на Alpine Linux 3.19
Переваги:
Недоліки:
Коли використовувати:
3. Ubuntu
FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy
# Базується на Ubuntu 22.04 (Jammy Jellyfish)
Переваги:
Недоліки:
Коли використовувати:
ASP.NET Core 8.0 Runtime:
| Варіант | Розмір | Економія |
|---|---|---|
| Debian (за замовчуванням) | 210 МБ | - |
| Ubuntu (jammy) | 215 МБ | -5 МБ |
| Alpine | 120 МБ | +90 МБ (43%) |
Runtime Dependencies 8.0:
| Варіант | Розмір | Економія |
|---|---|---|
| Debian | 110 МБ | - |
| Alpine | 15 МБ | +95 МБ (86%) |
Alpine використовує musl libc замість стандартної glibc. Це може призвести до проблем:
1. Нативні бібліотеки (P/Invoke)
// Може не працювати на Alpine
[DllImport("libnative.so")]
private static extern int NativeFunction();
Рішення: Перекомпілювати нативну бібліотеку для musl або використовувати Debian.
2. Globalization
// Може працювати некоректно на Alpine
var culture = new CultureInfo("uk-UA");
var formatted = 1234.56.ToString("C", culture); // "1 234,56 ₴"
Рішення: Встановити icu-libs в Alpine або використовувати Invariant Mode.
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
# Встановити ICU для підтримки локалізації
RUN apk add --no-cache icu-libs
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
3. Time Zones
// Може не працювати на Alpine
var kyivTime = TimeZoneInfo.FindSystemTimeZoneById("Europe/Kiev");
Рішення: Встановити tzdata.
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
RUN apk add --no-cache tzdata
Для більшості .NET додатків:
# ✅ Рекомендовано: Alpine для production
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
Якщо є проблеми сумісності:
# ✅ Альтернатива: Debian для максимальної сумісності
FROM mcr.microsoft.com/dotnet/aspnet:8.0
Для мінімального розміру (self-contained):
# ✅ Мінімальний: Alpine runtime-deps
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine
Почнемо з найпростішого сценарію — консольний додаток, який виводить повідомлення.
Структура проєкту:
MyConsoleApp/
├── Program.cs
├── MyConsoleApp.csproj
└── Dockerfile
Program.cs:
Console.WriteLine("Hello from Docker!");
Console.WriteLine($"Current time: {DateTime.Now}");
Console.WriteLine($"Environment: {Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}");
// Симуляція роботи
await Task.Delay(2000);
Console.WriteLine("Application finished.");
MyConsoleApp.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
Dockerfile (неоптимізований):
FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /app
# Копіюємо весь код
COPY . .
# Збираємо додаток
RUN dotnet build -c Release
# Запускаємо
ENTRYPOINT ["dotnet", "run", "--no-build", "-c", "Release"]
Проблеми:
bin/ та obj/Dockerfile:
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# Копіюємо .csproj та робимо restore (кешується окремо)
COPY ["MyConsoleApp.csproj", "."]
RUN dotnet restore
# Копіюємо решту коду
COPY . .
# Збираємо та публікуємо
RUN dotnet publish -c Release -o /app/publish
# Stage 2: Runtime
FROM mcr.microsoft.com/dotnet/runtime:8.0-alpine
WORKDIR /app
# Копіюємо лише опубліковані файли з build stage
COPY --from=build /app/publish .
# Запускаємо
ENTRYPOINT ["dotnet", "MyConsoleApp.dll"]
.dockerignore:
**/bin/
**/obj/
**/.vs/
**/.vscode/
**/*.user
.git/
.gitignore
README.md
docker build -t myconsoleapp:1.0.0 .
docker run --rm myconsoleapp:1.0.0
Hello from Docker!
Current time: 14.04.2026 11:12:25
Environment: Production
Application finished.
# Передати змінну оточення
docker run --rm -e DOTNET_ENVIRONMENT=Development myconsoleapp:1.0.0
Вивід:
Hello from Docker!
Current time: 14.04.2026 11:12:25
Environment: Development
Application finished.
| Варіант | Розмір образу | Час збірки |
|---|---|---|
| Неоптимізований (SDK) | 720 МБ | 45 с |
| Multi-stage (runtime) | 195 МБ | 50 с |
| Multi-stage (runtime-alpine) | 125 МБ | 50 с |
Економія: 595 МБ (83%)!
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyConsoleApp.csproj", "."]
# Restore з persistent NuGet cache
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet restore
COPY . .
# Build з cache
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/runtime:8.0-alpine
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyConsoleApp.dll"]
Переваги:
Worker Service — це фоновий сервіс, який працює постійно (на відміну від консольного додатку, який завершується).
Program.cs:
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
var host = builder.Build();
await host.RunAsync();
Worker.cs:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(5000, stoppingToken);
}
}
}
Dockerfile (ідентичний до Console App):
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyWorker.csproj", "."]
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/runtime:8.0-alpine
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyWorker.dll"]
Запуск:
docker run --rm --name myworker myworker:1.0.0
Вивід (кожні 5 секунд):
info: MyWorker.Worker[0]
Worker running at: 14.04.2026 11:12:25 +00:00
info: MyWorker.Worker[0]
Worker running at: 14.04.2026 11:12:30 +00:00
Зупинка (graceful shutdown):
docker stop myworker
# Worker отримає CancellationToken та коректно завершиться
Структура проєкту:
MyWebApi/
├── Controllers/
│ └── WeatherForecastController.cs
├── Program.cs
├── MyWebApi.csproj
├── appsettings.json
├── appsettings.Development.json
├── Dockerfile
└── .dockerignore
Program.cs (мінімальний API):
var builder = WebApplication.CreateBuilder(args);
// Додаємо сервіси
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Налаштовуємо HTTP pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
WeatherForecastController.cs:
using Microsoft.AspNetCore.Mvc;
namespace MyWebApi.Controllers;
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
Dockerfile:
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# Копіюємо .csproj та restore
COPY ["MyWebApi.csproj", "."]
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet restore
# Копіюємо решту коду
COPY . .
# Build та publish
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet publish -c Release -o /app/publish /p:UseAppHost=false
# Stage 2: Runtime
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
WORKDIR /app
# Копіюємо опубліковані файли
COPY --from=build /app/publish .
# Відкриваємо порт (документація)
EXPOSE 8080
# Запускаємо
ENTRYPOINT ["dotnet", "MyWebApi.dll"]
.dockerignore:
**/bin/
**/obj/
**/.vs/
**/.vscode/
**/*.user
.git/
.gitignore
README.md
Dockerfile
.dockerignore
docker build -t mywebapi:1.0.0 .
docker run -d \
--name mywebapi \
-p 8080:8080 \
mywebapi:1.0.0
# Перевірити health
curl http://localhost:8080/weatherforecast
# Відкрити Swagger (якщо Development)
open http://localhost:8080/swagger
docker logs mywebapi
docker stop mywebapi
docker rm mywebapi
Вивід API:
[
{
"date": "2026-04-15",
"temperatureC": 12,
"temperatureF": 53,
"summary": "Cool"
},
{
"date": "2026-04-16",
"temperatureC": 25,
"temperatureF": 76,
"summary": "Warm"
}
]
Development:
docker run -d \
--name mywebapi-dev \
-p 8080:8080 \
-e ASPNETCORE_ENVIRONMENT=Development \
mywebapi:1.0.0
Production:
docker run -d \
--name mywebapi-prod \
-p 8080:8080 \
-e ASPNETCORE_ENVIRONMENT=Production \
mywebapi:1.0.0
З кастомними налаштуваннями:
docker run -d \
--name mywebapi \
-p 8080:8080 \
-e ASPNETCORE_ENVIRONMENT=Production \
-e ConnectionStrings__DefaultConnection="Server=db;Database=mydb" \
mywebapi:1.0.0
Kestrel — це вбудований веб-сервер ASP.NET Core, який працює всередині контейнера. За замовчуванням Kestrel слухає на порту 5000 (HTTP) та 5001 (HTTPS).
Проблема в Docker:
У контейнері Kestrel за замовчуванням слухає на http://localhost:5000, що означає, що він доступний лише всередині контейнера. Щоб зробити API доступним ззовні, потрібно налаштувати Kestrel слухати на 0.0.0.0 (всі мережеві інтерфейси).
Рекомендований спосіб (ASP.NET Core 8.0+):
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
WORKDIR /app
COPY --from=build /app/publish .
# Налаштувати Kestrel слухати на порту 8080 на всіх інтерфейсах
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyWebApi.dll"]
Альтернативні варіанти:
# Варіант 1: Використати 0.0.0.0 замість +
ENV ASPNETCORE_URLS=http://0.0.0.0:8080
# Варіант 2: Кілька портів (HTTP + HTTPS)
ENV ASPNETCORE_URLS=http://+:8080;https://+:8443
# Варіант 3: Через окремі змінні
ENV ASPNETCORE_HTTP_PORTS=8080
ENV ASPNETCORE_HTTPS_PORTS=8443
EXPOSE — це документація, а не фактичне відкриття порту. Вона вказує, який порт використовує додаток.
# Документує, що додаток слухає на порту 8080
EXPOSE 8080
Важливо: EXPOSE не прокидає порт на хост. Для цього потрібен прапорець -p при запуску контейнера.
-p vs -PЯвне прокидання (-p):
# Прокинути порт 8080 контейнера на порт 8080 хоста
docker run -p 8080:8080 mywebapi:1.0.0
# Прокинути на інший порт хоста
docker run -p 3000:8080 mywebapi:1.0.0
# API доступний на http://localhost:3000
# Прокинути лише на localhost (безпечніше)
docker run -p 127.0.0.1:8080:8080 mywebapi:1.0.0
# Прокинути на випадковий порт хоста
docker run -p 8080 mywebapi:1.0.0
Автоматичне прокидання (-P):
# Прокинути всі EXPOSE порти на випадкові порти хоста
docker run -P mywebapi:1.0.0
# Дізнатися, який порт призначено
docker port <container_id>
# 8080/tcp -> 0.0.0.0:32768
Для production рекомендується використовувати зовнішній reverse proxy (Nginx, Traefik) для HTTPS. Але для розробки можна налаштувати HTTPS всередині контейнера.
Генерація dev-сертифіката:
# Згенерувати dev-сертифікат на хості
dotnet dev-certs https -ep ${HOME}/.aspnet/https/aspnetapp.pfx -p YourPassword
dotnet dev-certs https --trust
Dockerfile з HTTPS:
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
WORKDIR /app
COPY --from=build /app/publish .
ENV ASPNETCORE_URLS=https://+:8443;http://+:8080
ENV ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx
ENV ASPNETCORE_Kestrel__Certificates__Default__Password=YourPassword
EXPOSE 8080 8443
ENTRYPOINT ["dotnet", "MyWebApi.dll"]
Запуск з монтуванням сертифіката:
docker run -d \
-p 8080:8080 \
-p 8443:8443 \
-v ${HOME}/.aspnet/https:/https:ro \
mywebapi:1.0.0
appsettings.json:
{
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://0.0.0.0:8080"
}
}
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Dockerfile:
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
WORKDIR /app
COPY --from=build /app/publish .
# Порт вже налаштований в appsettings.json
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyWebApi.dll"]
ASPNETCORE_URLS) замість appsettings.json для налаштування портів. Це дозволяє легко змінювати конфігурацію без перебудови образу.# syntax=docker/dockerfile:1
# ============================================
# Stage 1: Build
# ============================================
FROM mcr.microsoft.com/dotnet/sdk:8.0.3-alpine AS build
WORKDIR /src
# Копіюємо .csproj та робимо restore (кешується окремо від коду)
COPY ["MyWebApi.csproj", "."]
# Restore з persistent NuGet cache
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet restore
# Копіюємо решту коду
COPY . .
# Build та publish з оптимізаціями
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet publish \
-c Release \
-o /app/publish \
/p:UseAppHost=false \
/p:PublishTrimmed=true \
/p:TrimMode=link
# ============================================
# Stage 2: Runtime
# ============================================
FROM mcr.microsoft.com/dotnet/aspnet:8.0.3-alpine AS final
# Створити non-root користувача
RUN addgroup -g 1000 appuser && \
adduser -u 1000 -G appuser -s /bin/sh -D appuser
WORKDIR /app
# Копіюємо опубліковані файли
COPY --from=build /app/publish .
# Змінити власника файлів на appuser
RUN chown -R appuser:appuser /app
# Перемкнутися на non-root користувача
USER appuser
# Налаштувати Kestrel
ENV ASPNETCORE_URLS=http://+:8080
ENV ASPNETCORE_ENVIRONMENT=Production
# Документація порту
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
# Запуск
ENTRYPOINT ["dotnet", "MyWebApi.dll"]
1. BuildKit syntax:
# syntax=docker/dockerfile:1
Увімкнення BuildKit для просунутих функцій (cache mounts, secrets).
2. Конкретна версія базового образу:
FROM mcr.microsoft.com/dotnet/sdk:8.0.3-alpine
Використання конкретної версії для стабільності (не 8.0 або latest).
3. Cache mounts для NuGet:
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet restore
Persistent кеш NuGet пакетів між збірками (економія 60-90% часу).
4. Publish з trimming:
/p:PublishTrimmed=true \
/p:TrimMode=link
Видалення невикористаного коду (економія 30-50% розміру).
5. Non-root користувач:
RUN addgroup -g 1000 appuser && \
adduser -u 1000 -G appuser -s /bin/sh -D appuser
USER appuser
Запуск від non-root для безпеки (захист від privilege escalation).
6. Health check:
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
Автоматична перевірка здоров'я контейнера (Docker перезапустить при збої).
Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddHealthChecks();
var app = builder.Build();
// Health check endpoint
app.MapHealthChecks("/health");
app.MapControllers();
app.Run();
Перевірка:
curl http://localhost:8080/health
# Healthy
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy())
.AddNpgSql(connectionString, name: "database")
.AddRedis(redisConnection, name: "redis");
| Варіант | Розмір | Час збірки | Безпека |
|---|---|---|---|
| Базовий (SDK) | 720 МБ | 45 с | ❌ |
| Multi-stage (aspnet) | 210 МБ | 50 с | ⚠️ |
| Multi-stage (aspnet-alpine) | 120 МБ | 50 с | ⚠️ |
| Production-ready (alpine + trimmed + non-root) | 85 МБ | 55 с | ✅ |
Економія: 635 МБ (88%)!
# Build artifacts
**/bin/
**/obj/
**/out/
# IDE
.vs/
.vscode/
.idea/
*.user
*.suo
# Git
.git/
.gitignore
.gitattributes
# Documentation
*.md
docs/
# Tests
**/*Tests/
**/*.Tests/
# CI/CD
.github/
.gitlab-ci.yml
azure-pipelines.yml
# Docker
Dockerfile*
.dockerignore
docker-compose*.yml
# Temporary files
*.tmp
*.log
*.cache
# OS files
.DS_Store
Thumbs.db
# Node.js (якщо є frontend)
node_modules/
npm-debug.log
# Збудувати з BuildKit
DOCKER_BUILDKIT=1 docker build -t mywebapi:1.0.0 .
# Або з явною директивою
docker build -t mywebapi:1.0.0 .
# Перевірити розмір
docker images mywebapi:1.0.0
# Запустити
docker run -d \
--name mywebapi \
-p 8080:8080 \
--restart unless-stopped \
mywebapi:1.0.0
# Перевірити health
docker inspect --format='{{.State.Health.Status}}' mywebapi
# healthy
1. ReadyToRun (R2R) compilation:
RUN dotnet publish \
-c Release \
-o /app/publish \
/p:PublishReadyToRun=true
Швидший запуск (+20-30%), але більший розмір (+10-15%).
2. AOT (Ahead-of-Time) compilation (.NET 8+):
RUN dotnet publish \
-c Release \
-o /app/publish \
/p:PublishAot=true
Найшвидший запуск, мінімальний розмір, але обмежена сумісність.
3. Compression:
RUN dotnet publish \
-c Release \
-o /app/publish \
/p:EnableCompressionInSingleFile=true
Зменшення розміру single-file додатків.
Мета: Створити та запустити простий консольний додаток у Docker.
Кроки:
dotnet new console -n MyConsoleApp.dockerignoreОчікуваний результат: Образ розміром ~125 МБ (Alpine) замість ~195 МБ (Debian).
Мета: Створити повноцінний ASP.NET Core Web API з Swagger у Docker.
Кроки:
dotnet new webapi -n MyWebApihttp://localhost:8080/swaggercurl http://localhost:8080/healthОчікуваний результат: Працюючий API з Swagger, розмір образу ~85-100 МБ.
Мета: Виміряти вплив кешування на швидкість збірки.
Кроки:
.cs файлОчікуваний результат:
Мета: Контейнеризувати solution з кількома проєктами.
Структура:
MySolution/
├── MySolution.sln
├── src/
│ ├── MyWebApi/
│ │ └── MyWebApi.csproj
│ ├── MyCore/
│ │ └── MyCore.csproj
│ └── MyInfrastructure/
│ └── MyInfrastructure.csproj
└── Dockerfile
Кроки:
.csproj файлиОчікуваний результат: Працюючий API, всі залежності коректно розв'язані.
У цій статті ми детально розглянули контейнеризацію .NET додатків:
Офіційні .NET образи:
dotnet/sdk — для збірки (700 МБ)dotnet/aspnet — для Web API (210 МБ / 120 МБ Alpine)dotnet/runtime — для консольних додатків (190 МБ / 110 МБ Alpine)dotnet/runtime-deps — для self-contained (110 МБ / 15 МБ Alpine)Вибір базового образу:
Контейнеризація Console App:
Контейнеризація Web API:
ASPNETCORE_URLSПорти та Kestrel:
ASPNETCORE_URLS=http://+:8080 — слухати на всіх інтерфейсахEXPOSE 8080 — документація порту-p 8080:8080 — прокидання порту на хостProduction-Ready Dockerfile:
Оптимізації:
dotnet/sdk:8.0.3-alpine з BuildKit cache mountsdotnet/aspnet:8.0.3-alpine для Web API або dotnet/runtime:8.0.3-alpine для Console App