Dockerfile の COPY
命令は、一見すると単純なファイルコピーのように見えます。
しかし、ビルドキャッシュ、イメージサイズ、セキュリティ、そして開発効率に直結する“奥の深いコマンド”でもあります。
本記事では、COPY
の内部動作から、キャッシュ最適化・マルチステージビルド・セキュリティ強化までを掘り下げ、さらにハンズオン形式で実践的な最適化手法を紹介します。
1. COPYとADDの違いを再確認する
まず基本をおさらいします。
COPY
:ローカルのファイルをイメージにコピーする(単純で安全)ADD
:COPY
の機能に加えて、アーカイブ展開やURLダウンロードが可能
表面的にはADD
の方が便利ですが、セキュリティと予測可能性の観点から、
原則としてCOPY
を使用すべきとされています。
NG例:ADD
で外部URLを直接ダウンロード
ADD https://example.com/script.sh /usr/local/bin/
この場合、ビルド再現性が失われる上、外部ファイルの改ざんリスクも生じます。
正しくは、curl
やwget
で取得し、署名検証を行うのがベストです。
2. キャッシュを意識したCOPYの構成
Dockerのビルドはレイヤーキャッシュを活用して高速化されています。
しかし、COPY
命令の位置やファイル構成を誤ると、このキャッシュが無効化され、毎回フルビルドが走ってしまいます。
悪い例(毎回キャッシュが壊れる)
FROM node:20-slim
WORKDIR /app
COPY . .
RUN npm install
この場合、ソースコードが変更されるたびにnpm install
も再実行されます。
改善例(キャッシュ効率を最大化)
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
依存関係(package*.json
)のみを先にコピーし、npm ci
を実行。
アプリ本体を後からコピーすることで、依存が変わらない限りキャッシュを再利用できます。
3. ハンズオン:キャッシュ最適化を実際に試す
ここでは、Node.jsプロジェクトを例に、COPY
最適化によるビルド時間短縮を体感します。
手順1:テスト用アプリを作成
mkdir docker-copy-demo && cd docker-copy-demo
npm init -y
echo 'console.log("Hello COPY Optimization!");' > index.js
npm install express
手順2:Dockerfileを作成
# Dockerfile
FROM node:20-slim
WORKDIR /app
# 依存関係のみ先にコピー
COPY package*.json ./
RUN npm ci
# アプリ本体を後からコピー
COPY . .
CMD ["node", "index.js"]
手順3:ビルドとキャッシュ確認
docker build -t copy-demo:latest .
次に、index.js
を少し変更して再ビルドします。
echo 'console.log("COPY caching test");' > index.js
docker build -t copy-demo:latest .
2回目のビルドでは npm ci
ステップがキャッシュされ、非常に高速に完了するはずです。
これが、COPY
最適化の威力です。
4. マルチステージビルドとCOPY –fromの活用
アプリケーションによっては、ビルドとランタイムを別のステージに分離した方が効率的です。COPY --from
を使うことで、不要な依存を最終イメージに含めずに済みます。
実践例:Node.js + Nginxの静的デプロイ
# ビルド用ステージ
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# デプロイ用ステージ
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
この構成では、最終的なイメージにはnode_modules
や開発ツールが含まれません。
結果として、軽量で安全なイメージを生成できます。
5. セキュリティと権限管理を明示する
Docker 18.09以降では、BuildKitによりCOPY
命令に新しいオプションが追加されています。
COPY --chown=appuser:appgroup --chmod=755 ./entrypoint.sh /usr/local/bin/
これにより、コンテナ内のファイル所有者や権限をビルド時に明示的に設定できます。
権限設定を怠ると、思わぬ実行エラーやセキュリティ事故の原因になります。
また、.dockerignore
を適切に設定して、機密情報や開発用ファイルが含まれないようにしましょう。
# .dockerignore
.git
node_modules
.env
*.log
6. BuildKitによるCOPYの進化
BuildKit有効化時(DOCKER_BUILDKIT=1
)には、COPY
命令がさらに強化されます。
新機能例
--link
オプションによるレイヤー共有(Docker 25以降)--mount=type=cache
と併用した高速ビルド
BuildKitを有効にするには、環境変数を設定してビルドします。
DOCKER_BUILDKIT=1 docker build -t copy-optimized .
この設定だけで、ビルド時間が数十%短縮されるケースもあります。
7. トラブルシューティング
COPY
に関連する典型的なエラーをいくつか紹介します。
エラー内容 | 原因 | 対処法 |
---|---|---|
COPY failed: no source files were specified | ファイルパスが間違っている | COPY パスはビルドコンテキスト内の相対パスで指定 |
COPY failed: stat ... no such file or directory | .dockerignore で除外されている | .dockerignore 設定を見直す |
Permission denied | ファイル権限不適切 | --chmod または--chown を明示 |
8. まとめと次のステップ
COPY
はDockerfileの中でもビルド効率とセキュリティを左右する重要な命令。.dockerignore
とCOPY
の組み合わせでキャッシュを守り、不要ファイルを除外する。COPY --from
でマルチステージを設計すれば、軽量かつ安全なプロダクション環境を構築できる。
次のステップ:
- CI/CD環境で
--cache-from
を活用してビルドキャッシュを共有 - TrivyなどのセキュリティスキャンでCOPYレイヤーの脆弱性をチェック
- BuildKitの
--link
や--mount=type=cache
を用いて、さらなる高速化を追求
このように、COPY
命令は「ファイルをコピーする」以上の戦略的な要素を持っています。
Dockerfileの最適化において、最も見落とされがちな部分を磨き上げることが、
ビルドパフォーマンスと運用品質の差を生むのです。
コメントを残す