はじめに
Docker Composeで分析環境を構築していたところ、コンテナを再ビルドする際に次のような表示が出ました。
Sending build context to Docker daemon 2.874GB
しかも、転送量が2GBを超えても増え続けていました。
プロジェクト内の重いデータを一部削除しても状況が改善しなかったため、原因を調べたところ、Dockerの以下の2つの仕組みを混同していたことが分かりました。
- Dockerイメージのビルド時に送信される「ビルドコンテキスト」
- コンテナ起動後にホストのファイルを見せる「ボリュームマウント」
この記事では、大容量の分析データをDockerイメージのビルド対象から除外しつつ、コンテナから利用できるようにする方法をまとめます。
発生した問題
当初のdocker-compose.ymlでは、次のようにプロジェクトディレクトリ全体をビルドコンテキストとし、同じディレクトリをコンテナの/workspaceへマウントしていました。
services:
analysis-env:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./:/workspace
この状態で以下を実行しました。
docker compose up -d --build --force-recreate
すると、Dockerデーモンへ数GBのデータが送信され始めました。
Sending build context to Docker daemon 2.874GB
原因
原因は次の設定です。
build:
context: .
.は、docker-compose.ymlが置かれているプロジェクトディレクトリ全体を意味します。
たとえば、プロジェクトが次の構成だったとします。
my-project/
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
├── src/
├── notebooks/
├── analysis
│ ├── dataset.csv
│ ├── outcome.parquet
│ └── model.bin
└── .venv/
context: .を指定すると、Dockerは原則としてmy-project配下のファイルを再帰的にビルドコンテキストへ含めます。
そのため、以下のようなファイルがあると送信量が急増します。
- CSVやParquetなどの分析データ
- 学習済みモデル
- ZIPファイル
- Pythonの仮想環境
- キャッシュ
- Gitの履歴
- チェックポイント
- 分析結果
重要なのは、これはDockerイメージに必ずコピーされるという意味ではないことです。
DockerfileのCOPY対象になる前段階として、Dockerデーモンへビルドコンテキストが送信されます。そのため、Dockerfileで使わない大容量ファイルであっても、除外設定をしなければ転送対象になる可能性があります。
ビルドコンテキストとボリュームマウントの違い
今回、特に混乱したのが次の2つです。
ビルドコンテキスト
build:
context: .
Dockerイメージを作る際に、Dockerfileから参照できるファイルの範囲を指定します。
ビルドコンテキストに含まれるファイルは、ビルド開始時にDockerデーモンへ送信されます。
ボリュームマウント
volumes:
- ./:/workspace
コンテナ起動後に、ホスト側のディレクトリをコンテナ内から見えるようにします。
この例では、ホスト側の、
/home/user/my-project
がコンテナ内の、
/workspace
として見えます。
つまり、分析データをビルドコンテキストから除外しても、ボリュームマウントされていれば、コンテナ起動後にデータを利用できます。
.dockerignoreはビルドコンテキストに影響しますが、ボリュームマウントには影響しません。
解決方法
今回は、次の構成に変更しました。
- 大容量の分析データをプロジェクト外の専用ディレクトリへ移動する
- そのディレクトリをコンテナへ明示的にマウントする
-
.dockerignoreで不要なファイルをビルド対象から除外する
大容量データを別ディレクトリへ移動する
たとえば、分析データを次の場所へ分離します。
/home/user/data/
プロジェクト本体は次の場所にあるものとします。
/home/user/my-project/
ディレクトリ構成は次のようになります。
/home/user/
├── my-project/
│ ├── Dockerfile
│ ├── docker-compose.yml
│ ├── requirements.txt
│ ├── src/
│ └── notebooks/
└── data/
├── raw/
├── processed/
├── models/
└── checkpoints/
データ用ディレクトリを作成します。
mkdir -p /home/user/data
既存のanalysisディレクトリを移動する場合は、次のようにします。
mv /home/user/my-project/analysis/* /home/user/data/
隠しファイルも含めて移動したい場合、単純な*では不十分なので注意が必要です。
rsyncが利用できる場合は、次の方法が安全です。
rsync -a --remove-source-files \
/home/user/my-project/analysis/ \
/home/user/data/
空になったサブディレクトリを削除します。
find /home/kobayashi/my-project/DL \
-mindepth 1 \
-type d \
-empty \
-delete
移動前後の容量は次のコマンドで確認できます。
du -sh /home/user/my-project
du -sh /home/user/data
Docker Composeでデータディレクトリをマウントする
docker-compose.ymlに、データディレクトリのマウントを追加します。
services:
analysis-env:
container_name: analysis-container
build:
context: .
dockerfile: Dockerfile
volumes:
# プログラムやNotebook
- ./:/workspace
# 大容量の分析データ
- /home/user/data:/data
stdin_open: true
tty: true
これにより、ホスト側の、
/home/user/data
がコンテナ内では、
/data
として見えるようになります。
.dockerignoreを作成する
大容量データを別ディレクトリへ移した場合でも、プロジェクト内に仮想環境やキャッシュが残っているとビルドコンテキストが膨らみます。
そこで、Dockerfileと同じディレクトリに.dockerignoreを作成します。
cd /home/user/my-project
touch .dockerignore
例として、次のように設定します。
# Git
.git
.git/**
# Python仮想環境
.venv
.venv/**
venv
venv/**
# Pythonキャッシュ
__pycache__
**/__pycache__
*.pyc
.pytest_cache
.mypy_cache
.ipynb_checkpoints
# 分析データ
data
data/**
datasets
datasets/**
*.csv
*.parquet
*.jsonl
# 圧縮ファイル
*.zip
*.tar
*.tar.gz
*.7z
# モデル・チェックポイント
models
models/**
checkpoints
checkpoints/**
outputs
outputs/**
results
results/**
*.pt
*.pth
*.ckpt
*.safetensors
*.bin
# キャッシュ
.cache
.cache/**
**/.cache/**
node_modules
node_modules/**
# 環境変数・認証情報
.env
.env/**
*.env
# ログ
*.log
# エディタ
.vscode
.idea
.dockerignoreは、必ずビルドコンテキストのルートへ配置します。
今回の設定では、
build:
context: .
であり、プロジェクトの場所が、
/home/user/my-project
なので、.dockerignoreの場所は次になります。
/home/user/my-project/.dockerignore
DockerfileのCOPYも確認する
.dockerignoreで除外したファイルは、DockerfileからCOPYできなくなります。
そのため、DockerfileのCOPYやADDを確認します。
grep -nE '^[[:space:]]*(COPY|ADD)' Dockerfile
たとえば次の構成なら問題ありません。
COPY requirements.txt /tmp/requirements.txt
RUN pip install --no-cache-dir \
-r /tmp/requirements.txt
COPY src/ /app/src/
一方で、次のようにプロジェクト全体をコピーしている場合は注意が必要です。
COPY . /workspace
.dockerignoreで除外したCSVやParquetは、COPY .の対象にもなりません。
ただし、今回のようにComposeで、
volumes:
- ./:/workspace
- /home/user/data:/data
とマウントする場合、分析データをDockerイメージへコピーする必要はありません。
むしろ、Dockerイメージには次のものだけを含める方が管理しやすくなります。
- OSパッケージ
- Python
- Pythonライブラリ
- 実行に必要なツール
- 必要最低限のスクリプト
分析データや出力結果はホスト側で管理し、起動時にマウントします。
どのファイルが大きいか確認する
ビルドコンテキストがまだ大きい場合は、プロジェクト直下の容量を確認します。
cd /home/user/my-project
du -xhd1 . 2>/dev/null | sort -h
大きなファイルを上位から表示するには、次のコマンドが便利です。
find . -xdev -type f -printf '%s\t%p\n' 2>/dev/null \
| sort -nr \
| head -30 \
| numfmt --field=1 --to=iec
たとえば、次のようなファイルが見つかる可能性があります。
4.0G ./.venv/lib/python3.10/site-packages/...
2.3G ./models/model.safetensors
1.8G ./data/articles.parquet
900M ./checkpoints/checkpoint.pt
対象に応じて、別ディレクトリへ移動するか.dockerignoreへ追加します。
設定を検証する
Composeファイルの文法と展開結果を確認します。
docker compose config
このコマンドでは、環境変数やenv_fileの内容が展開される場合があります。
出力を外部へ共有する場合は、認証キーやパスワードが含まれていないか確認する必要があります。
秘密情報を簡易的に伏せて表示する場合は、次のようにできます。
docker compose config \
| sed -E \
's/(PASSWORD|TOKEN|AUTHKEY):.*/\1: "***REDACTED***"/'
再ビルドする
設定変更後、まずビルドだけ実行します。
docker compose build
以前は次のように数GB送られていました。
Sending build context to Docker daemon 2.874GB
.dockerignoreが正しく効いていれば、プロジェクトの構成にもよりますが、数MBから数百MB程度まで減ります。
ビルドに成功したらコンテナを起動します。
docker compose up -d --force-recreate
docker compose buildを先に実行していれば、ここでは原則として--buildを付ける必要はありません。
まとめ
今回の問題は、build.context: .によって分析データや仮想環境を含むプロジェクト全体がビルドコンテキストとして送信されていたことが原因でした。
解決策は次の3点です。
- 大容量データをプロジェクト外へ分離する
- Docker Composeの
volumesでコンテナへ明示的にマウントする -
.dockerignoreで不要なファイルをビルドコンテキストから除外する
特に重要なのは、.dockerignoreで除外しても、ボリュームマウントされていればコンテナからデータを利用できるという点です。
ビルドコンテキストから除外
↓
Dockerイメージのビルドには送られない
ボリュームとしてマウント
↓
コンテナ起動後には利用できる
分析用のDocker環境では、実行環境と分析データを分離して管理することで、ビルド時間、イメージ容量、データの永続性を改善できます。