教學課程:容器化 .NET 應用程式
在本教學課程中,您將瞭解如何使用 Docker 將 .NET 應用程式容器化。 容器有許多功能和優點,例如不可變的基礎結構、提供可攜式架構,以及啟用延展性。 此映像可用來為本機開發環境、私人雲端或公用雲端建立容器。
在本教學課程中,您會:
- 建立和發佈簡單的 .NET 應用程式
- 建立及設定適用於 .NET 的 Dockerfile
- 建置 Docker 映像
- 建立和執行 Docker 容器
您可以探索 .NET 應用程式的 Docker 容器建置和部署工作。 Docker 平臺 使用 Docker 引擎 快速建置和封裝應用程式為 Docker 映像。 這些映像會以 Dockerfile 格式撰寫,以在分層容器中部署和執行。
提示
如果您想要將 .NET 應用程式發佈為容器,而不需要 Docker 或 Podman,請參閱使用 dotnet publish將 .NET 應用程式容器化
注意
本教學 不適用於 ASP.NET Core 應用程式。 如果您使用 ASP.NET Core,請參閱 瞭解如何將 ASP.NET Core 應用程式容器化 教學課程。
先決條件
安裝下列必要條件:
-
.NET 8+ 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!");
將檔案替換為以下每秒計數數字的程式碼:
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
再次測試程式。 請記住,此應用程式會無限期執行。 使用 cancel 命令 Ctrl+C 來停止它。 請考慮下列範例輸出:
dotnet run
Counter: 1
Counter: 2
Counter: 3
Counter: 4
^C
如果您將命令行上的數位傳遞給應用程式,它會將計數限制為該數量,然後結束。 嘗試使用 dotnet run -- 5
從一數到五。
重要
--
之後的任何參數都不會傳遞至 dotnet run
命令,而是傳遞至您的應用程式。
發佈 .NET 應用程式
為了讓應用程式適合建立映像,它必須編譯。
dotnet publish
命令最適合此命令,因為它會建置和發佈應用程式。 如需深入參考,請參閱 dotnet build 和 dotnet publish 命令檔。
dotnet publish -c Release
提示
如果您想要將 .NET 應用程式發佈為容器,而不需要 Docker,請參閱使用 dotnet publish將 .NET 應用程式容器化
dotnet publish
命令會將您的應用程式編譯至 publish 資料夾。 工作資料夾中 發佈 資料夾的路徑應 ./App/bin/Release/<TFM>/publish/:
從 [App] 資料夾中,取得發行資料夾的目錄清單,以確認已建立 DotNet.Docker.dll 檔案。
dir .\bin\Release\net9.0\publish\
Directory: C:\Users\default\docker-working\App\bin\Release\net9.0\publish
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 1/6/2025 10:11 AM 431 DotNet.Docker.deps.json
-a---- 1/6/2025 10:11 AM 6144 DotNet.Docker.dll
-a---- 1/6/2025 10:11 AM 145408 DotNet.Docker.exe
-a---- 1/6/2025 10:11 AM 11716 DotNet.Docker.pdb
-a---- 1/6/2025 10:11 AM 340 DotNet.Docker.runtimeconfig.json
dir .\bin\Release\net8.0\publish\
Directory: C:\Users\default\docker-working\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
建立 Dockerfile
docker build
命令會使用 Dockerfile 檔案來建立容器映像。 此檔案是名為 Dockerfile 的文字檔, 沒有擴展名。
在目錄中建立名為 Dockerfile 的檔案,其中包含 .csproj,並在文本編輯器中開啟它。 本教學課程使用 ASP.NET Core 執行時間映像(其中包含 .NET 運行時間映像),並與 .NET 控制台應用程式對應。
FROM mcr.microsoft.com/dotnet/sdk:9.0@sha256:3fcf6f1e809c0553f9feb222369f58749af314af6f063f389cbd2f913b4ad556 AS build
WORKDIR /App
# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:9.0@sha256:b4bea3a52a0a77317fa93c5bbdb076623f81e3e2f201078d89914da71318b5d8
WORKDIR /App
COPY --from=build /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
注意
此處刻意使用 ASP.NET Core 運行時映像,不過可以改用 mcr.microsoft.com/dotnet/runtime:9.0
映像。
FROM mcr.microsoft.com/dotnet/sdk:8.0@sha256:35792ea4ad1db051981f62b313f1be3b46b1f45cadbaa3c288cd0d3056eefb83 AS build
WORKDIR /App
# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0@sha256:6c4df091e4e531bb93bdbfe7e7f0998e7ced344f54426b7e874116a3dc3233ff
WORKDIR /App
COPY --from=build /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
注意
此處特意使用 ASP.NET Core 運行時映像,不過可以改用 mcr.microsoft.com/dotnet/runtime:8.0
映像。
重要
在 Dockerfile 中的映像標籤後面加入安全雜湊演算法(SHA)是最佳做法。 這可確保映像不會遭到竄改,而且映像與您預期的映像相同。 SHA 是影像的唯一標識碼。 如需詳細資訊,請參閱 Docker Docs:依摘要提取映射。
提示
此 Dockerfile 使用多階段組建,藉由分階段建置並只保留必要的產物,將映像的最終大小優化。 如需詳細資訊,請參閱 Docker Docs:多階段組建。
FROM
關鍵詞需要完整且符合資格的 Docker 容器映像名稱。 Microsoft Container Registry (MCR,mcr.microsoft.com) 是 Docker Hub 的一部分,承載可以公開存取的容器。
dotnet
區段是容器存放庫,而 sdk
或 aspnet
區段則是容器映像名稱。 圖像被標記為 9.0
,用於版本控制。 因此,mcr.microsoft.com/dotnet/aspnet:9.0
是 .NET 9.0 運行時間。 請確保您下載的執行時間版本與您的 SDK 所針對的執行時間版本一致。 例如,在上一節中建立的應用程式使用 .NET 9.0 SDK,而 Dockerfile 中參考的基底映射會以 9.0標記。
重要
使用以 Windows 為基礎的容器映像時,您必須指定映像標籤,不只限於 9.0
,例如,應使用 mcr.microsoft.com/dotnet/aspnet:9.0-nanoserver-1809
,而不是 mcr.microsoft.com/dotnet/aspnet:9.0
。 根據您使用的是 Nano Server 或 Windows Server Core,以及該作業系統的版本,選取映像名稱。 您可以在 .NET 的 Docker Hub 頁面上找到所有支援標記的完整清單
儲存 Dockerfile 檔案。 工作資料夾的目錄結構看起來應該如下所示。 一些更深層的檔案和資料夾會省略,以節省文章中的空間:
📁 docker-working
└──📂 App
├── Dockerfile
├── DotNet.Docker.csproj
├── Program.cs
├──📂 bin
│ └───📂 Release
│ └───📂 net9.0
│ ├───📂 publish
│ │ ├─── DotNet.Docker.deps.json
│ │ ├─── DotNet.Docker.dll
│ │ ├─── DotNet.Docker.exe
│ │ ├─── DotNet.Docker.pdb
│ │ └─── DotNet.Docker.runtimeconfig.json
│ ├─── DotNet.Docker.deps.json
│ ├─── DotNet.Docker.dll
│ ├─── DotNet.Docker.exe
│ ├─── DotNet.Docker.pdb
│ └─── DotNet.Docker.runtimeconfig.json
└──📁 obj
└──...
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-nanoserver-1809
,而非 mcr.microsoft.com/dotnet/aspnet:8.0
。 根據您使用的是 Nano Server 或 Windows Server Core,以及該作業系統的版本,選取映像名稱。 您可以在 .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
└──...
ENTRYPOINT
指令會將 dotnet
設定為 DotNet.Docker.dll
的主機。 不過,可以改為將 ENTRYPOINT
定義為應用程式可執行檔本身,並依賴操作系統作為應用程式主機:
ENTRYPOINT ["./DotNet.Docker"]
這會導致應用程式直接執行,而不需要 dotnet
,而是依賴應用程式主機和基礎OS。 如需部署跨平臺二進位檔的詳細資訊,請參閱 產生跨平臺二進位。
若要從終端機建置容器,請執行下列命令:
docker build -t counter-image -f Dockerfile .
Docker 會處理 Dockerfile中的每個行。
docker build
命令中的 .
會設定映像的建置內容。
-f
參數是 Dockerfile的路徑。 此命令會建置鏡像,並建立名為 counter-image 的本機儲存庫,該儲存庫指向該鏡像。 這個指令完成之後,請執行 docker images
以查看已安裝的映射清單:
REPOSITORY TAG IMAGE ID CREATED SIZE
counter-image latest 1c1f1433e51d 32 seconds ago 223MB
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
counter-image latest 2f15637dc1f6 10 minutes ago 217MB
counter-image
存放庫是映像的名稱。 此外,映像標籤、映像標識碼、大小和建立時間都是輸出的一部分。
Dockerfile 的最後步驟是從映像建立容器並執行應用程式、將已發佈的應用程式複製到容器,以及定義進入點:
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /App
COPY --from=build /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /App
COPY --from=build /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
FROM
命令會指定要使用的基底映像和標記。
WORKDIR
命令會將容器內部的 目前目錄 變更為 App。
COPY
命令會指示 Docker 將指定的來源目錄複製到目的地資料夾。 在此範例中,發佈的 內容會從 build
層輸出到名為 App/out的資料夾,因此該資料夾就是用來複製的來源。
App/out 目錄中所有已發佈的內容都會複製到目前的工作目錄中(應用程式)。
下一個命令 ENTRYPOINT
,會指示 Docker 將容器設定為執行為可執行檔。 當容器啟動時,ENTRYPOINT
命令就會執行。 此命令結束時,容器會自動停止。
提示
在 .NET 8 之前,設定為以唯讀身分執行的容器可能會因為 Failed to create CoreCLR, HRESULT: 0x8007000E
而失敗。 若要解決此問題,請將 DOTNET_EnableDiagnostics
環境變數指定為 0
(就在 ENTRYPOINT
步驟之前):
ENV DOTNET_EnableDiagnostics=0
如需各種 .NET 環境變數的詳細資訊,請參閱
注意
.NET 6 將標準化使用前置詞 DOTNET_
,以替代在設定 .NET 運行時行為的環境變數中使用的 COMPlus_
。 不過,COMPlus_
前綴會繼續運作。 如果您使用舊版的 .NET 執行階段,您仍應該使用環境變數的 COMPlus_
前綴。
建立容器
既然您有包含應用程式的映像檔,您可以建立容器。 您可以透過兩種方式建立容器。 首先,建立一個已停止的新容器。
docker create --name core-counter counter-image
這個 docker create
命令會根據 計數器映像 映射來建立容器。
docker create
命令的輸出會顯示容器的 容器識別碼(您的識別元會不同):
d0be06126f7db6dd1cee369d911262a353c9b7fb4829a0c11b4b2eb7b2d429cf
若要查看所有 容器
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 提供 docker run
命令,以單一命令建立和執行容器。 這個指令不需要執行 docker create
,然後 docker start
。 您也可以將此命令設定為在容器停止時自動刪除容器。 例如,使用 docker run -it --rm
執行兩件事,首先,自動使用目前的終端機連線到容器,然後在容器完成時將其移除:
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
。 視需要編輯命令。
在這裡範例中,ENTRYPOINT
會變更為 cmd.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
注意
此範例僅適用於 Windows 容器。 Linux 容器沒有 cmd.exe
。
基本命令
Docker 有許多不同的命令,可建立、管理及與容器和映像互動。 這些 Docker 命令對於管理容器而言非常重要:
清除資源
在本教學課程中,您已建立容器和映像。 如果您想要,請刪除這些資源。 使用下列命令
列出所有容器
docker ps -a
停止正在執行的容器,依名稱識別。
docker stop core-counter
刪除容器
docker rm core-counter
接下來,刪除計算機上不再需要的任何映像。 刪除 Dockerfile 所建立的映射,然後刪除 Dockerfile 所依據的 .NET 映射。 您可以使用 映射標識碼 或 REPOSITORY:TAG 格式化字串。
docker rmi counter-image:latest
docker rmi mcr.microsoft.com/dotnet/aspnet:9.0
docker rmi counter-image:latest
docker rmi mcr.microsoft.com/dotnet/aspnet:8.0
使用 docker images
命令來查看已安裝的映像清單。
提示
圖像檔可能很大。 一般而言,您會移除您在測試及開發應用程式時建立的暫存容器。 如果您打算根據該運行時間建置其他映像,通常會讓基底映像與運行時間一起安裝。