Контейнеризация C# приложений в Docker: продвинутый уровень

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему продвинутый уровень?

Контейнеризация 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:

Ключе

Блоги

Рекомендаций по книгам