Docker “Permission Denied” 权限踩坑记

最近在折腾 Docker 容器化我的 Rust 应用,目标很简单:让应用跑在容器里,同时能把数据持久化到宿主机目录,方便管理和备份。 结果,看似简单的需求,却让我陷入了一场漫长的 “Permission Denied” 权限噩梦… 今天就来复盘一下这个踩坑之旅,希望能帮到同样遇到 Docker 权限问题的你。

“Permission Denied” 的噩梦开始

一切都从一个看似正常的 docker run 命令开始:

docker run -d \
  --name mail2customer \
  -v $(pwd)/data:/app/data \
  --env-file ./config/env/docker.env \
  mail2customer

我的 Rust 应用 mail2customer 需要在 /app/data 目录下读写文件,为了持久化数据,我使用了 -v $(pwd)/data:/app/data 将宿主机的 data 目录挂载到容器的 /app/data

然而,容器启动后,日志里却不断刷出 “Permission denied (os error 13)” 错误。应用无法在 /app/data 目录下创建文件,持久化自然也无从谈起。

拨开迷雾:宿主机目录权限 vs Dockerfile 用户

最初,我怀疑是宿主机的 data 目录权限不对。 赶紧 ls -l data 检查,发现目录所有者竟然是 root root, 是 Docker 守护进程默认用 root 用户创建了宿主机目录,导致容器内的 appuser (我的 Dockerfile 中创建的非 root 用户) 没有写入权限。

解决方案一:手动 chownchmod 宿主机目录

手动执行 sudo chown -R $USER:$USER datachmod -R 775 data,将 data 目录的所有权和权限修改为当前用户。 再次运行 docker run,然而,“Permission denied” 依旧! 难道不是宿主机目录权限的问题?

Dockerfile 的 chown 指令:看似有效,实则…

我开始怀疑 Dockerfile 中的 chown 指令是否生效。 我的 Dockerfile 中明明有:

RUN mkdir /app/data && chown -R appuser:appuser /app/data
USER appuser

看起来没毛病啊,先创建目录,然后 chown,最后切换到 appuser。 为了验证,我在 Dockerfile 中加入了调试日志,强制无缓存重建镜像,再次运行,容器内 /app/data 目录权限 看起来 确实是 appuser 了!

但为什么还是 “Permission denied”?

docker-entrypoint.sh 最佳实践:柳暗花明

Docker 官方推荐使用 docker-entrypoint.sh 脚本来解决卷挂载权限问题!

文章的核心思想是:

  • 容器启动时,以 root 用户执行 docker-entrypoint.sh 脚本 (方便进行初始化操作,例如权限设置)。
  • docker-entrypoint.sh 脚本中,动态获取宿主机用户 ID,并将卷挂载目录的所有权 chown 为该用户。
  • 使用 gosu 工具安全地切换到指定用户 (与宿主机用户 ID 相同的用户) 来运行应用程序。

修改 Dockerfile,加入 gosu 工具和 docker-entrypoint.sh 脚本:

Dockerfile (关键部分):

FROM debian:bookworm-slim

# ... (安装依赖) ...
# 安装 gosu
RUN apt-get update && apt-get install -y gosu ...
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod a+x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]

docker-entrypoint.sh 脚本:

#!/bin/bash
set -e
USER_ID=${LOCAL_USER_ID:-9001}
chown -R $USER_ID /app/data
useradd --shell /bin/bash -u $USER_ID -o -c "" -m user
exec /usr/local/bin/gosu user "$@"

docker run 命令 (传递宿主机用户 ID):

docker run --rm \
    -e LOCAL_USER_ID=$(id -u $USER) \
    -v $(pwd)/data:/app/data \
    mail2customer

应用成功启动,并且可以正常读写 /app/data 目录了!

#!/bin/bash

docker run --rm \
    --name "mail2customer" \
    -e "LOCAL_USER_ID=$(id -u $USER)" \
    -v "$(pwd)/data:/app/data" \
    mail2customer

再次运行 sh run_docker.sh一切终于完美运行! 容器在后台启动,应用正常工作,数据持久化也成功实现!

总结与反思

这次 Docker 权限踩坑之旅,虽然漫长曲折,但也让我对 Docker 卷挂载、用户权限、docker-entrypoint.sh 脚本有了更深入的理解。

  • 宿主机目录权限是 Docker 卷挂载权限问题的根源之一。 确保宿主机目录权限设置正确,容器内的用户才能拥有读写权限。
  • Dockerfile 中的 chown 指令只影响镜像内部的文件系统,无法直接修改宿主机目录权限。
  • docker-entrypoint.sh 脚本 + gosu 工具 + 动态用户 ID 传递 是解决 Docker 卷挂载权限问题的最佳实践之一。 强烈推荐使用!
  • 编写 run.sh 等 shell 脚本时,注意命令语法,特别是续行符 \ 和引号 " 的正确使用,避免 shell 脚本解析错误。
  • 遇到问题,不要慌,仔细阅读错误信息,善用 Docker 日志,逐步排查,总能找到解决方案!

希望我的这次踩坑经历能帮到你,避免走弯路。 Happy Dockering! 😊


感谢:


已发布

分类

来自

标签:

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注