tech.guitarrapc.cóm

Technical updates

TargetFrameworksを指定しつつDockerビルドしたい

TargetFrameworksを用いると複数のフレームワークに対応したライブラリを作成できます。今回は、このTargetFrameworksを指定しつつDockerビルドする方法を紹介します。 なお、コンテナで複数ランタイムを持つ意義はほぼないので、この記事はただの興味本位です。

TargetFrameworksでDockerfileは追加できない

TargetFrameworkだと、Visual StudioでAdd > Docker Support...メニューを選べます。

image

しかし、TargetFrameworksを指定しているとVisual StudioでAdd > Docker Support...メニューがなくなります。

image

とはいえDockerfileを手動で作成したり、TargetFrameworkの時にDockerfileを追加しておけばDockerfileを追加できます。 今回はTargetFrameworkの時に追加していたDockerfileを使います。

Dockerfileに任意のSDKを追加する

Visual Studioで作成したDockerfileは2レイヤーの構成です。それぞれでSDKとRuntimeをインストールしています。

  1. ビルドフェーズ: プロジェクトをDockerコンテナでビルド (.NET SDK)
  2. ファイナルフェーズ: ビルド成果物を実行するための最小限の環境を提供 (.NET Runtime)

この時点で察しがつく通り、SDKをビルドフェーズに追加すればいいのですが、コンテナにおいて異なるランタイムバージョンを同時に実行できる必然性はありません。このため、本記事自体はさしたる意味を持ちませんが知りたいですよね?1見てみましょう。

TargetFrameworkを指定しているプロジェクト

例えばSerialization.Benchmark.csprojがありnet9.0向けとしましょう。プロジェクト参照などをしていると、よくある感じのDockerfileになります。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
    <DockerfileContext>..\..\..</DockerfileContext>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" />
    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\Serialization.Core\Serialization.Core.csproj" />
  </ItemGroup>

</Project>
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.

# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM mcr.microsoft.com/dotnet/runtime:9.0 AS base
USER app
WORKDIR /app


# This stage is used to build the service project
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["Directory.Build.props", "src/Directory.Build.props"]
COPY ["src/Serialization/Serialization.Benchmark/Serialization.Benchmark.csproj", "src/Serialization/Serialization.Benchmark/"]
COPY ["src/Serialization/Serialization.Core/Serialization.Core.csproj", "src/Serialization/Serialization.Core/"]
RUN dotnet restore "./src/Serialization/Serialization.Benchmark/Serialization.Benchmark.csproj"
COPY . .
WORKDIR "/src/src/Serialization/Serialization.Benchmark"
RUN dotnet build "./Serialization.Benchmark.csproj" -c $BUILD_CONFIGURATION -o /app/build -f net9.0

# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Serialization.Benchmark.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false -f net9.0

# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Serialization.Benchmark.dll"]

TargetFrameworksに切り替え

csprojのTargetFrameworkTargetFrameworksに書き換えて、net8.0を追加します。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworks>net8.0;net9.0</TargetFrameworks>
    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
    <DockerfileContext>..\..\..</DockerfileContext>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" />
    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\Serialization.Core\Serialization.Core.csproj" />
  </ItemGroup>

</Project>

ポイントはDockerfileに追加したCOPY --from=mcr.microsoft.com/dotnet/sdk:8.0 /usr/share/dotnet/sdk /usr/share/dotnet/sdkです。これを指定すると、リモート先のdotnet/sdk8.0イメージから/usr/share/dotnet/sdkをコピーします。

これまでDockerfileでツールを追加する方法はいろいろ変遷を辿ってきました。が、現在の主流は本記事の「既存の公式イメージから必要なファイルだけ持ってくる」方法となっており、curlやaptを使って持ってきたりするより使いやすくオススメです。

# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.

# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM mcr.microsoft.com/dotnet/runtime:9.0 AS base
USER app
WORKDIR /app


# This stage is used to build the service project
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY --from=mcr.microsoft.com/dotnet/sdk:8.0 /usr/share/dotnet/sdk /usr/share/dotnet/sdk
COPY ["Directory.Build.props", "src/Directory.Build.props"]
COPY ["src/Serialization/Serialization.Benchmark/Serialization.Benchmark.csproj", "src/Serialization/Serialization.Benchmark/"]
COPY ["src/Serialization/Serialization.Core/Serialization.Core.csproj", "src/Serialization/Serialization.Core/"]
RUN dotnet restore "./src/Serialization/Serialization.Benchmark/Serialization.Benchmark.csproj"
COPY . .
WORKDIR "/src/src/Serialization/Serialization.Benchmark"
RUN dotnet build "./Serialization.Benchmark.csproj" -c $BUILD_CONFIGURATION -o /app/build -f net9.0

# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Serialization.Benchmark.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false -f net9.0

# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Serialization.Benchmark.dll"]

これでTargetFrameworksを指定しつつDockerビルドできるようになりました。

$ docker build -t foo -f .\src\Serialization\Serialization.Benchmark\Dockerfile .

image

まとめ

これ、何度も言いますがあまり意味はないんですよね。Dockerコンテナはシンプルなランタイムサイズにするという原則からも、普段使う必要がないSDKを追加しているのはあまりよくないです。Visual Studioがサポートしていないのも理解できますし、今後もサポートするはずないと考えています。

ただ、Dockerfileで任意のイメージから必要なファイルをコピーする手法は.NET SDKじゃなくとも汎用的に利用できる方法なので覚えておくといいですね。


  1. ランタイムにも追加してnet8.0とnet9.0の実行を切り替えられるように、などは考えられますが、コンテナ運用的にイメージ分けるほうがいいです。