dotnet-containers について
.NETアプリをコンテナ化するための実践ガイドです。Dockerfileやdotnet publishによるイメージ作成、イメージとレイヤーの選択、非root実行、ヘルスチェックを扱います。
主な機能
- 01マルチステージDockerfileのパターン
- 02dotnet publishによるコンテナイメージ作成
- 03イメージ選択とレイヤーキャッシュ
- 04非rootコンテナの設定
- 05コンテナのヘルスチェック指針
活用シーン
- 01.NET APIのコンテナ化
- 02レイヤーキャッシュによる再ビルドの削減
- 03非root権限での.NETコンテナ実行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-containersインストール後、Claude Code で/dotnet-containersを入力して読み込まれたか確認します。
詳細
dotnet-containers
.NET アプリケーションをコンテナー化するためのベストプラクティスです。マルチステージ Dockerfile のパターン、dotnet publish のコンテナーイメージ機能(.NET 8 以降)、ルートレスコンテナーの構成、最適化されたレイヤーキャッシュ、コンテナーのヘルスチェックを扱います。
対象外: DI コンテナーの仕組みとサービスの有効期間については、[skill:dotnet-csharp-dependency-injection] を参照してください。Kubernetes のデプロイマニフェストと Docker Compose によるオーケストレーションについては、[skill:dotnet-container-deployment] で扱っています。イメージのビルドとプッシュに関する CI/CD パイプラインとの統合については、[skill:dotnet-gha-publish] と [skill:dotnet-ado-publish] を参照してください。コンテナー化されたアプリケーションのテストについては、Testcontainers のパターンを扱う [skill:dotnet-integration-testing] を参照してください。
相互参照:ヘルスチェックのパターンについては [skill:dotnet-observability]、Kubernetes へのコンテナーのデプロイと Compose を使ったローカル開発については [skill:dotnet-container-deployment]、ビルド出力を一元化するレイアウトを使用する場合の Dockerfile パスの調整については [skill:dotnet-artifacts-output] を参照してください。
マルチステージ Dockerfile
マルチステージビルドでは、ビルド環境とランタイム環境を分離し、最小限の最終イメージを作成します。
標準的なマルチステージパターン
# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
# Copy project files first for layer caching
COPY ["src/MyApi/MyApi.csproj", "src/MyApi/"]
COPY ["src/MyApi.Core/MyApi.Core.csproj", "src/MyApi.Core/"]
COPY ["Directory.Build.props", "."]
COPY ["Directory.Packages.props", "."]
RUN dotnet restore "src/MyApi/MyApi.csproj"
# Copy everything else and build
COPY . .
WORKDIR "/src/src/MyApi"
RUN dotnet publish -c Release -o /app/publish --no-restore
# Stage 2: Runtime
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
EXPOSE 8080
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApi.dll"]
レイヤーキャッシュの戦略
変更頻度の低いものから高いものの順に COPY 命令を記述します。
- プロジェクトファイルと props -- 依存関係が変更されたときのみ変更されます
dotnet restore-- プロジェクトファイルが変更されるまでキャッシュされます- ソースコード -- ビルドのたびに変更されます
dotnet publish-- ソースまたは復元レイヤーが変更された場合にのみ実行されます
# Good: restore layer is cached when only source changes
COPY ["src/MyApi/MyApi.csproj", "src/MyApi/"]
RUN dotnet restore
COPY . .
RUN dotnet publish
# Bad: restore runs on every source change
COPY . .
RUN dotnet restore
RUN dotnet publish
ソリューション全体の復元
複数プロジェクトのソリューションでは、すべての .csproj ファイルとソリューションファイルをコピーして、1 回の復元で済むようにします。
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
# Copy solution and all project files for restore caching
COPY ["MyApp.sln", "."]
COPY ["Directory.Build.props", "."]
COPY ["Directory.Packages.props", "."]
COPY ["src/MyApi/MyApi.csproj", "src/MyApi/"]
COPY ["src/MyApi.Core/MyApi.Core.csproj", "src/MyApi.Core/"]
COPY ["src/MyApi.Infrastructure/MyApi.Infrastructure.csproj", "src/MyApi.Infrastructure/"]
RUN dotnet restore
COPY . .
RUN dotnet publish "src/MyApi/MyApi.csproj" -c Release -o /app/publish --no-restore
dotnet publish コンテナーイメージ(.NET 8 以降)
.NET 8 以降では、Dockerfile を使わずに dotnet publish から OCI コンテナーイメージを直接作成できます。これは Microsoft.NET.Build.Containers SDK(.NET SDK に含まれています)を使用します。
基本的な使い方
# Publish as a container image to local Docker daemon
dotnet publish --os linux --arch x64 /t:PublishContainer
# Publish to a remote registry
dotnet publish --os linux --arch x64 /t:PublishContainer \
-p:ContainerRegistry=ghcr.io \
-p:ContainerRepository=myorg/myapi
MSBuild の構成
.csproj でコンテナーのプロパティを構成します。
<PropertyGroup>
<ContainerBaseImage>mcr.microsoft.com/dotnet/aspnet:10.0</ContainerBaseImage>
<ContainerImageName>myapi</ContainerImageName>
<ContainerImageTag>$(Version)</ContainerImageTag>
</PropertyGroup>
<ItemGroup>
<ContainerPort Include="8080" Type="tcp" />
</ItemGroup>
高度な構成
<PropertyGroup>
<ContainerBaseImage>mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled</ContainerBaseImage>
<ContainerUser>app</ContainerUser>
</PropertyGroup>
<ItemGroup>
<ContainerEnvironmentVariable Include="ASPNETCORE_URLS" Value="http://+:8080" />
<ContainerEnvironmentVariable Include="DOTNET_RUNNING_IN_CONTAINER" Value="true" />
<ContainerLabel Include="org.opencontainers.image.source" Value="https://github.com/myorg/myapi" />
</ItemGroup>
dotnet publish と Dockerfile の使い分け
| シナリオ | 推奨 |
|---|---|
| シンプルな単一プロジェクトの API | dotnet publish /t:PublishContainer -- 定型コードが少なくて済みます |
| ネイティブ依存関係を含むマルチステージビルド | Dockerfile -- ビルド環境を完全に制御できます |
OS パッケージ(例:libgdiplus)のインストールが必要 | Dockerfile -- SDK による publish では RUN apt-get install を利用できません |
| 複雑なビルド手順を伴う CI/CD | Dockerfile -- 明示的で再現性があります |
| ローカルでのコンテナーの簡易テスト | dotnet publish /t:PublishContainer -- 最も迅速に反復できます |
ベースイメージの選択
公式 .NET コンテナーイメージ
| イメージ | 用途 | サイズ |
|---|---|---|
mcr.microsoft.com/dotnet/aspnet:10.0 | ASP.NET Core アプリ(Ubuntu) | ~220 MB |
mcr.microsoft.com/dotnet/aspnet:10.0-alpine | ASP.NET Core アプリ(Alpine、より小型) | ~110 MB |
mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled | ディストロレス(シェルなし、パッケージマネージャーなし) | ~110 MB |
mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled-extra | Chiseled + グローバリゼーション + タイムゾーン | ~130 MB |
mcr.microsoft.com/dotnet/runtime:10.0 | コンソールアプリ、ワーカーサービス | ~190 MB |
mcr.microsoft.com/dotnet/runtime-deps:10.0 | 自己完結型/AOT アプリ(ランタイム不要) | ~30 MB |
ベースイメージの選び方
- デフォルト: Web アプリには
aspnet、ワーカーサービスにはruntimeを使用します - 最小サイズ:
chiseledバリアントを使用します(シェルなし、root ユーザーなし、パッケージマネージャーなし) - グローバリゼーションが必要: アプリでカルチャ固有の書式設定やタイムゾーンを使用する場合は、
chiseled-extraを使用します - 自己完結型または AOT: ランタイムがアプリにバンドルされるため、
runtime-depsを使用します - Alpine: Ubuntu より小型ですが、musl libc を使用します。ネイティブ依存関係との互換性をテストしてください
ルートレスコンテナー
コンテナーを非 root ユーザーで実行すると、攻撃対象領域を減らせます。.NET 8 以降の chiseled イメージは、デフォルトで非 root ユーザーとして実行されます。
標準イメージで非 root として実行する
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
# Create non-root user and switch to it
RUN adduser --disabled-password --gecos "" --uid 1001 appuser
USER appuser
COPY --from=build --chown=appuser:appuser /app/publish .
ENTRYPOINT ["dotnet", "MyApi.dll"]
chiseled イメージで非 root として実行する
chiseled イメージには、設定済みの app ユーザー(UID 1654)が含まれています。追加の設定は不要です。
FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled AS runtime
WORKDIR /app
# Already runs as non-root 'app' user (UID 1654)
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApi.dll"]
ポートの設定
非 root ユーザーは 1024 未満のポートにバインドできません。ASP.NET Core はコンテナー内でデフォルトのポート 8080 を使用します(ASPNETCORE_HTTP_PORTS で設定)。
# Default in .NET 8+ container images -- no explicit config needed
# ASPNETCORE_HTTP_PORTS=8080
# If you need a different port:
ENV ASPNETCORE_HTTP_PORTS=5000
EXPOSE 5000
コンテナーのヘルスチェック
ヘルスチェックを使用すると、コンテナーランタイムでアプリケーションの準備状況を監視できます。アプリケーションレベルのヘルスチェックエンドポイント([skill:dotnet-observability] を参照)は、Docker と Kubernetes のプローブで使用されます。
Docker HEALTHCHECK
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
# Health check using curl (not available in chiseled images)
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8080/health/live || exit 1
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApi.dll"]
chiseled イメージ(curl なし)の場合は、専用のヘルスチェックバイナリを使用するか、オーケストレーター側のプローブ(Kubernetes の httpGet、Docker Compose の test)を利用してください。
FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled AS runtime
WORKDIR /app
# No HEALTHCHECK directive -- use orchestrator probes instead
# See [skill:dotnet-container-deployment] for Kubernetes probe configuration
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApi.dll"]
ヘルスチェックエンドポイント
アプリケーションにヘルスチェックエンドポイントを登録してください(詳しい手順は [skill:dotnet-observability] を参照)。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), tags: ["live"])
.AddNpgSql(
builder.Configuration.GetConnectionString("DefaultConnection")!,
name: "database",
tags: ["ready"]);
var app = builder.Build();
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("live")
});
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
コンテナーの最適化
.dockerignore
ビルドコンテキストから不要なファイルを除外するため、必ず .dockerignore を含めてください。
**/.git
**/.vs
**/.vscode
**/bin
**/obj
**/node_modules
**/*.user
**/*.suo
**/Dockerfile*
**/docker-compose*
**/.dockerignore
**/README.md
**/LICENSE
グローバリゼーションとタイムゾーン
アプリでグローバリゼーション(カルチャー固有の書式設定、タイムゾーン)のサポートが必要な場合は、ICU を設定してください。
# Option 1: Use the chiseled-extra image (includes ICU + tzdata)
FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled-extra
# Option 2: Disable globalization for smaller images (if not needed)
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true
メモリ制限
.NET がコンテナーのメモリ制限を尊重するように設定してください。
# .NET automatically detects container memory limits and adjusts GC heap size.
# Override only if needed:
ENV DOTNET_GCHeapHardLimit=0x10000000 # 256 MB hard limit
.NET は cgroup のメモリ制限を自動的に読み取ります。GC はヒープサイズを調整し、コンテナーのメモリ予算内に収まるようにします。特別な理由がない限り、DOTNET_GCHeapHardLimit の設定は避けてください。
ReadOnlyRootFilesystem
多層防御のため、ルートファイルシステムを読み取り専用にして実行してください。一時ファイル用に書き込み可能なパスを確保してください。
ENV DOTNET_EnableDiagnostics=0
# Or mount a tmpfs at /tmp for diagnostics support
主な原則
- マルチステージビルドを使用する -- ビルドツールを最終イメージに含めないようにします
- レイヤーキャッシュを考慮して COPY の順序を決める -- ソースコードより前にプロジェクトファイルをコピーして restore を実行します
- 本番環境では chiseled イメージを優先する -- シェルなし、root なしで、攻撃対象領域を最小限に抑えます
- シンプルなプロジェクトでは
dotnet publish /t:PublishContainerを使用する -- Dockerfile の定型記述を省略できます - 非 root ユーザーとして実行する --
USERディレクティブまたは chiseled イメージ(デフォルトで非 root)を使用します - ヘルスチェックエンドポイントを設定する -- オーケストレーターがアプリケーションの状態を監視できるようにします([skill:dotnet-observability] を参照)
.dockerignoreを含める -- ビルドコンテキストを小さく保ち、シークレットを除外します
エージェントが注意すべき点
- 最終イメージに
mcr.microsoft.com/dotnet/sdkを使用しないでください -- SDK イメージは 800 MB を超え、ビルドツールも含まれています。最終ステージには必ずaspnet、runtime、またはruntime-depsを使用してください。 - イメージタグをパッチバージョンに固定しないでください(例:
10.0.1)-- セキュリティパッチを受け取るには10.0を使用してください。特定の互換性要件がある場合に限り、パッチバージョンに固定してください。 - chiseled イメージで
HEALTHCHECKを使用しないでください -- chiseled イメージにはcurlもシェルもありません。代わりに、オーケストレーター側のプローブ(Kubernetes のhttpGet、Docker Compose のtest)を使用してください。 dotnet restoreの後にdotnet publishを実行する際は、--no-restoreを忘れないでください -- これを指定しないと restore が再度実行され、レイヤーキャッシュが機能しなくなります。- 非 root コンテナーで 1024 未満のポートにバインドしないでください -- .NET はコンテナーイメージでデフォルトのポート 8080 を使用します。
ASPNETCORE_HTTP_PORTSを上書きする場合は、ポートが 1024 以上であることを確認してください。 .dockerignoreを省略しないでください -- 省略すると、ビルドコンテキストに.git、bin/obj、さらにシークレットが含まれる可能性があり、ビルド時間とイメージサイズが増加します。