Docker 中的 .NET 包还原与构建分开缓存
.NET package restore in Docker cached separately from build
如何构建 .NET 5/C# 应用程序的 Docker 图像,以便正确缓存恢复的 NuGet 包? 我所说的正确缓存是指当源(但不是项目文件)被更改时,包含恢复包的层仍然在 docker build
.
期间从缓存中获取
Docker 中的最佳做法是在添加完整源和构建应用程序本身之前执行包还原,因为这样可以单独缓存还原,从而显着加快构建速度。我知道不仅是packages
目录,还有个别项目的bin
和obj
目录从dotnet restore
到dotnet publish --no-restore
都要保留,这样一切一起工作。我也知道,一旦缓存被破坏,所有后续层都会重新构建。
我的问题是我想不出一种方法来仅复制 *.csproj
。如果我复制的不仅仅是 *.csproj
,源更改会破坏缓存。我可以将它们复制到 docker build
之外的一个地方,然后在构建中简单地复制它们,但我希望能够 甚至在管道 之外手动构建图像,使用一个相当简单的命令。 (是不是不合理的要求?)
对于在非常标准的文件夹结构中包含多个项目的 Web 应用程序 src/*/*.csproj
,我想出了这个尝试来补偿被复制到图像中的太多文件(这仍然破坏了缓存):
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build-env
WORKDIR /src
COPY NuGet.Config NuGet.Config
COPY src/ src/
RUN find . -name NuGet.Config -prune -o \! -type d \! -name \*.csproj -exec rm -f '{}' + \
&& find -depth -type d -empty -exec rmdir '{}' \;
RUN dotnet restore src/Company.Product.Component.App/Company.Product.Component.App.csproj
COPY src/ src/
RUN dotnet publish src/Company.Product.Component.App/Company.Product.Component.App.csproj -c Release --no-restore -o /out
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS run-env
WORKDIR /app
COPY --from=build-env /out .
ENTRYPOINT ["dotnet", "Company.Product.Component.App.dll"]
我还尝试在还原后将构建环境阶段一分为二,将 /root/.nuget/packages 和 /src 复制到构建阶段,但这也无济于事。
第一行 运行 和紧接在前的行应该替换为仅复制 *.csproj
的内容,但我不知道那是什么。 明显费力的解决方案 是为每个 *.csproj
设置一个单独的 COPY 行,但这感觉不对,因为项目往往会被添加和删除,所以它使得 Docker文件难以维护。我已经尝试 COPY src/*/*.csproj src/
然后修复扁平路径,这是我用谷歌搜索的一个技巧,但它对我不起作用,因为我的 Docker 只处理文件名中的通配符并从字面上解释目录名,为不存在的 src/*
目录发出错误。我正在使用 Docker Desktop 3.5.2 (66501),它使用 BuildKit 后端构建图像,但如果有帮助,我愿意更改工具。
这让我对如何满足相对简单的一组要求一无所知。我的选择似乎用尽了。 我错过了什么吗?我是否必须接受权衡并放弃我的一些要求?
不支持目录名称中的通配符可能是 BuildKit 中缺少的功能。该问题已在 moby/buildkit GitHub as #1900.
上报告
在问题解决之前, 如果您不需要它的任何功能。要么
- 将环境变量 DOCKER_BUILDKIT 设置为零 (
0
),或
- 编辑 Docker 守护程序配置,将“buildkit”功能设置为 false 并重新启动守护程序。
在 Docker 桌面中,可以在设置 > Docker 引擎中轻松访问配置。 Docker Desktop 3.2.0 release notes 建议使用这种关闭功能的方法,其中 BuildKit 最初是默认启用的。
禁用 BuildKit 后,替换
COPY src/ src/
RUN find . -name NuGet.Config -prune -o \! -type d \! -name \*.csproj -exec rm -f '{}' + \
&& find -depth -type d -empty -exec rmdir '{}' \;
和
COPY src/*/*.csproj src/
RUN for from in src/*.csproj; do to=$(echo "$from" | sed 's/\/\([^/]*\)\.csproj$/\/&/') \
&& mkdir -p "$(dirname "$to")" && mv "$from" "$to"; done
COPY 将在不破坏缓存的情况下成功,运行 将修复路径。它依赖于项目位于“src”目录中的事实,每个项目都位于与项目文件同名的单独目录中。
这基本上是. The answer also mentions Moby issue #15858底部的解决方案,其中有一个有趣的话题讨论。
还有一个dotnet tool for the paths fixup,不过我没测试过
不需要禁用 BuildKit 的 替代解决方案是 split the original stage in two 在清理复制的文件之后,即在恢复之前(而不是之后!)。
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS projects-env
WORKDIR /src
COPY NuGet.Config NuGet.Config
COPY src/ src/
RUN find . -name NuGet.Config -prune -o \! -type d \! -name \*.csproj -exec rm -f '{}' + \
&& find . -depth -type d -empty -exec rmdir '{}' \;
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build-env
WORKDIR /src
COPY --from=projects-env /src /src
RUN dotnet restore src/Company.Product.Component.App/Company.Product.Component.App.csproj
COPY src/ src/
RUN dotnet publish src/Company.Product.Component.App/Company.Product.Component.App.csproj -c Release --no-restore -o /out
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS run-env
WORKDIR /app
COPY --from=build-env /out .
ENTRYPOINT ["dotnet", "Company.Product.Component.App.dll"]
sources-env 中的 COPY src/ src/
层因源更改而失效,但缓存失效针对每个阶段单独进行。由于复制到 build-env 的文件在构建之间是相同的,COPY --from=projects-env
缓存不会失效,因此 RUN dotnet restore
层也从缓存中获取。
我怀疑还有其他解决方案使用 BuildKit mounts (RUN --mount=...
),但我没有测试过。
这是解决问题的替代方法。
首先,复制 .sln 和 .csproj 文件(取决于解决方案文件夹结构)
COPY *.sln ./
COPY **/*.csproj ./
COPY **/**/**/*.csproj ./
之后 运行 以下脚本:
RUN dotnet sln list | grep ".csproj" \
| while read -r line; do \
mkdir -p $(dirname $line); \
mv $(basename $line) $(dirname $line); \
done;
脚本只是将 .csproj 文件移动到它们在主机文件系统中的相同位置。
如何构建 .NET 5/C# 应用程序的 Docker 图像,以便正确缓存恢复的 NuGet 包? 我所说的正确缓存是指当源(但不是项目文件)被更改时,包含恢复包的层仍然在 docker build
.
Docker 中的最佳做法是在添加完整源和构建应用程序本身之前执行包还原,因为这样可以单独缓存还原,从而显着加快构建速度。我知道不仅是packages
目录,还有个别项目的bin
和obj
目录从dotnet restore
到dotnet publish --no-restore
都要保留,这样一切一起工作。我也知道,一旦缓存被破坏,所有后续层都会重新构建。
我的问题是我想不出一种方法来仅复制 *.csproj
。如果我复制的不仅仅是 *.csproj
,源更改会破坏缓存。我可以将它们复制到 docker build
之外的一个地方,然后在构建中简单地复制它们,但我希望能够 甚至在管道 之外手动构建图像,使用一个相当简单的命令。 (是不是不合理的要求?)
对于在非常标准的文件夹结构中包含多个项目的 Web 应用程序 src/*/*.csproj
,我想出了这个尝试来补偿被复制到图像中的太多文件(这仍然破坏了缓存):
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build-env
WORKDIR /src
COPY NuGet.Config NuGet.Config
COPY src/ src/
RUN find . -name NuGet.Config -prune -o \! -type d \! -name \*.csproj -exec rm -f '{}' + \
&& find -depth -type d -empty -exec rmdir '{}' \;
RUN dotnet restore src/Company.Product.Component.App/Company.Product.Component.App.csproj
COPY src/ src/
RUN dotnet publish src/Company.Product.Component.App/Company.Product.Component.App.csproj -c Release --no-restore -o /out
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS run-env
WORKDIR /app
COPY --from=build-env /out .
ENTRYPOINT ["dotnet", "Company.Product.Component.App.dll"]
我还尝试在还原后将构建环境阶段一分为二,将 /root/.nuget/packages 和 /src 复制到构建阶段,但这也无济于事。
第一行 运行 和紧接在前的行应该替换为仅复制 *.csproj
的内容,但我不知道那是什么。 明显费力的解决方案 是为每个 *.csproj
设置一个单独的 COPY 行,但这感觉不对,因为项目往往会被添加和删除,所以它使得 Docker文件难以维护。我已经尝试 COPY src/*/*.csproj src/
然后修复扁平路径,这是我用谷歌搜索的一个技巧,但它对我不起作用,因为我的 Docker 只处理文件名中的通配符并从字面上解释目录名,为不存在的 src/*
目录发出错误。我正在使用 Docker Desktop 3.5.2 (66501),它使用 BuildKit 后端构建图像,但如果有帮助,我愿意更改工具。
这让我对如何满足相对简单的一组要求一无所知。我的选择似乎用尽了。 我错过了什么吗?我是否必须接受权衡并放弃我的一些要求?
不支持目录名称中的通配符可能是 BuildKit 中缺少的功能。该问题已在 moby/buildkit GitHub as #1900.
上报告在问题解决之前,
- 将环境变量 DOCKER_BUILDKIT 设置为零 (
0
),或 - 编辑 Docker 守护程序配置,将“buildkit”功能设置为 false 并重新启动守护程序。
在 Docker 桌面中,可以在设置 > Docker 引擎中轻松访问配置。 Docker Desktop 3.2.0 release notes 建议使用这种关闭功能的方法,其中 BuildKit 最初是默认启用的。
禁用 BuildKit 后,替换
COPY src/ src/
RUN find . -name NuGet.Config -prune -o \! -type d \! -name \*.csproj -exec rm -f '{}' + \
&& find -depth -type d -empty -exec rmdir '{}' \;
和
COPY src/*/*.csproj src/
RUN for from in src/*.csproj; do to=$(echo "$from" | sed 's/\/\([^/]*\)\.csproj$/\/&/') \
&& mkdir -p "$(dirname "$to")" && mv "$from" "$to"; done
COPY 将在不破坏缓存的情况下成功,运行 将修复路径。它依赖于项目位于“src”目录中的事实,每个项目都位于与项目文件同名的单独目录中。
这基本上是
还有一个dotnet tool for the paths fixup,不过我没测试过
不需要禁用 BuildKit 的 替代解决方案是 split the original stage in two 在清理复制的文件之后,即在恢复之前(而不是之后!)。
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS projects-env
WORKDIR /src
COPY NuGet.Config NuGet.Config
COPY src/ src/
RUN find . -name NuGet.Config -prune -o \! -type d \! -name \*.csproj -exec rm -f '{}' + \
&& find . -depth -type d -empty -exec rmdir '{}' \;
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build-env
WORKDIR /src
COPY --from=projects-env /src /src
RUN dotnet restore src/Company.Product.Component.App/Company.Product.Component.App.csproj
COPY src/ src/
RUN dotnet publish src/Company.Product.Component.App/Company.Product.Component.App.csproj -c Release --no-restore -o /out
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS run-env
WORKDIR /app
COPY --from=build-env /out .
ENTRYPOINT ["dotnet", "Company.Product.Component.App.dll"]
sources-env 中的 COPY src/ src/
层因源更改而失效,但缓存失效针对每个阶段单独进行。由于复制到 build-env 的文件在构建之间是相同的,COPY --from=projects-env
缓存不会失效,因此 RUN dotnet restore
层也从缓存中获取。
我怀疑还有其他解决方案使用 BuildKit mounts (RUN --mount=...
),但我没有测试过。
这是解决问题的替代方法。
首先,复制 .sln 和 .csproj 文件(取决于解决方案文件夹结构)
COPY *.sln ./
COPY **/*.csproj ./
COPY **/**/**/*.csproj ./
之后 运行 以下脚本:
RUN dotnet sln list | grep ".csproj" \
| while read -r line; do \
mkdir -p $(dirname $line); \
mv $(basename $line) $(dirname $line); \
done;
脚本只是将 .csproj 文件移动到它们在主机文件系统中的相同位置。