做了个文件存储系统,被 Docker 缓存坑惨了
说一个真实踩坑经历。
上周我在做一个简单的文件存储服务——用户上传图片,服务端存到 S3,然后返回 URL。逻辑不复杂,照着文档撸了两个小时就写完了,本地测也没问题。
然后部署到测试服务器上,出事了。
问题现象
用户上传头像之后,返回的 URL 能访问,但是打开图片一看——是上一张图。
就是 A 上传了猫的图片,返回了猫的 URL。B 上传了狗的图片,返回的也是猫的 URL。C 上传了风景照,嗯,还是猫。
第一反应是缓存 —— Nginx 缓存、CDN 缓存、浏览器缓存全都排查了一遍,没问题。
第二反应是代码问题 —— 检查文件命名、检查存储逻辑、检查数据库记录,也没问题。
头大了。这是撞鬼了?
排查过程
搞了两个小时没找到原因,突然想起之前看到过一篇文章提到过的 Docker 缓存问题。
去服务器上一看,果然是 Docker 构建缓存惹的祸。
事情是这样的:我的 Dockerfile 里有这么一段:
COPY . /app
RUN pip install -r requirements.txt
而我的项目根目录下有个 uploads/ 文件夹,里面是一些测试文件。这个文件夹被 COPY 到了镜像里。
按理说这也不会出问题,但我犯了一个低级错误——我用的上传逻辑是按时间戳命名文件,而且本地测试的时候很多图片是同一秒上传的,所以文件名冲突了。
但我改完代码重新部署的时候,Docker 用了缓存层,新的 uploads/ 测试文件没被 COPY 进去,项目就用了旧的测试文件……
其实是自己的锅,镜像打包时偷偷带了测试数据。
Docker 缓存的工作原理
这次踩坑让我好好研究了一下 Docker 缓存。
简单说,Docker 构建镜像的时候,每一行指令都会生成一个缓存层。下次构建如果某条指令的内容没变,Docker 就复用之前的缓存层。
你怎么判断指令没变?Docker 会检查:
COPY/ADD—— 看被复制文件的 checksum 变了没RUN—— 看上一条命令的缓存命中了没- 其他 —— 看命令字符串变了没
问题就出在 COPY . /app 这条指令上。因为 Dockerfile 在构建上下文里,所以改了 Dockerfile 不代表 COPY . 的缓存会失效。实际触发缓存失效的是被 COPY 的那些文件有没有变化。
怎么避免
方案一:.dockerignore
这个是标准做法。在项目根目录下加个 .dockerignore:
__pycache__/
*.pyc
.env
.git
uploads/
node_modules/
意思就是告诉 Docker:打包镜像的时候,这些文件和目录别带进去。
方案二:多阶段构建
如果你追求更干净的镜像,可以用多阶段构建:
# 第一阶段:构建
FROM python:3.12-slim AS builder
COPY requirements.txt .
RUN pip install --user -r requirements.txt
# 第二阶段:运行
FROM python:3.12-slim
COPY --from=builder /root/.local /root/.local
COPY ./app /app
CMD ["python", "app.py"]
这样第一个阶段处理依赖,第二个阶段只复制需要的内容,层次清晰多了。
方案三:--no-cache 大法
实在搞不定的时候,就无脑清理:
docker build --no-cache -t myapp .
这不是最佳实践,但确实是解决玄学问题的终极方案。
事后反思
这次踩坑让我明白了几个道理:
- 永远不要信任 Docker 缓存。 能用
.dockerignore就老老实实用,别偷懒。 - Dockerfile 里的 COPY 要尽可能精确。
COPY . /app是偷懒写法,COPY ./app /app更可控。 - 镜像里不要包含任何运行时不需要的文件。 这不仅是缓存问题,也是安全问题。
最后,这个 Bug 其实是我 2025 年犯的最蠢的错误之一。分享出来让大家开心一下,顺便涨个教训。
你们有没有被 Docker 缓存坑过?评论区说说你的故事。
评论 (0)
暂无评论,来写第一条吧 ✍️