教程:容器化 .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 builddotnet 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 段是容器存储库,而 sdkaspnet 段是容器映像名称。 映像被标记为 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 段是容器存储库,而 sdkaspnet 段是容器映像名称。 图像被标记为 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 命令指定要使用的基本映像和标记。 命令将容器内的当前目录 更改为 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 环境变量

注意

.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 startdocker 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 -itCtrl+C 命令会停止在容器中运行的进程,进而停止容器。 由于提供了 --rm 参数,因此在停止进程时会自动删除容器。 验证它是否不存在:

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

更改 ENTRYPOINT

docker run 命令还允许从 Dockerfile 修改 ENTRYPOINT 命令,并运行其他操作,但只针对该容器运行。 例如,使用以下命令运行 bashcmd.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 命令对于管理容器至关重要:

清理资源

在本教程中,你创建了容器和映像。 如果你愿意,可以删除这些资源。 以下命令可用于

  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:9.0
docker rmi counter-image:latest
docker rmi mcr.microsoft.com/dotnet/aspnet:8.0

使用 docker images 命令查看已安装的映像列表。

提示

图像文件可能很大。 通常,你将删除在测试和开发应用时创建的临时容器。 如果计划在相应运行时的基础之上生成其他映像,通常会将基础映像与运行时一同安装。

后续步骤