You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

切换用户时如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 23:42:31