根本原因是stat轮询机制在docker volume挂载等场景下无法感知文件变更;安装watchdog可启用inotify等原生事件监听,再配合正确挂载路径和flask run启动方式即可解决。

Flask 在 Docker 容器里热重载失效,根本不是 Flask 或 Docker “不支持”,而是默认监听机制在容器环境下压根看不见你改的代码——stat 轮询模式在 volume 挂载、WSL2、NFS 等场景下会静默失灵。
为什么控制台显示 * Restarting with stat 却不重启?
这是最典型的信号:Flask 正在用文件时间戳轮询(stat)检测变更,但 Docker volume 挂载时,宿主机修改文件的时间戳可能未同步到容器内 inode,或者 IDE 保存策略(如 VS Code 的 atomic write)导致临时文件替换,stat 根本捕获不到变化。
- 现象:终端日志有
* Restarting with stat和* Debugger is active!,但改完app.py后刷新页面,内容还是旧的 - 验证方式:在路由里加
import time; return f"Updated at {time.time():.0f}",保存后时间戳不变就确认是监听失效 - Linux 主机上可用
inotifywait -m -e modify,move,create /app进容器测试是否能捕获事件;如果无输出,说明底层事件不可达
pip install watchdog 是唯一可靠解法
装了 watchdog,Flask 就会自动切换到操作系统原生事件监听(Linux 用 inotify,macOS 用 FSEvents),不再依赖脆弱的轮询。
- 执行
pip install watchdog,无需改任何代码或配置 - 重启容器后,日志应变为
* Restarting with watchdog (inotify)—— 这行出现才算真正生效 - 注意:Docker 构建时必须把
watchdog写进requirements.txt并安装,不能只在本地装 - WSL2 用户需确保使用
/home/xxx/路径挂载代码,避免挂/mnt/c/(该路径不支持 inotify 事件穿透)
Docker 启动命令和挂载路径必须匹配监听范围
即使装了 watchdog,路径没对齐照样白搭。监听器只监控应用工作目录下的变更,挂载点必须覆盖整个包结构。
- 假设 Flask 应用入口是
app/main.py,且app是包(含__init__.py),挂载就得写成volumes: .:/app,而不是./app:/app - 检查容器内路径:进容器运行
ls -la /app/main.py和ls -la /app/__init__.py,确认文件真实存在且权限可读 - 启动命令别用
python app/main.py,改用flask run --debug(它会自动识别FLASK_APP=app.main:app和FLASK_DEBUG=1) - 若用
gunicorn或uvicorn做 WSGI 服务器,它们本身不支持热重载——watchdog只对flask run有效
别让 IDE 或环境变量干扰重载逻辑
很多问题其实出在开发工具链上,而不是 Flask 本身。
- VS Code / PyCharm 必须关闭
Files: Safe Write(安全写入),否则编辑器先写临时文件再原子替换,watchdog监听到的是临时文件创建+删除,而非目标文件修改 - 不要在代码里写
if __name__ == '__main__': app.run(debug=True),这和flask run冲突;统一用命令行方式,并通过.env文件设FLASK_DEBUG=1 -
FLASK_ENV=development已弃用(Flask 2.3+ 警告),只认FLASK_DEBUG=1 - 确保
.env文件在容器内WORKDIR下,且load_dotenv()调用在Flask(__name__)实例创建之前
热重载失效的根因从来不在“要不要开 debug”,而在于“变更能不能被监听到”。watchdog 解决的是底层感知能力,路径、挂载、启动方式解决的是作用域对齐——三者缺一不可。漏掉任意一环,都会表现为“看着像在重载,其实没动”。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











