Введение: почему продвинутый уровень?
Контейнеризация C#-приложений на базе .NET перестала быть просто трендом — это индустриальный стандарт. На начальном уровне достаточно базового Dockerfile с FROM mcr.microsoft.com/dotnet/aspnet. Однако в production-среде возникают задачи, требующие более глубокого понимания: минимизация размера образа, безопасность, многоконтейнерная архитектура, эффективное управление конфигурацией и отладка.
Эта статья предназначена для разработчиков, которые уже знакомы с основами Docker и C#, но хотят перейти на следующий уровень. Мы разберем продвинутые техники, которые позволят создавать легкие, безопасные и легко масштабируемые образы для ваших .NET-приложений.
1. Многоэтапная сборка (Multi-stage build): искусство легких образов
Классическая проблема — образы .NET SDK весят более 1.5 ГБ. Затаскивать их в production бессмысленно и опасно. Многоэтапная сборка решает эту задачу, разделяя этап сборки и этап запуска.
Продвинутый Dockerfile с multi-stage
Вместо того чтобы просто копировать бинарные файлы, мы используем официальные образы SDK для компиляции, а затем копируем результат в минимальный образ aspnet.
# Этап 1: Сборка
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /app
# Копируем только файлы проекта (оптимизация кэширования)
COPY src/MyApp.csproj .
RUN dotnet restore
# Копируем остальной код
COPY src/ .
RUN dotnet publish -c Release -o /app/publish
# Этап 2: Runtime
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
# Добавляем непривилегированного пользователя
RUN adduser --disabled-password --gecos '' appuser
USER appuser
COPY --from=build /app/publish .
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENTRYPOINT ["dotnet", "MyApp.dll"]
Что здесь важно:
- Кэширование слоев: сначала копируется только
.csprojи выполняетсяdotnet restore. Пока зависимости не меняются, Docker будет использовать кэш для этого слоя. - Непривилегированный пользователь:
appuser— критически важно для безопасности в production. - Порт 8080: в .NET 8 по умолчанию используется 8080, что соответствует современным облачным стандартам.
Трюк: Trimmed и AOT-сборка
Для еще большего уменьшения размера используйте Trimming (вырезание неиспользуемого кода) или Native AOT. Это позволяет получить образы размером 10-30 МБ.
# Для ReadyToRun (R2R) и Trimming
RUN dotnet publish -c Release -r linux-x64 --self-contained true \
-p:PublishTrimmed=true \
-p:PublishSingleFile=true \
-o /app/publish
Важно: AOT накладывает ограничения на рефлексию и динамическую загрузку сборок. Перед использованием убедитесь, что ваш код совместим.
2. Работа с конфигурацией и секретами
Хранение строк подключения и API-ключей внутри образа — грубая ошибка. На продвинутом уровне мы используем Docker Secrets, переменные окружения и связку с appsettings.json.
Переопределение appsettings через Docker
В .NET есть встроенный механизм приоритетов конфигурации. Мы можем передавать настройки через переменные окружения, которые автоматически маппятся в секции конфигурации.
// В C# код
var builder = WebApplication.CreateBuilder(args);
builder.Configuration
.AddJsonFile("appsettings.json", optional: false)
.AddEnvironmentVariables(); // Переопределяет значения из JSON
Запускаем контейнер:
docker run -d -p 8080:8080 \
-e "ConnectionStrings__DefaultConnection=Server=db;Database=myapp;User Id=sa;Password=MyPass123!" \
-e "Logging__LogLevel__Default=Warning" \
myapp:latest
Синтаксис с двойным подчеркиванием (__) заменяет двоеточие (:) в названиях секций, что необходимо для совместимости с Docker.
Docker Secrets для чувствительных данных
Для Swarm-режима или Kubernetes используйте секреты. Вот пример с Docker Compose:
version: '3.8'
services:
web:
image: myapp:latest
secrets:
- db_password
environment:
- ConnectionStrings__DefaultConnection=Server=db;Database=myapp;User Id=sa;Password=__DB_PASSWORD__
secrets:
db_password:
file: ./secrets/db_password.txt
В коде C# секрет можно прочитать из файла /run/secrets/db_password, либо использовать шаблон с заменой строки через скрипт entrypoint.
3. Docker Compose для микросервисов на C#
Когда ваше приложение состоит из нескольких сервисов (API, бэкграунд-воркеры, база данных), docker-compose.yml становится вашим основным инструментом оркестрации на локальной машине и в CI/CD.
Продвинутый Compose: healthcheck и зависимости
version: '3.8'
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- postgres_data:/var/lib/postgresql/data
api:
build:
context: .
dockerfile: Dockerfile
ports:
- "5000:8080"
depends_on:
db:
condition: service_healthy
environment:
- ConnectionStrings__DefaultConnection=Host=db;Database=myapp;Username=user;Password=password
restart: unless-stopped
worker:
build:
context: .
dockerfile: Dockerfile.worker
depends_on:
db:
condition: service_healthy
environment:
- DOTNET_ENVIRONMENT=Production
restart: unless-stopped
volumes:
postgres_data:
Ключе