DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

Dependency-Track 前端启动时报 default.conf: Read-only file system

Dependency-Track 前端启用只读根文件系统后,envsubst 无法生成 default.conf。区分根文件系统与只读挂载,说明允许可写容器层时的调整方法、运行时配置检查,以及严格只读部署的边界。

给 Dependency-Track 前端容器启用只读根文件系统后,容器可能在启动阶段退出,日志停在配置生成这一步:

Running envsubst on /etc/nginx/templates/default.conf.template to /etc/nginx/conf.d/default.conf
can't create /etc/nginx/conf.d/default.conf: Read-only file system

如果只给 /tmp 加了可写的 tmpfs,这个错误仍会出现:启动脚本要写的是 /etc/nginx/conf.d/default.conf,它不在 /tmp 下面。

下面以官方前端镜像 4.12.1 的启动结构为范围:镜像中包含 Nginx 模板,启动时生成配置,并修改前端的运行时配置文件。其他版本只有在日志、入口脚本和写入路径一致时才能沿用判断;这不是要求把当前安装版本降到 4.12.1。自制静态镜像或已经改过入口脚本的部署需要单独检查。

确认哪个挂载阻止了写入

以下命令假设 Compose 服务名为 frontend,在原部署项目目录执行;服务名不同就替换它。原部署若使用多个 -f 文件或指定项目名,所有命令都沿用相同参数,避免检查到另一套容器。

docker compose ps -a frontend
docker compose logs --tail 100 frontend

从列表中取得容器名或 ID,用它替换下面的 FRONTEND_CONTAINER_ID。已退出的容器也可以检查,不需要先让它保持运行。

docker inspect --type container \
  --format 'image={{.Config.Image}} rootfs_readonly={{.HostConfig.ReadonlyRootfs}}' \
  FRONTEND_CONTAINER_ID

docker inspect --type container \
  --format '{{json .Mounts}}' \
  FRONTEND_CONTAINER_ID

先看 rootfs_readonly,再看挂载列表中有没有覆盖 /etc/nginx/conf.d、它的上级目录或 default.conf 本身的挂载。挂载项里的 Destination 是容器内路径,RW: false 表示该挂载只读。

  • 根文件系统只读,报错路径又没有单独可写挂载:启动脚本无法在镜像目录里生成配置。
  • 根文件系统可写,但该目录或文件被只读挂载覆盖:限制来自挂载,修改根文件系统选项不会解除它。
  • 两项都不符合:继续核对实际镜像、完整日志和挂载来源,不要仅凭这一篇就认定是 Compose 的只读选项。

这个错误明确说文件系统只读,单独调整文件权限或让容器改用 root,并不能把只读挂载变为可写。也不要直接给整个配置目录放开所有用户的写权限。

Kubernetes 部署还要检查 Pod 的 securityContext 与 volumeMounts。ConfigMap 卷本身始终只读;即使 readOnlyRootFilesystem 为 false,把它挂到 /etc/nginx/conf.d 后,配置生成脚本仍不能向这个目录写文件。

可以使用可写根文件系统时,恢复前端所需的启动条件

这条处理路径适用于部署策略允许前端拥有可写容器层的情况。先保存当前 Compose 文件、覆盖文件和镜像引用;包含环境变量的配置备份应按敏感文件保管。调整会重建前端容器,安排能够接受短暂中断的时间。容器可写层里若还有手动修改的文件,先另行保存,重建不会保留这些修改。

在现有服务定义中修改下面这一项,保留原来的镜像、环境变量、端口和其他设置。此处只是需要合入现有文件的配置片段,不是完整部署文件:

services:
  frontend:
    read_only: false

如果覆盖文件里又设置了 read_only: true,也要处理实际生效的定义。根文件系统恢复可写后,镜像的非 root 用户和普通文件权限仍然有效;这项调整不要求增加特权模式或改成 root。不过,它确实取消了一层禁止写入容器根文件系统的限制,应由部署策略决定能否接受。

如果另有只读挂载覆盖配置输出目录,先查清它提供了哪些自定义配置。确认不再需要时才撤掉该挂载,恢复使用镜像自带模板;需要保留自定义配置时,应重新安排只读输入与可写输出的位置,并确认 Nginx 最终加载的是哪份配置。不能只把 ConfigMap 随意挪到另一个目录,就当作原有配置仍然生效。

配置调整完成后,先校验,再仅更新前端:

docker compose config --quiet
docker compose up -d --no-deps frontend

先确认校验成功再执行第二条。up 会根据服务配置变化重建容器;只做进程重启不能应用新的容器创建选项。--no-deps 避免同时启动关联服务,原有 API 服务应已经运行。这一步不需要删除数据库卷,也不需要重建 API 服务。

只让 Nginx 配置可写,还不等于前端配置完整

4.12.1 的前端启动脚本还会写入:

/opt/owasp/dependency-track-frontend/static/config.json

这份文件承载 API_BASE_URL、OIDC 等运行时设置。无法修改它时,日志会出现:

can not modify config.json - ENV configuration will be ignored

此时 Nginx 有可能已经启动,浏览器也能打开页面,但通过环境变量传入的 API 地址或登录配置没有写入前端文件。首页返回成功不足以证明配置已经生效。

如果有意挂载了完整的 config.json,则应按该文件配置前端,不再期待环境变量覆盖它。若原本采用环境变量配置,就要确认它没有因只读限制被忽略。

必须保留只读根文件系统的部署,不应直接照抄前面的放宽设置。需要按所用镜像的入口脚本,设计启动期间的可写配置目录及初始化过程,并逐一验证生成文件和最终加载路径。只添加 /tmp,或只修改 Nginx 模板输出目录,都不能证明前端的全部启动写入已经满足。

不要把空 tmpfs 直接挂到整个 static 目录来碰运气:挂载会遮住镜像原有文件,其中包括用于修改的配置文件;tmpfs 中的新文件还会在容器停止后消失。严格只读部署需要完整的初始化方案,这里不提供一份跨版本通用的可写目录清单。

验收与回退

重建后重新取得容器名或 ID,再检查根文件系统和挂载状态,并查看新容器日志:

docker compose ps -a frontend
docker compose logs --tail 100 frontend

确认配置生成不再报只读错误,容器没有反复退出。然后在浏览器重新加载前端,通过网络面板检查它实际访问的 API 地址;使用 OIDC 时再验证登录流程。采用环境变量配置的部署,还应确认不再出现忽略环境配置的提示。日志和前端配置可能包含内部地址,分享前先做脱敏。

如果改动影响了自定义路由或登录,恢复保存的 Compose 文件及挂载定义,仍使用原来的镜像引用,再运行前面的前端更新命令。不要同时升级镜像,否则无法分清行为变化来自配置还是版本。恢复原只读设置会恢复原来的写入限制;若它原本就导致启动失败,回退只能恢复配置状态,不能保证服务恢复可用。