7.2 COPY 複製檔案
何時使用 COPY
COPY 是在建立映象時,將建立上下文(Dockerfile 所在目錄及其子目錄)中的檔案或目錄複製到容器內的指令。它是處理應用程式碼、設定檔案最常用的方式。
典型場景:
- 複製應用原始碼:
COPY . /app - 複製設定檔案:
COPY nginx.conf /etc/nginx/nginx.conf - 複製靜態資源:
COPY public /app/public
為什麼 COPY 比 ADD 更值得推薦? 筆者建議在 90% 的情況下使用 COPY。原因是 COPY 的語義更清晰——它就是簡單地複製檔案。而 ADD 有額外的功能(自動解壓、支援 URL),這些功能往往會帶來意外的行為。除非你明確需要 ADD 的自動解壓功能,否則用 COPY。詳見 7.3 ADD 指令 中的詳細對比。
7.2.1 基本語法
COPY [選項] <源路徑>... <目標路徑>
COPY [選項] ["<源路徑1>", "<源路徑2>", ... "<目標路徑>"]
COPY 指令將建立上下文中的檔案或目錄複製到映象內。
7.2.2 基本用法
複製單個檔案
## 複製檔案到指定目錄
COPY package.json /app/
## 複製檔案並重命名
COPY config.json /app/settings.json
複製多個檔案
## 複製多個指定檔案
COPY package.json package-lock.json /app/
## 使用萬用字元
COPY *.json /app/
COPY src/*.js /app/src/
複製目錄
## 複製整個目錄的內容(不是目錄本身)
COPY src/ /app/src/
⚠️ 注意:複製目錄時,複製的是目錄的 內容,不包含目錄本身。
建立上下文: 映象內:
src/ /app/src/
├── index.js → ├── index.js
└── utils.js └── utils.js
7.2.3 萬用字元規則
COPY 支援 Go 的 filepath.Match 萬用字元規則:
| 萬用字元 | 說明 | 範例 |
|---|---|---|
* |
對應任意字元序列 | *.json |
? |
對應單個字元 | config?.json |
[abc] |
對應括號內任一字元 | [abc].txt |
[a-z] |
對應範圍內字元 | file[0-9].txt |
COPY hom* /mydir/ # home.txt, homework.md 等
COPY hom?.txt /mydir/ # home.txt, homy.txt 等
COPY app[0-9].js /app/ # app0.js ~ app9.js
7.2.4 目標路徑
絕對路徑
COPY app.js /usr/src/app/
相對路徑:基於 WORKDIR
WORKDIR /app
COPY package.json ./ # 複製到 /app/package.json
COPY src/ ./src/ # 複製到 /app/src/
自動建立目錄
如果目標目錄不存在,Docker 會自動建立:
## /app/config/ 不存在也會自動建立
COPY settings.json /app/config/
7.2.5 修改檔案所有者
使用 --chown 選項設定檔案的使用者和組:
## 使用使用者名和組名
COPY --chown=node:node package.json /app/
## 使用 UID 和 GID
COPY --chown=1000:1000 . /app/
## 只指定使用者
COPY --chown=node . /app/
💡 結合
USER指令使用,確保應用以非 root 使用者執行。
7.2.6 保留檔案元資料
COPY 會保留源檔案的元資料:
- 讀、寫、執行許可權
- 修改時間
這對於指令碼檔案特別重要:
## start.sh 的可執行許可權會被保留
COPY start.sh /app/
7.2.7 COPY vs ADD
| 屬性 | COPY | ADD |
|---|---|---|
| 複製本地檔案 | ✅ | ✅ |
| 自動解壓 tar | ❌ | ✅ |
| 支援 URL | ❌ | ✅ (不推薦) |
| 推薦程度 | ✅ 推薦 | ⚠️ 特殊場景使用 |
## 推薦:使用 COPY
COPY app.tar.gz /app/
RUN tar -xzf /app/app.tar.gz
## ADD 會自動解壓(行為不明顯,不推薦)
ADD app.tar.gz /app/
筆者建議:除非需要自動解壓 tar 檔案,否則始終使用 COPY。明確的行為比隱式的魔法更好。
7.2.8 多階段建立中的 COPY
從其他建立階段複製
## 建立階段
FROM node:22 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
## 生產階段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
使用 --link 最佳化快取
## 使用 --link 後,檔案以獨立層新增,不依賴前序指令
COPY --link --from=builder /app/dist /usr/share/nginx/html
--link 的優勢:
- 更高效利用建立快取
- 並行化建立過程
- 加速多階段建立
⚠️ 注意:使用
--link需要滿足以下條件:
- 啟用 BuildKit(Docker Engine 23.0+ 預設啟用)
- Dockerfile 語法版本 1.4 或更高(在首行宣告
# syntax=docker/dockerfile:1)- 目標路徑在前序指令中應不存在或為空目錄
7.2.9 dockerignore
使用 .dockerignore 排除不需要複製的檔案:
## .dockerignore
node_modules
.git
.env
*.log
Dockerfile
.dockerignore
這可以:
- 減小建立上下文大小
- 加速建立
- 避免複製敏感檔案
7.2.10 最佳實踐
1. 利用快取,先複製依賴檔案
## ✅ 好:先複製依賴定義,再安裝,最後複製程式碼
COPY package.json package-lock.json ./
RUN npm install
COPY . .
## ❌ 差:一次性複製所有檔案,程式碼變更會導致重新 npm install
COPY . .
RUN npm install
2. 使用 .dockerignore
## 確保 node_modules 不被複製
COPY . .
## .dockerignore 中應包含 node_modules
...
3. 明確複製路徑
## ✅ 好:明確的路徑
COPY src/ /app/src/
COPY package.json /app/
## ❌ 差:過於寬泛
COPY . .
🔥 踩坑實錄
某公司在最佳化 Node.js 應用的 Docker 映象時,發現建立出來的映象體積超過了 2GB,遠遠超過生產部署的需求。排查發現,Dockerfile 中使用了
COPY . .把整個建立上下文複製進映象,導致node_modules/、.git/目錄和大量測試資料全部被打包進映象。最初他們沒有建立.dockerignore檔案,預設會複製所有檔案。解決方案是新增一個.dockerignore檔案,排除這些不必要的目錄,使映象縮小到了 200MB。這個教訓深刻地說明瞭:.dockerignore和.gitignore一樣重要,應該在專案初始化時就建立,而不是等到出現問題時才想起來。建議的標準做法是先複製package.json和package-lock.json安裝依賴,再複製應用程式碼,同時在.dockerignore中明確列出node_modules、.git、test 目錄等。