教程:容器化 .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 社区版。
- 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
命令将创建名为 应用 的新文件夹,并生成“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
计数到 5。
重要
--
后的任何参数不会传递到 dotnet run
命令,而是传递给应用程序。
发布 .NET 应用
为了使应用适合创建映像,它必须进行编译。 dotnet publish
命令最适合用于这一任务,因为它生成并发布应用程序。 要获得详细参考信息,请参阅 dotnet build 和 dotnet publish 命令文档。
dotnet publish -c Release
提示
如果有兴趣将 .NET 应用发布为容器,而无需 Docker,请参阅使用 dotnet publish 容器化 .NET 应用。
dotnet 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 没有扩展名的文本文件。
在包含 .csproj 的目录中创建名为 Dockerfile 的文件,并在文本编辑器中打开该文件。 本教程使用 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 文档:按摘要拉取映像。
提示
此 Dockerfile 使用多阶段生成,通过分层生成并仅保留必要的工件来优化映像的最终大小。 有关详细信息,请参阅 Docker Docs:多阶段生成。
FROM
关键字需要完全限定的 Docker 容器映像名称。 Microsoft 容器注册表(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 中心页面上找到所有受支持的标签的完整列表。
保存 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 容器注册表(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 的路径。 此命令将生成映像,并创建一个名为 计数器映像 的本地存储库,该存储库指向该映像。 此命令完成后,运行 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
命令指定要使用的基本映像和标记。
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 环境变量。
注意
.NET 6 将用于配置 .NET 运行时行为的环境变量的前缀标准化为 DOTNET_
,而不是 COMPlus_
。 但是,COMPlus_
前缀仍将继续正常工作。 如果使用的是 .NET 运行时的早期版本,则仍应对环境变量使用 COMPlus_
前缀。
创建容器
现在,你已有一个包含应用程序的镜像,可以创建容器。 可以通过两种方式创建容器。 首先,创建处于停止状态的新容器。
docker create --name core-counter counter-image
此 docker create
命令会在 counter-image 映像的基础之上创建容器。 docker create
命令的输出会向你显示容器的容器 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 提供用于创建和运行容器的 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 映像。 可以使用 IMAGE ID 或 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
命令查看已安装的映像列表。
提示
图像文件可能很大。 通常,你将删除在测试和开发应用时创建的临时容器。 如果计划在相应运行时的基础之上生成其他映像,通常会将基础映像与运行时一同安装。