Dockerfile 中如何用 ADD 命令复制目录
ADD 与 COPY 都可将构建上下文目录的内容复制进镜像。本地复制优先 COPY;ADD 还支持压缩包及远程资源,使用时核对校验和、版本和权限。
一句话回答:ADD ./本地目录 /镜像内路径 就能把目录整个复制进镜像。但 Docker 官方建议:本地文件/目录一律用 COPY,ADD 只留给它独有的额外能力,如自动解压本地 tar、添加远程 HTTP 或 Git 资源。COPY 语义透明、行为可预期,ADD 的"智能"反而常造成困惑。
基本用法
# 把构建上下文里的 myapp 目录复制到镜像的 /app
ADD ./myapp /app
# 等价的推荐写法
COPY ./myapp /app
注意:源路径相对构建上下文(docker build 最后那个路径),不是 Dockerfile 所在目录;上下文外的文件复制不进来。
ADD 与 COPY 的区别
| 能力 | COPY | ADD |
|---|---|---|
| 复制本地文件/目录 | ✅ | ✅ |
| 自动解压本地 tar/tar.gz | ❌(原样复制) | ✅(解压到目标路径) |
| HTTP/Git 远程资源 | 不直接支持(COPY 可从命名上下文/阶段复制) | 支持,需按版本使用 checksum 或固定提交 |
| 使用原则 | 普通本地复制优先 | 确实需要额外能力时使用并锁定来源 |
ADD 的坑: 源是压缩包时它悄悄解压,源是普通目录时它直接复制——同一个指令行为不一致,镜像里出现什么都不意外。COPY 则永远"所见即所得"。
目录复制的三个细节
-
结尾斜杠语义:
COPY ./myapp /app是把 myapp 的内容放进 /app(/app 即 myapp 本身);写成COPY ./myapp /app/myapp则会在 /app 下再建一层 -
用 .dockerignore 控制内容:
node_modules、.git、日志文件别带进镜像:node_modules .git *.log -
权限与属主:
COPY --chown=node:node ./myapp /app设置 Linux 镜像中的属主(该用户/组须存在),仍需检查文件权限与挂载覆盖
多阶段构建场景
目录复制常与多阶段构建配合——构建阶段装依赖编译,运行阶段只 COPY 产物:
FROM node:22 AS build
WORKDIR /app
COPY . .
RUN npm ci && npm run build
FROM node:22-slim
WORKDIR /app
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server.js"]
这是“dist 已包含全部运行时依赖”的最小示例,不能直接用于任意 Node 项目。若 dist 仍引用外部包,运行阶段还需复制 package.json/lock 并安装生产依赖(如 npm ci --omit=dev);按项目选取 Node 受支持版本、非 root 用户和基础镜像,实际体积需测量。
常见问题(FAQ)
Q:ADD 从 URL 下载文件为什么被反对?
A:不能一概反对。当前 Dockerfile 的 ADD 支持 --checksum=sha256:... 校验 HTTP 资源,远程 Git 也可固定提交;是否支持具体选项需核对构建器版本。curl | tar 并不会自动做完整性校验,也不是更安全的默认替代。普通本地复制仍优先 COPY。
Q:复制目录后容器里文件属主是 root,应用读不了?
A:构建时加 --chown:COPY --chown=1000:1000 ./myapp /app;或在 Dockerfile 里 RUN chown -R(会产生额外镜像层,不如 --chown)。
Q:改了目录里的文件,为什么 build 没更新?
A:Docker 构建缓存按 COPY/ADD 的文件内容校验和判断,内容没变才命中缓存。如果真的变了却没更新,检查 .dockerignore 是否把该文件排除了,或 --no-cache 强制重建验证。
核查依据
本文依据官方文档核对,示例未在实际业务环境运行;上线前请按部署版本、权限与数据范围验证。