技术笔记 8 次阅读

做了个文件存储系统,被 Docker 缓存坑惨了

# 原创 # 技术分享

做了个文件存储系统,被 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 会检查:

  1. COPY / ADD —— 看被复制文件的 checksum 变了没
  2. RUN —— 看上一条命令的缓存命中了没
  3. 其他 —— 看命令字符串变了没

问题就出在 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 .

这不是最佳实践,但确实是解决玄学问题的终极方案。

事后反思

这次踩坑让我明白了几个道理:

  1. 永远不要信任 Docker 缓存。 能用 .dockerignore 就老老实实用,别偷懒。
  2. Dockerfile 里的 COPY 要尽可能精确。 COPY . /app 是偷懒写法,COPY ./app /app 更可控。
  3. 镜像里不要包含任何运行时不需要的文件。 这不仅是缓存问题,也是安全问题。

最后,这个 Bug 其实是我 2025 年犯的最蠢的错误之一。分享出来让大家开心一下,顺便涨个教训。

你们有没有被 Docker 缓存坑过?评论区说说你的故事。

分享

评论 (0)

评论通过后显示

暂无评论,来写第一条吧 ✍️

0.175648s