Next.js应用写入Docker挂载卷遇EACCES权限拒绝问题排查
问题:Docker部署Next.js与.NET8应用时文件上传权限拒绝错误
Error: EACCES: permission denied, open '/app/public/v1/uploads/639ceeac-65f3-4726-8a31-9e55e603ff9b.png'...
相关配置文件
Next.js的Dockerfile
FROM node:20.15.0-alpine3.20 AS base FROM base AS deps RUN apk add --no-cache libc6-compat WORKDIR /app COPY --from=ext_path somefile somefile COPY package*.json ./ RUN mkdir node_modules RUN npm install --no-audit --omit=dev RUN npm install sharp FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/.env.local ./.env.local COPY . . ENV NEXT_TELEMETRY_DISABLED 1 RUN npm run build FROM base AS runner WORKDIR /app ENV NODE_ENV production ENV NEXT_TELEMETRY_DISABLED 1 COPY --from=deps /app/.env.local ./.env.local COPY --from=ext_path somefile somefile RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public COPY --from=builder /app/package.json ./package.json COPY --from=builder /app/next.config.mjs ./next.config.mjs COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static RUN mkdir -p ./public/v1/uploads RUN chown nextjs:nodejs ./public/v1/uploads USER nextjs EXPOSE 80 ENV PORT 80 ENV HOSTNAME "0.0.0.0" CMD ["node", "server.js"]
.NET8的Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS base WORKDIR /app EXPOSE 80 ENV ASPNETCORE_URLS=http://+:80 USER app FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build ARG configuration=Release COPY ["MyApp.sln", "MyApp.sln"] COPY ["MyApp/MyApp.csproj", "MyApp/"] RUN dotnet restore "MyApp.sln" COPY MyApp/ MyApp/ WORKDIR "/MyApp" FROM build AS publish ARG configuration=Release RUN dotnet publish "MyApp.csproj" -c $configuration -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "MyApp.dll"]
production/docker.env文件
SRV_NAME=myproject DOCKER_ENV=production RUNTIME_ENV=production WEB_EXPOSE_PORT=4000 API_EXPOSE_PORT=4100 UPLOAD_MOUNT_PATH=/var/lib/MyApp/web/public/v1/uploads
docker-compose.yml文件
networks: default: name: ${SRV_NAME}_default external: false infra: name: infra external: true driver: bridge services: my-web: container_name: ${SRV_NAME}-web image: jd/my-web:latest hostname: ${SRV_NAME}-web depends_on: - my-api build: context: ./nextjs dockerfile: ./Dockerfile network: host args: NODE_ENV: ${RUNTIME_ENV} additional_contexts: ext_path: ./${DOCKER_ENV}/ # This is just for reading some additional resources. volumes: - ${UPLOAD_MOUNT_PATH}:/app/public/v1/uploads:rw environment: - NODE_ENV=${RUNTIME_ENV} ports: - ${WEB_EXPOSE_PORT}:80 networks: default: infra: my-api: container_name: ${SRV_NAME}-api image: jd/my-api:latest hostname: ${SRV_NAME}-api build: context: ./dotnet8 dockerfile: ./MyApp/Dockerfile volumes: - ${UPLOAD_MOUNT_PATH}:/app/public/v1/uploads:rw ports: - ${API_EXPOSE_PORT}:80 networks: default: infra:
问题根源分析
- 挂载卷覆盖容器内权限设置:Next.js Dockerfile中已创建并修改
./public/v1/uploads目录权限为nextjs:nodejs,但docker-compose用宿主机目录挂载到该路径后,宿主机目录的默认权限(归属宿主机用户)会覆盖容器内的权限配置,导致nextjs用户无写入权限。 - 容器用户UID/GID不统一:Next.js使用UID/GID为1001的
nextjs用户运行,而.NET8官方镜像默认的app用户UID/GID与1001不一致,两个容器共享同一个挂载目录时,权限规则冲突。 - 宿主机目录权限未适配:宿主机的
/var/lib/MyApp/web/public/v1/uploads目录默认权限未开放给容器内的非root用户,导致写入失败。
解决方案
1. 统一两个容器的用户UID/GID(核心解决)
调整其中一个容器的用户UID/GID与另一个一致,推荐修改.NET8的app用户UID为1001(对齐Next.js的nextjs用户):
修改.NET8 Dockerfile
在base阶段添加用户调整命令:
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS base WORKDIR /app EXPOSE 80 ENV ASPNETCORE_URLS=http://+:80 # 删除默认app用户,重新创建UID为1001的app用户,对齐Next.js的nextjs用户 RUN deluser app && adduser -D -u 1001 app USER app
2. 配置宿主机目录初始权限
在宿主机预先创建挂载目录并设置匹配的权限:
mkdir -p /var/lib/MyApp/web/public/v1/uploads chown 1001:1001 /var/lib/MyApp/web/public/v1/uploads
3. 优化docker-compose配置(可选)
确保两个服务的挂载卷配置一致,无需额外修改,因为用户UID已统一,挂载后容器内用户可正常读写。
内容的提问来源于stack exchange,提问作者amiry jd
相关产品推荐
相关产品推荐

