Docker容器内执行mkdir遇到Permission Denied的问题排查
看起来你遇到的问题核心是权限匹配不对应,咱们一步步拆解来看:
先看你贴的ls -l输出:
drwxr-xr-x 2 755 djuser 4096 Dec 5 11:07 media
这里的755是目录的数字UID所有者,而你当前运行的djuser属于djuser组。755权限的目录,组用户只有r-x(读+执行)权限,没有写权限——这就是你没法在media里创建uploads目录的直接原因!
那为什么明明Dockerfile里执行了chown -R djuser:djuser /vol,结果media的所有者变成了数字UID 755?大概率是卷挂载的权限覆盖导致的:
最可能的原因:Bind Mount卷的宿主机权限冲突
如果你在docker-compose.yml里把/vol/web挂载成了宿主机的Bind Mount目录(比如./vol:/vol/web),那容器里/vol/web的权限会直接继承宿主机对应目录的权限。
- 假设宿主机上
./vol/web/media的所有者是UID 755的用户,那容器里看到的所有者就是755,而你的djuser的UID和755不匹配,只能以组用户的身份访问,自然没有写权限。
解决方案(按推荐度排序)
1. 改用Docker Named Volume
如果不需要宿主机目录直接映射,换成Named Volume的话,Docker会自动继承镜像里的目录权限:
在docker-compose.yml里配置:
volumes: web_media: web_static: services: app: # ... 其他配置 volumes: - web_media:/vol/web/media - web_static:/vol/web/static
这样启动后,Named Volume会继承你Dockerfile里设置的djuser:djuser权限,djuser作为所有者拥有rwx权限,就能正常创建目录了。
2. 固定djuser的UID与宿主机匹配
如果必须用Bind Mount,那让容器里djuser的UID和宿主机对应目录的所有者UID保持一致:
- 先在宿主机执行
id -u,拿到你当前用户的数字UID(比如1000) - 修改Dockerfile里的
adduser命令,指定固定UID:
RUN \ adduser -u 1000 --disabled-password --no-create-home djuser && \ mkdir -p /vol/web/media && \ mkdir -p /vol/web/static && \ chown -R djuser:djuser /vol && \ chmod -R 755 /vol
重新构建镜像后,容器里的djuserUID就是1000,和宿主机目录所有者一致,此时djuser就是media目录的所有者,拥有写权限。
3. 用Entrypoint脚本自动处理权限(无需手动sudo)
如果需要容器启动时自动创建目录,又不想每次切root,可以写一个简单的entrypoint脚本:
- 在项目根目录创建
entrypoint.sh:
#!/bin/sh # 以root身份创建uploads目录(如果不存在) mkdir -p /vol/web/media/uploads # 把目录权限交还给djuser chown -R djuser:djuser /vol/web/media/uploads # 切换回djuser执行原启动命令 exec su-exec djuser "$@"
- 修改Dockerfile,安装
su-exec(alpine轻量的权限切换工具)并设置entrypoint:
# 原有RUN命令之后加: RUN apk add --no-cache su-exec COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"]
这样每次容器启动时,会自动帮你创建目录并设置正确权限,全程不需要手动干预root操作。
最后验证
不管用哪种方案,都可以先在容器里跑个命令确认权限:
docker-compose run --rm app sh -c "whoami && id && ls -ld /vol/web/media"
如果输出里media的所有者是djuser(或者和djuser的UID一致),那权限就没问题了,再试mkdir应该就能成功。
备注:内容来源于stack exchange,提问作者RomanMin




