最近在折腾 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 用户) 没有写入权限。
解决方案一:手动 chown 和 chmod 宿主机目录
手动执行 sudo chown -R $USER:$USER data 和 chmod -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! 😊
发表回复