チュートリアル: .NET アプリのコンテナー化

このチュートリアルでは、Docker を使用して .NET アプリケーションをコンテナー化する方法について説明します。 コンテナーには、変更できないインフラストラクチャになったり、移植可能なアーキテクチャを提供したり、スケーラビリティを可能にしたりといった、さまざまな特徴とベネフィットがあります。 そのイメージを使って、ローカル開発環境、プライベート クラウド、またはパブリック クラウド用にコンテナーを作成することができます。

このチュートリアルでは、次の作業を行いました。

  • 簡単な .NET アプリを作成して発行する
  • .NET 用の Dockerfile を作成して構成する
  • Docker イメージの構築
  • Docker コンテナーを作成して実行する

.NET アプリケーション用に Docker コンテナーを構築してデプロイするタスクについて説明します。 "Docker プラットフォーム" では、"Docker エンジン" を使用して、すばやくアプリがビルドされ、"Docker イメージ" としてパッケージ化されます。 これらのイメージは、階層型コンテナーに展開されて実行される Dockerfile 形式で記述されています。

メモ

このチュートリアルは ASP.NET Core アプリ向けのものではありません。 ASP.NET Core を使用している場合は、ASP.NET Core アプリケーションをコンテナー化する方法に関するチュートリアルを参照してください。

必須コンポーネント

次の前提条件をインストールします。

  • .NET 8+ SDK
    .NET がインストールされている場合は、dotnet --info コマンドを使用して、使用している SDK を特定します。
  • Docker Community Edition
  • Dockerfile と .NET サンプル アプリ用の一時作業フォルダー。 このチュートリアルでは、作業フォルダーに docker-working の名前が使用されます。
  • .NET 7+ SDK
    .NET がインストールされている場合は、dotnet --info コマンドを使用して、使用している SDK を特定します。
  • Docker Community Edition
  • Dockerfile と .NET サンプル アプリ用の一時作業フォルダー。 このチュートリアルでは、作業フォルダーに docker-working の名前が使用されます。

.NET アプリの作成

Docker コンテナーが実行される .NET アプリが必要です。 ターミナルを開き、作業フォルダーがまだない場合は作成して、移動します。 作業フォルダーで次のコマンドを実行し、App という名前のサブディレクトリに新しいプロジェクトを作成します。

dotnet new console -o App -n DotNet.Docker

フォルダー ツリーは次のようになります:

📁 docker-working
    └──📂 App
        ├──DotNet.Docker.csproj
        ├──Program.cs
        └──📂 obj
            ├── DotNet.Docker.csproj.nuget.dgspec.json
            ├── DotNet.Docker.csproj.nuget.g.props
            ├── DotNet.Docker.csproj.nuget.g.targets
            ├── project.assets.json
            └── project.nuget.cache

dotnet new コマンドでは、"App" という名前の新しいフォルダーが作成され、"Hello World" コンソール アプリケーションが生成されます。 ディレクトリを変更し、ターミナル セッションから "App" フォルダーに移動します。 dotnet run コマンドを使用してアプリを起動します。 アプリケーションが実行され、コマンドの下に Hello World! 出力されます:

cd App
dotnet run
Hello World!

既定のテンプレートでは、ターミナルに出力した後、すぐに終了するアプリが作成されます。 このチュートリアルでは、無限にループするアプリを使います。 テキスト エディターで Program.cs ファイルを開きます。

ヒント

Visual Studio Code を使用している場合は、前のターミナル セッションで次のコマンドを入力します。

code .

これにより、Visual Studio Code のプロジェクトを含む "App" フォルダーが開きます。

"Program.cs" は次のような C# コードになります。

Console.WriteLine("Hello World!");

1 秒ごとに数をカウントする次のコードにファイルを置き換えます。

var counter = 0;
var max = args.Length is not 0 ? Convert.ToInt32(args[0]) : -1;
while (max is -1 || counter < max)
{
    Console.WriteLine($"Counter: {++counter}");
    await Task.Delay(TimeSpan.FromMilliseconds(1_000));
}
var counter = 0;
var max = args.Length is not 0 ? Convert.ToInt32(args[0]) : -1;
while (max is -1 || counter < max)
{
    Console.WriteLine($"Counter: {++counter}");
    await Task.Delay(TimeSpan.FromMilliseconds(1_000));
}

ファイルを保存し、dotnet run で再びプログラムをテストします。 このアプリは無限に実行されることに注意してください。 停止するにはキャンセル コマンド Ctrl + C を使用します。 次に出力例を示します。

dotnet run
Counter: 1
Counter: 2
Counter: 3
Counter: 4
^C

コマンド ラインでアプリに数値を渡すと、アプリはその値までカウント アップしただけで終了します。 dotnet run -- 5 で 5 までカウントしてみてください。

重要

-- より後のパラメーターは dotnet run コマンドに渡されず、代わりにアプリケーションに渡されます。

.NET アプリを発行する

.NET アプリを Docker イメージに追加する前に、まず発行する必要があります。 アプリの発行済みバージョンをコンテナーで実行することをお勧めします。 アプリを発行するには、次のコマンドを実行します。

dotnet publish -c Release

このコマンドでは、publish フォルダーにアプリがコンパイルされます。 作業フォルダーから publish フォルダーへのパスは .\App\bin\Release\net8.0\publish\ です。

このコマンドでは、publish フォルダーにアプリがコンパイルされます。 作業フォルダーから publish フォルダーへのパスは .\App\bin\Release\net7.0\publish\ です。

App フォルダーから、publish フォルダーのディレクトリ一覧を表示し、DotNet.Docker.dll ファイルが作成されたことを確認します。

dir .\bin\Release\net8.0\publish\

    Directory: C:\Users\default\App\bin\Release\net8.0\publish

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           9/22/2023  9:17 AM            431 DotNet.Docker.deps.json
-a---           9/22/2023  9:17 AM           6144 DotNet.Docker.dll
-a---           9/22/2023  9:17 AM         157696 DotNet.Docker.exe
-a---           9/22/2023  9:17 AM          11688 DotNet.Docker.pdb
-a---           9/22/2023  9:17 AM            353 DotNet.Docker.runtimeconfig.json
dir .\bin\Release\net7.0\publish\

    Directory: C:\Users\default\App\bin\Release\net7.0\publish

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           2/13/2023  1:52 PM            431 DotNet.Docker.deps.json
-a---           2/13/2023  1:52 PM           6144 DotNet.Docker.dll
-a---           2/13/2023  1:52 PM         153600 DotNet.Docker.exe
-a---           2/13/2023  1:52 PM          11052 DotNet.Docker.pdb
-a---           2/13/2023  1:52 PM            253 DotNet.Docker.runtimeconfig.json

Dockerfile を作成する

Dockerfile ファイルは、コンテナー イメージを作成するために docker build コマンドによって使用されます。 このファイルは Dockerfile という名前のテキスト ファイルで、拡張子はありません。

.csproj が含まれるディレクトリに Dockerfile という名前のファイルを作成し、テキスト エディターで開きます。 このチュートリアルでは、ASP.NET Core ランタイム イメージ (.NET ランタイム イメージを含む) を使用して、.NET コンソール アプリケーションに対応します。

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
WORKDIR /App

# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -c Release -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]

Note

mcr.microsoft.com/dotnet/runtime:8.0 イメージが使用されている可能性もありますが、ここでは ASP.NET Core ランタイム イメージを意図的に使用します。

ヒント

この Dockerfile はマルチステージ ビルドを使用します。これにより、ビルドをレイヤー化して必要な成果物のみを残すことで、イメージの最終的なサイズが最適化されます。 詳細については、「Docker Docs: マルチステージ ビルド」を参照してください。

FROM キーワードには、完全修飾 Docker コンテナー イメージ名が必要です。 Microsoft Container Registry (MCR、mcr.microsoft.com) は、パブリック アクセスが可能なコンテナーをホストする Docker Hub のシンジケートです。 dotnet セグメントはコンテナー リポジトリで、sdk または aspnet セグメントはコンテナー イメージの名前です。 イメージには 8.0 のタグが付けられ、バージョン管理に使用されます。 このため、mcr.microsoft.com/dotnet/aspnet:8.0 は .NET 8.0 ランタイムです。 必ず、SDK のターゲットのランタイムと一致するランタイム バージョンをプルしてください。 たとえば、前のセクションで作成したアプリでは .NET 8.0 SDK が使用されているため、Dockerfile で参照される基本イメージは 8.0 でタグ付けされます。

重要

Windows ベースのコンテナー イメージを使う場合は、単なる 8.0 ではないイメージ タグを指定する必要があります (たとえば、mcr.microsoft.com/dotnet/aspnet:8.0 ではなく mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-1809)。 Nano Server または Windows Server Core のどちらを使用しているか、およびその OS のバージョンに基づいてイメージ名を選択します。 .NET の Docker Hub ページで、サポートされているすべてのタグの完全な一覧を確認できます。

Dockerfile ファイルを保存します。 作業フォルダーのディレクトリ構造は次のようになります。 一部のより深いレベルのファイルとフォルダーは、スペース削減のためこの記事では省略されています。

📁 docker-working
    └──📂 App
        ├── Dockerfile
        ├── DotNet.Docker.csproj
        ├── Program.cs
        ├──📂 bin
        │   └──📂 Release
        │       └──📂 net8.0
        │           └──📂 publish
        │               ├── DotNet.Docker.deps.json
        │               ├── DotNet.Docker.exe
        │               ├── DotNet.Docker.dll
        │               ├── DotNet.Docker.pdb
        │               └── DotNet.Docker.runtimeconfig.json
        └──📁 obj
            └──...
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env
WORKDIR /App

# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -c Release -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]

Note

mcr.microsoft.com/dotnet/runtime:7.0 イメージが使用されている可能性もありますが、ここでは ASP.NET Core ランタイム イメージを意図的に使用します。

ヒント

この Dockerfile はマルチステージ ビルドを使用します。これにより、ビルドをレイヤー化して必要な成果物のみを残すことで、イメージの最終的なサイズが最適化されます。 詳細については、「Docker Docs: マルチステージ ビルド」を参照してください。

FROM キーワードには、完全修飾 Docker コンテナー イメージ名が必要です。 Microsoft Container Registry (MCR、mcr.microsoft.com) は、パブリック アクセスが可能なコンテナーをホストする Docker Hub のシンジケートです。 dotnet セグメントはコンテナー リポジトリで、sdk または aspnet セグメントはコンテナー イメージの名前です。 イメージには 7.0 のタグが付けられ、バージョン管理に使用されます。 このため、mcr.microsoft.com/dotnet/aspnet:7.0 は .NET 7.0 ランタイムです。 必ず、SDK のターゲットのランタイムと一致するランタイム バージョンをプルしてください。 たとえば、前のセクションで作成したアプリでは .NET 7.0 SDK が使用されているため、Dockerfile で参照される基本イメージは 7.0 でタグ付けされます。

Dockerfile ファイルを保存します。 作業フォルダーのディレクトリ構造は次のようになります。 一部のより深いレベルのファイルとフォルダーは、スペース削減のためこの記事では省略されています。

📁 docker-working
    └──📂 App
        ├── Dockerfile
        ├── DotNet.Docker.csproj
        ├── Program.cs
        ├──📂 bin
        │   └──📂 Release
        │       └──📂 net7.0
        │           └──📂 publish
        │               ├── DotNet.Docker.deps.json
        │               ├── DotNet.Docker.exe
        │               ├── DotNet.Docker.dll
        │               ├── DotNet.Docker.pdb
        │               └── DotNet.Docker.runtimeconfig.json
        └──📁 obj
            └──...

ターミナルから、次のコマンドを実行します。

docker build -t counter-image -f Dockerfile .

Docker により Dockerfile 内の各行が処理されます。 docker build コマンドの . を使って、イメージのビルド コンテキストを設定します。 -f スイッチは Dockerfile へのパスです。 このコマンドでは、イメージがビルドされて、そのイメージを指す counter-image という名前のローカル リポジトリが作成されます。 このコマンドが終了したら、docker images を実行し、インストールされているイメージの一覧を表示します。

docker images
REPOSITORY                         TAG       IMAGE ID       CREATED          SIZE
counter-image                      latest    2f15637dc1f6   10 minutes ago   217MB

counter-image リポジトリはイメージの名前です。 latest タグは、イメージの識別に使われるタグです。 2f15637dc1f6 はイメージ ID です。 10 minutes ago はイメージが作成された時間です。 217MB はイメージのサイズです。 Dockerfile の最後の手順は、イメージからコンテナーを作成してアプリを実行し、発行されたアプリをコンテナーにコピーし、エントリ ポイントを定義することです。

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
docker images
REPOSITORY                         TAG       IMAGE ID       CREATED          SIZE
counter-image                      latest    2f15637dc1f6   10 minutes ago   208MB

counter-image リポジトリはイメージの名前です。 latest タグは、イメージの識別に使われるタグです。 2f15637dc1f6 はイメージ ID です。 10 minutes ago はイメージが作成された時間です。 208MB はイメージのサイズです。 Dockerfile の最後の手順は、イメージからコンテナーを作成してアプリを実行し、発行されたアプリをコンテナーにコピーし、エントリ ポイントを定義することです。

FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]

COPY コマンドは、自分のコンピューターの指定したフォルダーをコンテナー内のフォルダーにコピーするよう Docker に指示します。 この例では、publish フォルダーが、コンテナーの App/out という名前のフォルダーにコピーされています。

WORKDIR コマンドでは、コンテナー内の現在のディレクトリが、"App" に変更されます。

次のコマンド ENTRYPOINT は、実行可能ファイルとして実行するためにコンテナーを構成するよう Docker に指示します。 コンテナーの起動時に、ENTRYPOINT コマンドが実行されます。 このコマンドが終了すると、コンテナーは自動的に停止します。

ヒント

.NET 8 より前で、読み取り専用として実行するように構成されたコンテナーは Failed to create CoreCLR, HRESULT: 0x8007000E で失敗する可能性があります。 この問題に対処するには、(ENTRYPOINT 手順の直前に) DOTNET_EnableDiagnostics 環境変数に 0 を指定します。

ENV DOTNET_EnableDiagnostics=0

さまざまな .NET 環境変数の詳細については、「.NET 環境変数」を参照してください。

メモ

.NET 6 では、.NET の実行時の動作を構成する環境変数のプレフィックスが、COMPlus_ ではなく DOTNET_ に標準化されています。 ただし、プレフィックス COMPlus_ は引き続き機能します。 以前のバージョンの .NET ランタイムを使用している場合は、環境変数に COMPlus_ プレフィックスをまだ使用する必要があります。

コンテナーの作成

アプリを含むイメージができたので、コンテナーを作成することができます。 2 つの方法でコンテナーを作成することができます。 最初に、停止している新しいコンテナーを作成します。

docker create --name core-counter counter-image

この docker create コマンドでは、counter-image イメージに基づくコンテナーが作成されます。 そのコマンドの出力に、作成されたコンテナーの CONTAINER ID (個々に異なります) が示されます。

d0be06126f7db6dd1cee369d911262a353c9b7fb4829a0c11b4b2eb7b2d429cf

"すべて" のコンテナーの一覧を表示するには、docker ps -a コマンドを使います。

docker ps -a
CONTAINER ID   IMAGE           COMMAND                  CREATED          STATUS    PORTS     NAMES
d0be06126f7d   counter-image   "dotnet DotNet.Docke…"   12 seconds ago   Created             core-counter

コンテナーを管理する

コンテナーが特定の名前 core-counter で作成されました。この名前を使用してコンテナーを管理します。 次の例では、docker start コマンドを使ってコンテナーを起動した後、docker ps コマンドを使って、実行されているコンテナーのみを表示します。

docker start core-counter
core-counter

docker ps
CONTAINER ID   IMAGE           COMMAND                  CREATED          STATUS          PORTS     NAMES
cf01364df453   counter-image   "dotnet DotNet.Docke…"   53 seconds ago   Up 10 seconds             core-counter

同様に、docker stop コマンドではコンテナーを停止します。 次の例では、docker stop コマンドを使ってコンテナーを停止した後、docker ps コマンドを使って、実行されているコンテナーがないことを示します。

docker stop core-counter
core-counter

docker ps
CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES

コンテナーへの接続

コンテナーが実行状態になった後、それに接続して出力を確認できます。 docker start コマンドを使ってコンテナーを起動し、docker attach コマンドを使って出力ストリームを表示します。 この例では、Ctrl+C キー入力を使用して、実行中のコンテナーからデタッチします。 このキー入力では、コンテナーを停止するように指定されている場合を除き、コンテナー内のプロセスが終了します。 --sig-proxy=false パラメーターを指定すると、Ctrl + C キーを押してもコンテナー内のプロセスが停止しないことが保証されます。

コンテナーからデタッチした後、再アタッチして、まだ実行されていてカウントが行われていることを確認します。

docker start core-counter
core-counter

docker attach --sig-proxy=false core-counter
Counter: 7
Counter: 8
Counter: 9
^C

docker attach --sig-proxy=false core-counter
Counter: 17
Counter: 18
Counter: 19
^C

コンテナーを削除する

この記事では、何も実行しないコンテナーを放置しないようにします。 前に作成したコンテナーを削除します。 コンテナーが実行されている場合は、それを停止します。

docker stop core-counter

次の例では、すべてのコンテナーを一覧表示します。 次に、docker rm コマンドを使ってコンテナーを削除した後、もう一度実行中のコンテナーを確認します。

docker ps -a
CONTAINER ID    IMAGE            COMMAND                   CREATED          STATUS                        PORTS    NAMES
2f6424a7ddce    counter-image    "dotnet DotNet.Dock…"    7 minutes ago    Exited (143) 20 seconds ago            core-counter

docker rm core-counter
core-counter

docker ps -a
CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES

単一実行

Docker では、1 つのコマンドとしてコンテナーを作成して実行するための docker run コマンドが提供されています。 このコマンドでは、docker create を実行してから docker start を実行する必要がありません。 コンテナーが停止したら自動的にコンテナーを削除するように、このコマンドを設定することもできます。 たとえば、docker run -it --rm を使うと 2 つのことが行われます。つまり、最初に現在の端末を使ってコンテナーに自動的に接続し、次にコンテナーが終了したらそれを削除します。

docker run -it --rm counter-image
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
^C

また、コンテナーは .NET アプリの実行にパラメーターを渡します。 .NET アプリに対して 3 つまでしかカウントしないように指示するには、3 を渡します。

docker run -it --rm counter-image 3
Counter: 1
Counter: 2
Counter: 3

docker run -it では、Ctrl+C キーを押すと、コンテナーで実行されているプロセスを停止し、さらにコンテナーを停止します。 --rm パラメーターを指定したので、プロセスが停止するとコンテナーは自動的に削除されます。 それが存在しないことを確認します。

docker ps -a
CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES

ENTRYPOINT を変更する

docker run コマンドでは、Dockerfile から ENTRYPOINT コマンドを変更し、そのコンテナーに対してのみ何か他のことを実行することもできます。 たとえば、次のコマンドを使うと bash または cmd.exe を実行できます。 必要に応じて、コマンドを編集します。

この例では、ENTRYPOINTcmd.exe に変更されています。 プロセスを終了してコンテナーを停止するには、Ctrl+C キーを押します。

docker run -it --rm --entrypoint "cmd.exe" counter-image

Microsoft Windows [Version 10.0.17763.379]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\>dir
 Volume in drive C has no label.
 Volume Serial Number is 3005-1E84

 Directory of C:\

04/09/2019  08:46 AM    <DIR>          app
03/07/2019  10:25 AM             5,510 License.txt
04/02/2019  01:35 PM    <DIR>          Program Files
04/09/2019  01:06 PM    <DIR>          Users
04/02/2019  01:35 PM    <DIR>          Windows
               1 File(s)          5,510 bytes
               4 Dir(s)  21,246,517,248 bytes free

C:\>^C

重要なコマンド

Docker には、コンテナーとイメージを作成、管理、操作するさまざまなコマンドが用意されています。 コンテナーの管理に不可欠な Docker コマンドは次のとおりです。

リソースをクリーンアップする

このチュートリアルでは、コンテナーとイメージを作成しました。 必要な場合は、これらのリソースを削除します。 次のコマンドを使います

  1. すべてのコンテナーを一覧表示します

    docker ps -a
    
  2. 実行しているコンテナーを名前で選んで停止します。

    docker stop core-counter
    
  3. コンテナーを削除します

    docker rm core-counter
    

次に、コンピューターに残しておきたくないイメージを削除します。 Dockerfile によって作成されたイメージを削除した後、Dockerfile が基にした .NET イメージを削除します。 IMAGE ID または REPOSITORY:TAG の書式に設定された文字列を使うことができます。

docker rmi counter-image:latest
docker rmi mcr.microsoft.com/dotnet/aspnet:8.0
docker rmi counter-image:latest
docker rmi mcr.microsoft.com/dotnet/aspnet:7.0

インストールされているイメージの一覧を表示するには、docker images コマンドを使います。

ヒント

イメージ ファイルは大きくなることがあります。 普通、アプリのテスト中および開発中に作成した一時的なコンテナーは削除します。 通常、ランタイムがインストールされた基本イメージは、そのランタイムを基にして他のイメージをビルドする予定がある場合は、残しておきます。

次のステップ