.NETは組み込みコンテナサポートを持っておりdotnet publish
でコンテナイメージを作成できます。このようなDockerfileなしのコンテナ作成はJibやBuildpacksを思い起こしますが、dotnet publish
という標準コマンドで提供しているのが大きな違いです。
Dockerfileを書かなくていいというだけで魅力を感じる面があるので、何ができて何ができないのかいろいろ触ってみましょう。
- .NET 7以降、dotnet publishでコンテナイメージ作成をサポート
- dotnet publishでコンテナイメージを作成する
- イメージ作成を調整してみる
- コンテナイメージを調整する
- FAQ
- まとめ
- 参考
.NET 7以降、dotnet publishでコンテナイメージ作成をサポート
.NET 7からdotnet publish
でコンテナイメージ作成できるようになりました。.NETランタイムも、.NET 3.0でコンテナにおけるメモリ制限下のOOMの課題が修正され1、.NET 5以降はコンテナでの動作が安定しています。さらに.NET 8以降徐々に機能追加されて、簡単なコンテナイメージならDockerfileを書かずにdotnet publish
だけでコンテナイメージを作成できるようになったことはうれしいことです。
今回は、コンテナイメージの作成、イメージ作成の調整、イメージの調整など「Dockerfileでこうやるよね」ということをdotnet publish
でどうやるかを見ていきます。
dotnet publishでコンテナイメージを作成する
dotnet publishでコンテナイメージを作成する前に必要なものは次の通りです。
- .NET 8+ SDK: 現在サポートされている.NET SDKに合わせる
- コンテナランタイム: ローカルならDocker DesktopやPodman、Docker Engineなど
プロジェクトタイプごとの事前準備
dotnet publish
を使ったコンテナイメージ作成は、WebアプリケーションやWorkerサービスなどWeb SDK
プロジェクトはデフォルトで有効になっており追加設定不要です。一方、ConsoleAppはプロジェクトファイル(.csproj)でEnableSdkContainerSupport
を有効にする必要があります。
プロジェクトタイプ | dotnet new | SDKによるコンテナサポート | 備考 |
---|---|---|---|
Webアプリケーション | web | 〇 | デフォルトで有効 |
Workerサービス | worker | 〇 | デフォルトで有効 |
ConsoleApp | console | × | EnableSdkContainerSupportの追加が必要 |
SDKによるコンテナサポートを有効にする.csproj設定は次の通りです。
<PropertyGroup> <EnableSdkContainerSupport>true</EnableSdkContainerSupport> </PropertyGroup>
プロジェクトごとのコンテナイメージ作成
Webアプリケーション、Workerサービス、Web APIアプリケーション、ConsoleAppのプロジェクトごとにコンテナイメージを作成してみましょう。
Webアプリケーション
$ dotnet new web -o WebApp20 $ cd WebApp20 $ dotnet publish --os linux --arch x64 -c Release /t:PublishContainer Restore complete (0.3s) WebApp20 succeeded (8.5s) → bin\Release\net9.0\linux-x64\publish\ Build succeeded in 9.1s $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE webapp20 latest b909b3cec675 22 minutes ago 329MB
csprojです。
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net9.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> </Project>
Workerサービス
デフォルトで有効なため、追加設定不要でdothnet publishでコンテナイメージを作成できます。
$ dotnet new worker -o WorkerApp21 $ cd WorkerApp21 $ dotnet publish --os linux --arch x64 -c Release /t:PublishContainer Restore complete (0.3s) WorkerApp21 succeeded (8.7s) → bin\Release\net9.0\linux-x64\publish\ Build succeeded in 9.4s $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE workerapp21 latest 90a9c6a9afda 30 seconds ago 295MB
csprojです。
<Project Sdk="Microsoft.NET.Sdk.Worker"> <PropertyGroup> <TargetFramework>net9.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <UserSecretsId>dotnet-WorkerApp21-462df4c8-ecc5-4b76-8e17-5e3f8f27dd57</UserSecretsId> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.2" /> </ItemGroup> </Project>
Web APIサービス
デフォルトで有効なため、追加設定不要でdothnet publishでコンテナイメージを作成できます。
$ dotnet new api -o WebApiApp22 $ cd WebApiApp22 $ dotnet publish --os linux --arch x64 -c Release /t:PublishContainer Restore complete (0.3s) WebApiApp22 succeeded (2.8s) → bin\Release\net9.0\linux-x64\publish\ Build succeeded in 3.5s $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE webapiapp22 latest d8e46ecffda6 10 seconds ago 330MB
csporjです。
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net9.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.2" /> </ItemGroup> </Project>
コンソールアプリケーション
追加設定をするとdothnet publishでコンテナイメージを作成できます。csprojでEnableSdkContainerSupport
を有効にしない場合、ビルド時に-p:EnableSdkContainerSupport=true
を指定します。
$ dotnet new console -o ConsoleApp23 $ cd ConsoleApp23 # csprojでEnableSdkContainerSupportを有効にする場合 $ dotnet publish --os linux --arch x64 -c Release /t:PublishContainer # csprojでEnableSdkContainerSupportを有効にしない場合 $ dotnet publish --os linux --arch x64 -c Release /t:PublishContainer -p:EnableSdkContainerSupport=true Restore complete (0.3s) ConsoleApp23 succeeded (7.8s) → bin\Release\net9.0\linux-x64\publish\ Build succeeded in 8.3s $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE consoleapp23 latest e7b922fab1f0 About a minute ago 293MB
csprojです。
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net9.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <PropertyGroup> <EnableSdkContainerSupport>true</EnableSdkContainerSupport> </PropertyGroup> </Project>
イメージ作成を調整してみる
csprojに設定を追加したり、コマンドでイメージ作成時の動作を調整できます。ドキュメントを見つつ、調整してみましょう。
イメージ名の指定
デフォルトではAssemblyName
をべースに設定されます。これを上書きするには、ContainerRepository
を指定します。
<PropertyGroup> <EnableSdkContainerSupport>true</EnableSdkContainerSupport> <ContainerRepository>container-app-23</ContainerRepository> </PropertyGroup>
コンテナイメージを生成してみると、指定した名前でイメージができたことを確認できます。
$ dotnet publish --os linux --arch x64 -c Release /t:PublishContainer Restore complete (0.2s) ConsoleApp23 succeeded (8.7s) → bin\Release\net9.0\linux-x64\publish\ Build succeeded in 9.1s $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE container-app-23 latest 21ff9cf2268b 7 seconds ago 293MB
イメージをプッシュする
イメージ名と同様にレジストリ名など設定ごとにプロパティがあります。例えばECRなら{ACCOUNT_ID}.dkr.ecr.{REGION}.amazonaws.com/foobar:9.0-alpine
となるのですが、次のマッピングになります。
Image name part | MSBuild property | Example values |
---|---|---|
REGISTRY[:PORT] | ContainerRegistry |
{ACCOUNT_ID}.dkr.ecr.{REGION}.amazonaws.com |
PORT | ContainerPort |
:443 |
REPOSITORY | ContainerRepository |
foobar |
TAG | ContainerImageTag |
9.0 |
FAMILY2 | ContainerFamily |
-alpine |
レジストリ名を指定するとdotnet publish
時に自動的に認証を確認してpushされます。
ECRにプッシュする
例えばECRに展開することを想定して次のような設定を追加します。
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net9.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <PropertyGroup> <EnableSdkContainerSupport>true</EnableSdkContainerSupport> <ContainerRegistry>12345678901234.dkr.ecr.ap-northeast-1.amazonaws.com</ContainerRegistry> <ContainerRepository>container-app-23</ContainerRepository> </PropertyGroup> </Project>
あとはdocker loginして、-p:ContainerImageTag:タグ指定
でタグを実行ごとに指定してみましょう。dotnet publish
を実行するとイメージが作成、pushされます。
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 12345678901234.dkr.ecr.ap-northeast-1.amazonaws.com $ dotnet publish --os linux --arch x64 -c Release /t:PublishContainer -p:ContainerImageTag=1.0
Docker Hubにプッシュする
DockerHubの場合、-p:ContainerRegistry=docker.io
と指定します。事前にDocker Hubへログインしておきましょう。
マルチプラットフォーム対応イメージ
ベースイメージがマルチプラットフォームに対応しているなら、RuntimeIdentifiers
を指定すると自動的に対象プラットフォーム+マルチプラットフォームイメージが生成されます。例えば、linux/amd64とlinux/arm64をサポートする場合は次のように指定します。3
<PropertyGroup> <RuntimeIdentifiers>linux-x64;linux-arm64</RuntimeIdentifiers> </PropertyGroup> <PropertyGroup> <EnableSdkContainerSupport>true</EnableSdkContainerSupport> <ContainerRepository>container-app-23</ContainerRepository> </PropertyGroup>```
実行します。
$ dotnet publish -c Release -p:ContainerImageTag="latest" -p:ContainerRegistry=docker.io /t:PublishContainer
すると次の3イメージがpushされます。latestはマルチプラットフォーム対応イメージですが、linux-x64とlinux-arm64はそれぞれのプラットフォーム向けのイメージです。イメージ名は異なりますが、次のような結果です。
- latest
- latest-linux-x64
- latest-linux-arm64
Dockerfileと違って、dotnet publish
でマルチプラットフォーム対応イメージを作る時、各プラットフォームサフィックスイメージが生成を抑制できないことに注意してください。
デフォルトイメージ
dotnet publish
のコンテナイメージは、設定に応じて自動的に切り替わります4。ベースイメージ変更は別途説明するので、まずはデフォルトを把握しておきましょう。
自己完結型アプリケーション (ベースイメージ: mcr.microsoft.com/dotnet/runtime-deps
)
実行例
$ dotnet publish --os linux --arch x64 -p:SelfContained=true -p:PublishSingleFile=true -c Release /t:PublishContainer -p:ContainerImageTag=1.0 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE container-app-23 1.0 55f88494a42c 3 seconds ago 283MB
ASP.NET Coreプロジェクト (ベースイメージ: mcr.microsoft.com/dotnet/aspnet
)
実行例
$ dotnet publish --os linux --arch x64 -c Release /t:PublishContainer -p:ContainerImageTag=1.0 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE webapiapp22 1.0 04af635c8280 17 seconds ago 330MB
ランタイム指定がlinux-musl-x64
やlinux-musl-arm64
(ベースイメージ: alpine
)
実行例
$ dotnet publish -r linux-musl-x64 -c Release /t:PublishContainer -p:ContainerImageTag=1.0 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE container-app-23 1.0 547a3669f3ac 8 seconds ago 130MB
PublishAot=true
(ベースイメージ: mcr.microsoft.com/dotnet/runtime-deps:9.0-noble-chiseled
など)
実行例
$ dotnet publish --os linux --arch x64 -p:PublishAot=true -c Release /t:PublishContainer -p:ContainerImageTag=1.0 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE container-app-23 1.0 3acafde7dd76 2 minutes ago 125MB $ docker inspect container-app-23:1.0 [ { "Id": "sha256:3acafde7dd76b6b9799139b78e225fcc2a6d9decc9782b564c5e2a235cfc3a60", "RepoTags": [ "container-app-23:1.0" ], "RepoDigests": [], "Parent": "", "Comment": "", "Created": "2025-03-10T09:55:18.0968842Z", "DockerVersion": "", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "1654", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "ExposedPorts": { "8080/tcp": {} }, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "APP_UID=1654", "ASPNETCORE_HTTP_PORTS=8080", "DOTNET_RUNNING_IN_CONTAINER=true" ], "Cmd": null, "Image": "", "Volumes": null, "WorkingDir": "/app", "Entrypoint": [ "/app/ConsoleApp23", "dotnet", "ConsoleApp23.dll" ], "OnBuild": null, "Labels": { "org.opencontainers.artifact.created": "2025-03-10T09:55:17.4730219Z", "org.opencontainers.image.authors": "ConsoleApp23", "org.opencontainers.image.base.name": "mcr.microsoft.com/dotnet/runtime-deps:8.0", "org.opencontainers.image.created": "2025-03-10T09:55:17.4730219Z", "org.opencontainers.image.version": "1.0.0" } }, "Architecture": "amd64", "Os": "linux", "Size": 125199770, "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/4a9eec2b50acd2f8acd2d7967da00beecceb60194a16316d07a9dc2f7cd7fbb4/diff:/var/lib/docker/overlay2/2c4c43e9f57a0cc4bcb440483d784d5de24502ea979bad82e8ca4eb0379eb855/diff:/var/lib/docker/overlay2/4b2b1d21296eac3283284faa97b977c23a08bb359611b32fac7714044da40817/diff", "MergedDir": "/var/lib/docker/overlay2/fda7ce03d0948fef20e0132be71f59a87159da9dc8745bf1faaa931ea9ca5cf2/merged", "UpperDir": "/var/lib/docker/overlay2/fda7ce03d0948fef20e0132be71f59a87159da9dc8745bf1faaa931ea9ca5cf2/diff", "WorkDir": "/var/lib/docker/overlay2/fda7ce03d0948fef20e0132be71f59a87159da9dc8745bf1faaa931ea9ca5cf2/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:5f1ee22ffb5e68686db3dcb6584eb1c73b5570615b0f14fabb070b96117e351d", "sha256:13538303ed9cedfa748390cad44880ed7002ff88467c351388a8df331a989662", "sha256:05df6742558bf5c12ca2109e48499d5d5103127803e51f85502e1b223ce0df00", "sha256:210d1b841410c5bd137db34f6d54df88e9c777b9fd3df8fe2675b128dbb3485b" ] }, "Metadata": { "LastTagTime": "0001-01-01T00:00:00Z" } } ]
他 (ベースイメージ: mcr.microsoft.com/dotnet/runtime
)
実行例
$ dotnet publish --os linux --arch x64 -c Release /t:PublishContainer -p:ContainerImageTag=1.0 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE container-app-23 1.0 862527cad6f1 6 seconds ago 293MB
コンテナイメージを調整する
Dockerfileでいうところの以下の調整ができます。詳しくはドキュメントを見るとして、ベースイメージ、ラベル、EXPOSE、ENV、USERあたりは経験上操作する可能性があるので見ておきましょう。
- FROM
- LABEL
- WORKDIR
- EXPOSE
- ENV
- USER
- ENTRYPOINT
ベースイメージを変更する
いわゆるFROM
相当です。ベースイメージはコマンドライン--os
、ContainerFamily
、ContainerBaseImage
のいずれかで設定できます。
C#においてもベースイメージを変更することでコンテナイメージサイズは大きく変わるため、サイズ削減に取り組むときは検討するといいでしょう。検証結果を見るとaspnet:8.0-jammy
で非圧縮217MBがaspnet:8.0-jammy-chiseled
で111MBになります。
--osでディストロを指定する
--os ディストロ
で指定したディストロイメージを使うこともできます。例えば、--os linux-musl
でalpine
イメージを使うことができます。
$ dotnet publish --os linux-musl -c Release /t:PublishContainer Restore complete (0.3s) ConsoleApp23 succeeded (2.1s) → bin\Release\net9.0\linux-musl-x64\publish\ Build succeeded in 2.7s $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE container-app-23 latest e48a67df6cbc 16 seconds ago 130MB
ContainerFamilyでイメージを指定する
例えばchiseledイメージを使う場合、次のように指定します。8.0ならjammy
、9.0ならnoble
と違うことに気を付けてください。めんどくさい。ICUやtzdataを使えないので、大抵は後述している-extra
かデフォルトイメージのほうが使いやすいでしょう。
$ dotnet publish -t:PublishContainer -c Release -r linux-amd64 -p:ContainerFamily=noble-chiseled Restore complete (0.2s) ConsoleApp23 succeeded (5.7s) → bin\Release\net9.0\linux-x64\publish\ Build succeeded in 6.2s $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE container-app-23 latest bb555d7fcc63 21 seconds ago 133MB
ラベルを設定する
LABEL
相当です。ItemGroupでContainerLabel
を指定します。5
コンテナのラベルは外からどのような用途のものか識別するのに使われることが多いです。dotnet publish
でイメージ作成する場合、デフォルトラベルが設定さますが、これに加えて独自ラベルを設定できます。例えば、git-sha
やgit-branch
などを設定しておくと便利だったりします。
<ItemGroup> <ContainerLabel Include="git-sha" Value="abcd123" /> <ContainerLabel Include="git-branch" Value="FooBar" /> </ItemGroup>
$ dotnet publish --os linux --arch x64 -c Release /t:PublishContainer $ docker image inspect container-app-23:latest [ { { // 省略 "Labels": { "git-branch": "FooBar", "git-sha": "abcd123", "net.dot.runtime.majorminor": "9.0", "net.dot.sdk.version": "9.0.200", "org.opencontainers.artifact.created": "2025-03-10T13:06:42.0776844Z", "org.opencontainers.image.authors": "ConsoleApp23", "org.opencontainers.image.base.digest": "sha256:e0ac714c1c99cf2b30e655ebd0f4bd6736b1d960a21b2c061711e43474aa5632", "org.opencontainers.image.base.name": "mcr.microsoft.com/dotnet/runtime:9.0", "org.opencontainers.image.created": "2025-03-10T13:06:42.0776844Z", "org.opencontainers.image.version": "1.0.0" } }, "Architecture": "amd64", "Os": "linux", // 省略 } ]
コンテナの公開ポートを設定する
EXPOSE
相当です。ItemGroupでContainerPort
を指定します。また、ASPNETCORE_
から始まる定番の環境変数でポートが類推されます。
- ASPNETCORE_URLS
- ASPNETCORE_HTTP_PORTS
- ASPNETCORE_HTTPS_PORTS
直接指定する場合は次のようにIncludeとTypeで設定します。
<ItemGroup> <ContainerPort Include="8080" Type="tcp"/> </ItemGroup>
コンテナの環境変数を設定する
ENV
相当です。ItemGroupでContainerEnvironmentVariable
を指定します。
制約として、コマンドラインからdotnet publish -p:ContainerEnvironmentVariable=
のように設定できないことがIssueで報告されています。
<ItemGroup> <ContainerEnvironmentVariable Include="FOOBAR" Value="Trace" /> </ItemGroup>
FOOBARという環境変数が設定されていますね。
$ dotnet publish --os linux --arch x64 -c Release /t:PublishContainer $ docker image inspect container-app-23:latest [ { { // 省略 "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "APP_UID=1654", "ASPNETCORE_HTTP_PORTS=8080", "DOTNET_RUNNING_IN_CONTAINER=true", "DOTNET_VERSION=9.0.2", "FOOBAR=Trace" ], } // 省略 } ]
USERの設定
USER
相当です。ItemGroupでContainerUser
を指定します。
.NET 8から.NETのコンテナはnon-rootを果たしました。このため、rootではなくapp
ユーザー(UID/GIDは1654
[^5])で動作します。non-rootならばUSERを変更する理由はあまりないはずですが、独自USERでやってる場合にどうぞ。
$ docker run -it --entrypoint /bin/bash container-app-23 app@0e671b15e9f6:/app$ whoami app
もしもrootに変更したい場合は次のように設定します。
<ItemGroup> <ContainerUser Include="root" /> </ItemGroup>
あるいはコマンドラインでも設定できます。
$ dotnet publish -t:PublishContainer -c Release -r linux-amd64 -p:ContainerUser=root $ docker run -it --entrypoint /bin/bash container-app-23 root@ec6dac910244:/app# whoami root
FAQ
いくつかドキュメントに明示記載されていない挙動について検証した結果を残しておきます。
RUNのサポートはないのか
ありません、Dockerfileを使うことになります。例えばgrpc_health_probeを使わざるを得ない場合は、Dockerfile一択になります。
Microsoft.NET.Build.Containersパッケージ
ドキュメントでは、Microsoft.NET.Build.Containers
パッケージを追加するケースも記載されています。.NET9ではこれがなくても問題なくビルドできるようです。
$ dotnet new console -o ConsoleApp24 $ cd ConsoleApp24 $ dotnet add package Microsoft.Net.Build.Containers $ dotnet publish --os linux --arch x64 -c Release /t:PublishContainer Restore complete (0.3s) ConsoleApp24 succeeded (1.2s) → bin\Release\net9.0\linux-x64\publish\ Build succeeded in 1.8s # コンテナイメージが作成されない $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE
ただ.NET 8でPublishAot=true
をする場合、あったほうが安定してビルドできるようです。この時、自身のSDKバージョンに合わせたMicrosoft.NET.Build.Containers
バージョンをインストールするようにしましょう。
$ dotnet --list-sdks 8.0.112 [/usr/lib/dotnet/sdk] $ cat ConsoleApp24.csproj # 省略 <ItemGroup> <PackageReference Include="Microsoft.NET.Build.Containers" Version="8.0.112" /> </ItemGroup>
dotnet publish時のプロファイルはmsbuildで直接指定する
dotnet publish
時は/t:PublishContainer
を使ってコンテナイメージを作ると、どのようなプロジェクトでも安定してコンテナイメージを作成できます。
Microsoft LearnやGitHub Issue、あるいはGitHubの適当な文章を見ると、dotnet publish -p:PublishProfile=DefaultContainer
や-t:PublishContainer
を使ってコンテナイメージを作成する方法が記載されていますが、ConsoleAppなどデフォルトでサポートされていないプラットフォームでコンテナイメージを作成できません。Web SDKプロジェクトなら-t:PublishContainer
でイメージ作成されるのですが、ConsoleAppプロジェクトなどで-t:PublishContainer
を使ってもイメージ作成されません。
# 微妙 (ConsoleAppだと発行できない) $ dotnet publish --os linux --arch x64 -c Release -p:PublishProfile=DefaultContainer Restore complete (0.8s) ConsoleApp19 succeeded with 1 warning(s) (1.3s) → bin\Release\net9.0\linux-x64\publish\ C:\Program Files\dotnet\sdk\9.0.200\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Publish.targets(208,5): warning NETSDK1198: A publish profile with the name 'DefaultContainer' was not found in the project. Set the PublishProfile property to a valid file name. $ docker image ls # 微妙 (こっちも同じくConsoleAppだと発行できない) $ dotnet publish --os linux --arch x64 -c Release -t:PublishContainer Restore complete (0.2s) ConsoleApp23 succeeded (0.1s) → bin\Release\net9.0\linux-x64\publish\ Build succeeded in 0.5s $ docker image ls
一方、/t:PublishContainer
を使うとイメージが作成できています。
# 安定 $ dotnet publish --os linux --arch x64 -c Release /t:PublishContainer Restore complete (0.2s) ConsoleApp19 succeeded (8.3s) → bin\Release\net9.0\linux-x64\publish\ Build succeeded in 8.7s $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE consoleapp19 latest c5c9f37a3a4a 14 seconds ago 293MB $ docker run -it consoleapp19 Hello, World!
ICU/tzdataが含まれるイメージか注意する
ベースイメージ変更時、alpine
やdistroless
、ubuntu chiseled
イメージはICUやtzdataを含まないため国際対応で困ることが予想されます。csprojの設定がInvariantGlobalization=true
ならこのイメージでいいのですが、ICUやtzdataを含む適切なイメージを使うといいでしょう。[^7]
<!-- falseを指定しても-extraイメージは自動選択されない --> <PropertyGroup> <InvariantGlobalization>false</InvariantGlobalization> </PropertyGroup>
ICUやtzdataが問題ないかは、globalappのような実行を使うといいです。実行例を見てみましょう。
デフォルトイメージの実行例
デフォルトイメージにはICU/tzdataが含まれるので、問題なく出力できます。
$ dotnet publish -t:PublishContainer -c Release -r linux-amd64 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE container-app-23 latest c01119c971f4 3 minutes ago 293MB $ docker run -it container-app-23 Hello, World! ****Print baseline timezones** Utc: (UTC) Coordinated Universal Time; 03/10/2025 13:36:22 Local: (UTC) Coordinated Universal Time; 03/10/2025 13:36:22 ****Print specific timezone** Home timezone: America/Los_Angeles DateTime at home: 03/10/2025 06:36:22 ****Culture-specific dates** Current: 03/10/2025 English (United States) -- en-US: 3/10/2025 1:36:22 PM 3/10/2025 1:36 PM English (Canada) -- en-CA: 3/10/2025 1:36:22 p.m. 3/10/2025 1:36 p.m. French (Canada) -- fr-CA: 2025-03-10 13 h 36 min 22 s 2025-03-10 13 h 36 Croatian (Croatia) -- hr-HR: 10. 03. 2025. 13:36:22 10. 03. 2025. 13:36 jp (Japan) -- jp-JP: 3/10/2025 13:36:22 3/10/2025 13:36 Korean (South Korea) -- ko-KR: 2025. 3. 10. 오후 1:36:22 2025. 3. 10. 오후 1:36 Portuguese (Brazil) -- pt-BR: 10/03/2025 13:36:22 10/03/2025 13:36 Chinese (China) -- zh-CN: 2025/3/10 13:36:22 2025/3/10 13:36 ****Culture-specific currency:** Current: ¤1,337.00 en-US: $1,337.00 en-CA: $1,337.00 fr-CA: 1 337,00 $ hr-HR: 1.337,00 € jp-JP: ¥ 1337 ko-KR: ₩1,337 pt-BR: R$ 1.337,00 zh-CN: ¥1,337.00 ****Japanese calendar** 08/18/2019 01/08/18 平成元年8月18日 平成元年8月18日 ****String comparison** Comparison results: `0` mean equal, `-1` is less than and `1` is greater Test: compare i to (Turkish) İ; first test should be equal and second not 0 -1 Test: compare Å Å; should be equal 0
chiseledイメージの実行例
chiseledイメージにはICU/tzdataが含まれないため、tzdataへアクセスできずエラーが発生します。これはICUでも同様にトラブルを起こします。
$ dotnet publish -t:PublishContainer -c Release -r linux-amd64 -p:ContainerFamily=noble-chiseled $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE container-app-23 latest 8a083166147b 4 seconds ago 133MB $ docker run -it container-app-23 Hello, World! ****Print baseline timezones** Utc: (UTC) Coordinated Universal Time; 03/10/2025 13:35:54 Local: (UTC) Coordinated Universal Time; 03/10/2025 13:35:54 ****Print specific timezone** Unhandled exception. System.TimeZoneNotFoundException: The time zone ID 'America/Los_Angeles' was not found on the local computer. ---> System.IO.DirectoryNotFoundException: Could not find a part of the path '/usr/share/zoneinfo/America/Los_Angeles'. at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirError) at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String path, OpenFlags flags, Int32 mode, Boolean failForSymlink, Boolean& wasSymlink, Func`4 createOpenException) at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, UnixFileMode openPermissions, Int64& fileLength, UnixFileMode& filePermissions, Boolean failForSymlink, Boolean& wasSymlink, Func`4 createOpenException) at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode) at System.TimeZoneInfo.ReadAllBytesFromSeekableNonZeroSizeFile(String path, Int32 maxFileSize) at System.TimeZoneInfo.TryGetTimeZoneFromLocalMachineCore(String id, TimeZoneInfo& value, Exception& e) --- End of inner exception stack trace --- at System.TimeZoneInfo.FindSystemTimeZoneById(String id) at Program.<Main>$(String[] args) in C:\Users\guitarrapc\source\repos\ConsoleApp23\Program.cs:line 24
chiseled-extraイメージの実行例
chiseledイメージでも-extra
をつけたものはICU/tzdataが含まれているため、問題なく出力できます。
$ dotnet publish -t:PublishContainer -c Release -r linux-amd64 -p:ContainerFamily=noble-chiseled-extra Restore complete (0.2s) ConsoleApp23 succeeded (10.5s) → bin\Release\net9.0\linux-x64\publish\ Build succeeded in 10.9s $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE container-app-23 latest b2db2bfebb37 20 seconds ago 190MB $ docker run -it container-app-23 Hello, World! ****Print baseline timezones** Utc: (UTC) Coordinated Universal Time; 03/10/2025 13:34:50 Local: (UTC) Coordinated Universal Time; 03/10/2025 13:34:50 ****Print specific timezone** Home timezone: America/Los_Angeles DateTime at home: 03/10/2025 06:34:50 ****Culture-specific dates** Current: 03/10/2025 English (United States) -- en-US: 3/10/2025 1:34:50 PM 3/10/2025 1:34 PM English (Canada) -- en-CA: 2025-03-10 1:34:50 p.m. 2025-03-10 1:34 p.m. French (Canada) -- fr-CA: 2025-03-10 13 h 34 min 50 s 2025-03-10 13 h 34 Croatian (Croatia) -- hr-HR: 10. 03. 2025. 13:34:50 10. 03. 2025. 13:34 jp (Japan) -- jp-JP: 3/10/2025 13:34:50 3/10/2025 13:34 Korean (South Korea) -- ko-KR: 2025. 3. 10. 오후 1:34:50 2025. 3. 10. 오후 1:34 Portuguese (Brazil) -- pt-BR: 10/03/2025 13:34:50 10/03/2025 13:34 Chinese (China) -- zh-CN: 2025/3/10 13:34:50 2025/3/10 13:34 ****Culture-specific currency:** Current: ¤1,337.00 en-US: $1,337.00 en-CA: $1,337.00 fr-CA: 1 337,00 $ hr-HR: 1.337,00 € jp-JP: ¥ 1337 ko-KR: ₩1,337 pt-BR: R$ 1.337,00 zh-CN: ¥1,337.00 ****Japanese calendar** 08/18/2019 01/08/18 平成元年8月18日 平成元年8月18日 ****String comparison** Comparison results: `0` mean equal, `-1` is less than and `1` is greater Test: compare i to (Turkish) İ; first test should be equal and second not 0 -1 Test: compare Å Å; should be equal 0
まとめ
Dockerfileじゃなくてもdotnet publish
でのコンテナ作成はずいぶんといい感じになってきていました。Dockerfileは必ずしも意識しないといけないものではないので、言語SDKからコンテナ作成できるのは便利です。ただ、RUN
のサポートがないことや、マルチプラットフォームイメージを作成する際にプラットフォームイメージを省略できないのは使いにくさがあります。
.NET 8のnon-root化やchiseledイメージをはじめとして、.NETのコンテナエコシステムも着実によくなってきているので、今後も期待していきたいですね。ただ、コンテナに任せていた部分は、今後WASMサンドボックスの発展で置き換わっていくとより良いなぁとも感じます。
参考
- .NET Core buildpack | Cloud Foundry Docs
- jib - DockerなしでDockerイメージを生成 #Kotlin | Qiita
- Announcing built-in container support for the .NET SDK | .NET Blog
- Streamline your container build and publish with .NET 8 | .NET Blog
- Secure your container build and publish with .NET 8 | .NET Blog
- Announcing .NET Chiseled Containers | .NET Blog
- dotnet-docker/documentation/distroless.md at main | dotnet/dotnet-docker
- Containerize a .NET app reference - .NET | Microsoft Learn
- Containerize a .NET app reference - .NET | Microsoft Learn
- dotnet-docker/documentation/sample-image-size-report.md at main | dotnet/dotnet-docker
- sdk-container-builds/docs/RegistryAuthentication.md at main | dotnet/sdk-container-builds
- sdk-container-builds/docs/ZeroToContainer.md at main | dotnet/sdk-container-builds
- dotnet-docker/samples/aspnetapp at main | dotnet/dotnet-docker
Issue
- Cannot specify ContainerEnvironmentVariable via the command line using -p:ContainerEnvironmentVariable=? · Issue #451 | dotnet/sdk-container-builds
- Unable to publish console app as container · Issue #551 | dotnet/sdk-container-builds
- Error on publish when upload to aws ecr · Issue #424 | dotnet/sdk-container-builds