切换用户时如何在Docker中结合NuGet使用构建缓存?
问题:切换到非Root用户执行DotNet Restore时NuGet缓存挂载权限错误
问题背景
Root用户构建DotNet应用镜像时,NuGet缓存挂载复用正常,但切换到test用户执行单元测试阶段的dotnet restore时失败,报错无法设置/tmp/NuGetScratch目录权限。
项目结构
/MyApp.sln (关联下方csproj) /src/app/MyApp.Main/MyApp.Main.csproj /src/test/MyApp.UnitTests/MyApp.UnitTests.csproj /src/test/runUnitTests.sh /src/docker/Dockerfile
简化后的Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim AS build-env WORKDIR /appcode COPY . ./ RUN --mount=type=cache,id=nuget-packages,target=/root/.nuget/packages \ --mount=type=cache,id=nuget-cache,target=/root/.local/share/NuGet/v3-cache \ --mount=type=cache,id=nuget-plugins,target=/root/.local/share/NuGet/plugins-cache \ --mount=type=cache,id=nuget-scratch,target=/tmp/NuGetScratch \ dotnet restore # Unit Testing FROM build-env AS unit-test-env ARG UID=999 RUN useradd -u ${UID} --shell /bin/bash -m test-user \ && chown -R test-user /appcode/* USER test-user WORKDIR /appcode/src/test/MyApp.UnitTests RUN --mount=type=cache,id=nuget-packages,target=/home/test-user/.nuget/packages \ --mount=type=cache,id=nuget-cache,target=/home/test-user/.local/share/NuGet/v3-cache \ --mount=type=cache,id=nuget-plugins,target=/home/test-user/.local/share/NuGet/plugins-cache \ --mount=type=cache,id=nuget-migrations,target=/home/test-user/.local/share/NuGet/Migrations \ --mount=type=cache,id=nuget-scratch,target=/tmp/NuGetScratch \ dotnet restore CMD ["/appcode/src/test/runUnitTests.sh"]
构建错误信息
$ docker build -f src/docker/Dockerfile -t my-app:1 --target=unit-test-env --rm . [+] Building 7.4s (12/12) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 3.18kB 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim 0.2s => [build-env 1/4] FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim@sha256:79042d5b225ec9f40a4144f6a60492aeccf5eea1d47a390e428c736cd368351c 0.0s => [internal] load build context 0.1s => => transferring context: 101.18kB 0.1s => CACHED [build-env 2/4] WORKDIR /appcode 0.0s => [build-env 3/4] COPY . ./ 0.2s => [build-env 4/4] RUN --mount=type=cache,id=nuget-packages,target=/root/.nuget/packages --mount=type=cache,id=nuget-cache,target=/root/.local/share/NuGet/v3-cache --mount=type=cache,id=nuget-plugins,target 5.0s => [unit-test-env 1/4] RUN useradd -u 999 --shell /bin/bash -m test-user && chown -R test-user /appcode/* && chmod -R a+x /appcode/src/deploy/* && mkdir -p /output/report && chown test-user /output && chown test-use 0.6s => [unit-test-env 2/4] RUN --mount=type=cache,id=nuget-scratch,target=/tmp/NuGetScratch chown -R test-user /tmp 0.2s => [unit-test-env 3/4] WORKDIR /appcode/src/test/MyApp.UnitTests 0.0s => ERROR [unit-test-env 4/4] RUN --mount=type=cache,id=nuget-packages,target=/home/test-user/.nuget/packages --mount=type=cache,id=nuget-cache,target=/home/test-user/.local/share/NuGet/v3-cache --mount=type=cach 0.8s ------ > [unit-test-env 4/4] RUN --mount=type=cache,id=nuget-packages,target=/home/test-user/.nuget/packages --mount=type=cache,id=nuget-cache,target=/home/test-user/.local/share/NuGet/v3-cache --mount=type=cache,id=nuget-plugins,target=/home/test-user/.local/share/NuGet/plugins-cache --mount=type=cache,id=nuget-migrations,target=/home/test-user/.local/share/NuGet/Migrations --mount=type=cache,id=nuget-scratch,target=/tmp/NuGetScratch dotnet restore: #11 0.815 /usr/share/dotnet/sdk/6.0.406/NuGet.targets(394,5): error MSB4018: The "WarnForInvalidProjectsTask" task failed unexpectedly. [/appcode/src/test/MyApp.UnitTests/MyApp.UnitTests.csproj] #11 0.815 /usr/share/dotnet/sdk/6.0.406/NuGet.targets(394,5): error MSB4018: System.InvalidOperationException: Unable to set permission while creating /tmp/NuGetScratch, errno=1. [/appcode/src/test/MyApp.UnitTests/MyApp.UnitTests.csproj] #11 0.815 /usr/share/dotnet/sdk/6.0.406/NuGet.targets(394,5): error MSB4018: at NuGet.Common.NuGetEnvironment.<GetFolderPath>g__CreateSharedDirectory|6_0(String path) [/appcode/src/test/MyApp.UnitTests/MyApp.UnitTests.csproj] #11 0.815 /usr/share/dotnet/sdk/6.0.406/NuGet.targets(394,5): error MSB4018: at NuGet.Common.NuGetEnvironment.GetFolderPath(NuGetFolderPath folder) [/appcode/src/test/MyApp.UnitTests/MyApp.UnitTests.csproj] #11 0.815 /usr/share/dotnet/sdk/6.0.406/NuGet.targets(394,5): error MSB4018: at NuGet.Common.PathUtility.CheckIfFileSystemIsCaseInsensitive() [/appcode/src/test/MyApp.UnitTests/MyApp.UnitTests.csproj] #11 0.815 /usr/share/dotnet/sdk/6.0.406/NuGet.targets(394,5): error MSB4018: at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode) [/appcode/src/test/MyApp.UnitTests/MyApp.UnitTests.csproj] #11 0.815 /usr/share/dotnet/sdk/6.0.406/NuGet.targets(394,5): error MSB4018: at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor) [/appcode/src/test/MyApp.UnitTests/MyApp.UnitTests.csproj] #11 0.815 /usr/share/dotnet/sdk/6.0.406/NuGet.targets(394,5): error MSB4018: at System.Lazy`1.CreateValue() [/appcode/src/test/MyApp.UnitTests/MyApp.UnitTests.csproj] #11 0.815 /usr/share/dotnet/sdk/6.0.406/NuGet.targets(394,5): error MSB4018: at System.Lazy`1.get_Value() [/appcode/src/test/MyApp.UnitTests/MyApp.UnitTests.csproj] #11 0.815 /usr/share/dotnet/sdk/6.0.406/NuGet.targets(394,5): error MSB4018: at NuGet.Common.PathUtility.get_IsFileSystemCaseInsensitive() [/appcode/src/test/MyApp.UnitTests/MyApp.UnitTests.csproj] #11 0.815 /usr/share/dotnet/sdk/6.0.406/NuGet.targets(394,5): error MSB4018: at NuGet.Common.PathUtility.GetStringComparerBasedOnOS() [/appcode/src/test/MyApp.UnitTests/MyApp.UnitTests.csproj] #11 0.815 /usr/share/dotnet/sdk/6.0.406/NuGet.targets(394,5): error MSB4018: at NuGet.Build.Tasks.WarnForInvalidProjectsTask.Execute() [/appcode/src/test/MyApp.UnitTests/MyApp.UnitTests.csproj] #11 0.815 /usr/share/dotnet/sdk/6.0.406/NuGet.targets(394,5): error MSB4018: at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute() [/appcode/src/test/MyApp.UnitTests/MyApp.UnitTests.csproj] #11 0.815 /usr/share/dotnet/sdk/6.0.406/NuGet.targets(394,5): error MSB4018: at Microsoft.Build.BackEnd.TaskBuilder.ExecuteInstantiatedTask(ITaskExecutionHost taskExecutionHost, TaskLoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask) [/appcode/src/test/MyApp.UnitTests/MyApp.UnitTests.csproj] ------ executor failed running [/bin/sh -c dotnet restore]: exit code: 1
已尝试的无效方案
- 创建test用户后以Root执行
RUN chown -R test-user /tmp - 执行
RUN rm -rf /tmp/NuGetScratch
解决方案
方案1:给缓存挂载指定UID/GID
Docker缓存挂载目录默认由Root创建,test用户无权限修改权限。需在挂载时显式指定缓存目录的所有者为test用户:
修改后的单元测试阶段Dockerfile:
# Unit Testing FROM build-env AS unit-test-env ARG UID=999 RUN useradd -u ${UID} --shell /bin/bash -m test-user \ && chown -R test-user /appcode/* USER test-user WORKDIR /appcode/src/test/MyApp.UnitTests RUN --mount=type=cache,id=nuget-packages,target=/home/test-user/.nuget/packages,uid=${UID},gid=${UID} \ --mount=type=cache,id=nuget-cache,target=/home/test-user/.local/share/NuGet/v3-cache,uid=${UID},gid=${UID} \ --mount=type=cache,id=nuget-plugins,target=/home/test-user/.local/share/NuGet/plugins-cache,uid=${UID},gid=${UID} \ --mount=type=cache,id=nuget-migrations,target=/home/test-user/.local/share/NuGet/Migrations,uid=${UID},gid=${UID} \ --mount=type=cache,id=nuget-scratch,target=/tmp/NuGetScratch,uid=${UID},gid=${UID} \ dotnet restore CMD ["/appcode/src/test/runUnitTests.sh"]
关键修改:给每个--mount=type=cache添加uid=${UID},gid=${UID}参数,确保缓存目录归test用户所有,NuGet操作时不会触发权限错误。
方案2:使用用户专属临时目录
让test用户使用独立的临时目录,避免与Root共享/tmp:
# Unit Testing FROM build-env AS unit-test-env ARG UID=999 RUN useradd -u ${UID} --shell /bin/bash -m test-user \ && mkdir -p /home/test-user/tmp \ && chown -R test-user /home/test-user/tmp \ && chown -R test-user /appcode/* USER test-user # 设置临时目录环境变量 ENV TMPDIR=/home/test-user/tmp WORKDIR /appcode/src/test/MyApp.UnitTests RUN --mount=type=cache,id=nuget-packages,target=/home/test-user/.nuget/packages \ --mount=type=cache,id=nuget-cache,target=/home/test-user/.local/share/NuGet/v3-cache \ --mount=type=cache,id=nuget-plugins,target=/home/test-user/.local/share/NuGet/plugins-cache \ --mount=type=cache,id=nuget-migrations,target=/home/test-user/.local/share/NuGet/Migrations \ --mount=type=cache,id=nuget-scratch,target=/home/test-user/tmp/NuGetScratch \ dotnet restore CMD ["/appcode/src/test/runUnitTests.sh"]
原理:完全隔离test用户的临时目录,避免权限冲突,同时缓存挂载目录自然归用户所有。
内容的提问来源于stack exchange,提问作者Chip
相关产品推荐
相关产品推荐

