# Docker bind-mount **文件**会被旧 inode 锁住——atomic rename 后容器看不到新内容

**日期**：2026-05-17
**场景**：给 robot-lifecycle 流程图 HTML 加 Caddy 反代域名，Edit 改完 Caddyfile + `docker exec caddy caddy reload`，结果 reload 日志反复输出 `config is unchanged`，新域名无效。
**影响范围**：所有用 Docker bind-mount **单文件**（不是目录）+ Claude Code Edit / Write 工具 / 任何 atomic-rename 写法（vim `:w`、`mv tmp file`）改配置的场景。本项目最典型：`/home/chentao/caddy-config/Caddyfile` → `caddy:/etc/caddy/Caddyfile`。

## 现象

```
=== inode 对比 ===
918010 /home/chentao/caddy-config/Caddyfile     # 宿主机：新 inode（Edit 写出来的）
917971 /etc/caddy/Caddyfile（容器内）             # 容器：仍指向旧 inode

docker exec caddy caddy reload  →  "config is unchanged"
```

宿主机文件 tail 已含新条目，容器内文件 tail 还是老的——**两边内容不一致**，但 mount 路径相同。

## 直接原因

Claude Code 的 `Edit` / `Write` 工具用 **atomic rename** 改文件：
1. 写一个临时文件（new inode）
2. `rename(tmp, target)` 把临时文件改名顶替原文件

这是教科书级的"安全替换"做法，避免半写状态。但 **Linux Docker 的 bind mount 一个文件时锁的是 inode**（更准确：mount 时记下的 dentry，rename 不会更新 mount 内部 dentry 缓存）。原文件 inode 还活着（被 mount ref-count 持有），目录条目却已经指向新 inode 了——容器和宿主机看到的是**两个不同的文件**。

`caddy reload` 读容器内 `/etc/caddy/Caddyfile` = 旧 inode = 旧内容 → 跟运行中的配置完全一样 → `config is unchanged`。

## 元根因

Docker bind mount **目录**没这个问题（rename 改的是目录条目，目录本身的 inode 不变）。bind mount **单文件**才会触发。本项目 Caddy 的 docker-compose（或 run 命令）把单文件 `Caddyfile` 直接 bind 过去——这种写法常见，但跟 atomic-rename 编辑器组合就是坑。

## 修复（应用层）

绕过 bind mount，把新文件直接塞进容器：

```bash
docker cp /home/chentao/caddy-config/Caddyfile caddy:/tmp/Caddyfile.new
docker exec caddy caddy reload --config /tmp/Caddyfile.new
```

`/tmp/Caddyfile.new` 走容器的 union FS（不是 bind mount），caddy 看到的就是真新内容。**注意**：这条路径不持久——`docker restart caddy` 后 caddy 又回去读 bind-mount 的 `/etc/caddy/Caddyfile`（旧 inode 此时也会被释放，mount 重新解析到新 inode，反而恢复正常）。所以这是临时手段。

## 修复（工程化保险，根治）

**改 docker-compose**：bind mount **目录**而非**单文件**。

```yaml
# 不要这样（坑）
volumes:
  - /home/chentao/caddy-config/Caddyfile:/etc/caddy/Caddyfile:ro

# 应该这样
volumes:
  - /home/chentao/caddy-config:/etc/caddy:ro
```

挂目录后，Edit 的 atomic rename 改文件 → 目录条目更新 → 容器内 `/etc/caddy/Caddyfile` 路径解析时拿到新 inode → caddy reload 正常生效。

## 复现 / 验证

宿主机和容器分别 `stat -c '%i %n'` 看 inode，不一致就是踩坑。或者改完 Caddyfile 后 `docker exec caddy md5sum /etc/caddy/Caddyfile` 跟宿主机 `md5sum` 对比。

## 这个坑还会出现在哪

任何 Docker bind-mount 单配置文件的服务：
- Nginx config 单文件挂载
- Postgres `postgresql.conf` 单文件挂载
- Redis `redis.conf` 单文件挂载
- 任何 systemd-style 配置

只要用 Edit / Write / vim 改，atomic rename 就触发。**建议项目级约定：所有需要 hot-reload 的 docker 服务配置都 bind 目录、不 bind 单文件**。

## 跟同类坑的关系

- `2026-05-17-esm-node-path-createrequire-trap.md` — 流程图渲染器自己的依赖解析坑（不同主题）
- 本坑：宿主机 ↔ 容器**配置文件**视图分裂
- 一般的"docker compose 配置漂移" — 是版本/语法问题，不是 inode 问题
