Docker Composeのvolumeはbindとnamedどちらを使う?
Docker Composeのbind mountとnamed volumeの使い分けを解説します。設定とデータの適性、権限エラーの原因、inspectでの確認、バックアップと移行方法を紹介します。
Bind mountとnamed volume:要点
Docker Composeのvolumeには2種類あり、選択の基準はファイルの所有者です。自分で書き込みと読み取りを行うファイルにはbind mountを使用します。設定、テンプレート、静的サイトなどが該当します。アプリケーションが管理するデータにはnamed volumeを使用します。データベースファイル、検索インデックス、アップロードされたメディアなどが該当します。bind mountは、エディターで開けるホスト上のパスを指定します。named volumeはDockerが作成して管理するストレージで、Dockerを介してアクセスします。
どちらもサービス内の同じvolumes:キーに記述するため、混同されます。違いはコロンの左側です。左側が.または/で始まる場合はホストパスなので、bind mountです。それ以外は名前なので、named volumeです。その名前はトップレベルのvolumes:ブロックでも宣言する必要があります。
composeファイルの2つの構文
services:
db:
image: postgres:17
environment:
POSTGRES_PASSWORD: changeme
volumes:
- pgdata:/var/lib/postgresql/data
web:
image: nginx:1.27
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./site:/usr/share/nginx/html:ro
volumes:
pgdata:pgdata:/var/lib/postgresql/dataは名前付きボリュームです。./nginx.conf:/etc/nginx/nginx.confはbind mountで、:roはそれをread onlyでmountします。これは、コンテナが書き換えてはならない設定に適したデフォルトです。トップレベルのvolumes:エントリを忘れると、Composeはservice "db" refers to undefined volume pgdataで停止します。
起動して、Dockerが作成したものを一覧表示します。
docker compose up -d
docker volume lsボリュームの名前はpgdataではありません。<project>_pgdataという名前です。プロジェクト名は、デフォルトではcomposeファイルを格納しているディレクトリの名前になります。myappというディレクトリでは、myapp_pgdataになります。これは、ディレクトリ名を変更すると新しい空のボリュームが作成され、アプリケーションからデータが失われたように見えるため重要です。実際には失われていません。古いボリュームはdocker volume lsで引き続き一覧表示されます。composeファイルでname:を指定するか、ディレクトリを移動する可能性がある場合はCOMPOSE_PROJECT_NAMEを設定してください。このような設定は、他のComposeの環境ファイルとシークレットと一緒に管理します。
bind mountで権限エラーが発生する理由
これは最も実用的な違いです。1つのルールが原因です。初回使用時に空の名前付きvolumeはimageから内容をコピーされますが、bind mountではコピーされません。
Dockerが、image内ですでに内容があるdirectoryに空の名前付きvolumeをmountすると、その内容をvolumeにコピーします。所有者とmodeはimageで設定されたものが引き継がれます。公式のPostgres imageでは、/var/lib/postgresql/dataは専用のpostgres userが所有しています。そのため、volumeも同じnumeric idの所有になり、databaseが起動します。
bind mountでは逆になります。host上にあるものが、所有権を含めてそのままcontainerから見えます。そのpathにあるimageの内容は隠されます。hostのdirectoryが存在しない場合、Docker daemonが作成します。daemonはrootとして実行されるため、root:rootが所有するdirectoryになります。non-root userとして実行されるcontainer processは、そこに書き込めません。
PermissionError: [Errno 13] Permission denied: '/data/app.db'解決策は、数値を一致させることです。bind mountでは、所有権はnameではなくnumeric user idで比較されます。containerには独自の/etc/passwdがあるためです。container内のappという名前のuserは、host上では意味を持ちません。Uid 1000は、両方でuid 1000を意味します。
id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./datadocker compose exec web idは、container processが実際に実行されているuidを表示します。hostのdirectoryをその数値に合わせるか、serviceでuser: "1000:1000"を使ってcontainerを自分のuidに固定します。自分で作成したapplicationでは、user:を固定する方法が適しています。自分で作成していないimageでは、host directoryの所有者を変更するほうが安全です。一部のimageはrootとしてentrypointを開始し、その後権限を下げ、内部に特定の所有権が設定されていることを想定するためです。
もう2つ注意点があります。Fedora、RHELなどでSELinux(security-enhanced Linux)がenforcingの場合、bind mountはrelabelされるまで拒否されます。そのため、container間で共有するpathには:zを、1つのcontainerだけが使用するpathには:Zを追加し、- ./data:/data:Zの形式で記述します。また、directoryではなく単一のfileをbind mountすると、editorがfileをその場で書き換えずに置き換えた場合に動作しなくなります。mountは元のinodeを参照するためです。containerは、再起動するまで古い内容を見続けます。fileを頻繁に編集する場合は、親directoryをmountしてください。
パフォーマンス: 差が生じる場所
Linuxサーバーでは、どちらも同じカーネルパスを通るため、スループットの差は小さいものです。この点だけを理由に選択しないでください。デフォルトの local ドライバーを使用する名前付きボリュームは、/var/lib/docker/volumes/ 配下のDockerの他のデータと同じファイルシステム上に保存されます。バインドマウントは、指定した場所に保存されます。
差が現れるのは、コンテナが仮想マシン内で動作するDocker Desktop for macOSおよびWindowsです。そこでのバインドマウントは、ファイル共有レイヤーを介してホストのファイルシステムから仮想マシンへアクセスします。そのため、Node.jsの依存関係ツリーやPHPフレームワークのキャッシュなど、多数の小さなファイル操作を行うワークロードでは、速度が明らかに低下します。名前付きボリュームは仮想マシン内にとどまるため、このコストは発生しません。このため、多くの開発用composeファイルではソースディレクトリをバインドマウントし、node_modules に名前付きボリュームを割り当てています。
もう1つの実質的な違いは、データの保存先です。/mnt/backup へのバインドマウントでは、データがそのディスクに保存されます。名前付きボリュームは、/var/lib/docker を保持するファイルシステムに保存されます。VPSでは通常、これはrootディスクです。名前付きボリューム内でデータベースが増大すると、システムログと同じディスクを消費します。障害になる前に確認してください。
docker system df -v
df -h /var/lib/dockerdocker system df -v はすべてのボリュームとそのサイズを一覧表示し、どのコンテナからも参照されなくなったボリュームに印を付けます。
名前付きボリュームの確認
名前付きボリュームはブラックボックスではありません。Docker に場所を確認させます。
docker volume inspect myapp_pgdataMountpoint フィールドには、通常 /var/lib/docker/volumes/myapp_pgdata/_data という実際のホストパスが表示されます。sudo ls で読み取ることができ、簡単な確認に便利です。ここをファイル編集用の場所として扱わないでください。root としてここに書き込むと、前述の所有権の問題が再発します。また、このパスは local ドライバー固有の詳細であり、他のボリュームドライバーには共通しません。
安全に内部を確認するには、ボリュームをマウントした使い捨てコンテナを使用します。
docker run --rm -v myapp_pgdata:/vol alpine ls -la /volこれはどのドライバーでも機能します。実際のコンテナと同じ権限を確認できます。また、--rm により後に何も残りません。
各種類のバックアップ
bind mountは通常のディレクトリなので、ファイル単位のバックアップツールでそのまま処理できます。バックアップ対象にホスト側のパスを指定すれば完了です。named volumeでは、ツールが内部にアクセスできるように追加の手順が必要です。volumeとホストのディレクトリを同じ短時間だけ実行するコンテナにマウントし、アーカイブを作成します。
docker run --rm \
-v myapp_pgdata:/data:ro \
-v "$PWD":/backup \
alpine tar czf /backup/pgdata.tar.gz -C /data .新しいvolumeに逆の手順で展開して復元します。
docker volume create myapp_pgdata_restored
docker run --rm \
-v myapp_pgdata_restored:/data \
-v "$PWD":/backup \
alpine tar xzf /backup/pgdata.tar.gz -C /datarootとしてコンテナ内で実行する際、tarによって数値の所有権が保持されます。これにより、復元したvolumeをアプリケーションで使用できます。
両方の種類に共通する注意点があります。データベースの実行中にファイルをコピーすると、変化中の状態をアーカイブすることになり、復元後に破損した状態になる可能性があります。先にサービスを停止するか、docker compose exec -T db pg_dump -U postgres appdb > appdb.sqlのようにデータベース独自のツールでダンプしてください。これによりプレーンファイルが作成されるため、composeファイルとともに通常の暗号化されたresticバックアップ手順へ含めることができます。
bind mountをnamed volumeに移行する
移行はrenameではなくcopyです。所要時間は約1分です。
docker compose down
docker volume create myapp_pgdata
docker run --rm \
-v "$PWD/data":/from \
-v myapp_pgdata:/to \
alpine sh -c 'cp -a /from/. /to/'cp -aはownership、mode、timestampを保持します。そのため、以前のdirectoryをreadできたcontainer userは、新しいvolumeもreadできます。次に、serviceがpgdata:/var/lib/postgresql/dataを使用するように変更し、top-levelのvolumes: blockにpgdataを追加して、docker compose up -dを実行します。古いdirectoryをdeleteする前に、applicationのlogsを確認してください。逆方向に移行する場合は、同じcommandで/fromと/toを入れ替えます。
テスト中は、1つ注意してください。docker compose downはnamed volumeをそのまま残しますが、docker compose down -vはprojectが宣言するすべてのnamed volumeをdeleteし、取り消すことはできません。bind mountはどちらの場合も残ります。Dockerがそのdirectoryを所有していないためです。lifecycle commandにまだ慣れていない場合は、VPS向けDocker Composeの基本ガイドで手順を確認できます。
サービスごとの選択
ファイルを誰が書き込むかを確認します。テキストエディターで編集して git にコミットする設定ファイルは、:ro にマウントする bind mount に置きます。ファイルを参照でき、バージョン管理もできるためです。手動で開くことのないアプリケーションの状態データは named volume に置きます。Docker が権限を正しく初期設定し、データがホストのパスに依存しないためです。
メディアは中間的なケースです。写真ライブラリはアプリケーションが書き込みますが、ユーザーも管理します。また、特定のディスクに置きたいほど大容量になることがよくあります。そのディスク上のパスに bind mount し、所有権を最初に明示的に設定します。多くの self-hosted スタックでは、これが一般的な構成です。データベースとキャッシュには named volume を使用し、設定ファイルと管理対象の大容量ディレクトリには bind mount を使用します。
FAQ
bind mountとnamed volumeの違いは何ですか?
bind mountはホスト上のパスをコンテナ内に割り当てます。そのため、双方から同じディレクトリが見え、通常のツールで編集できます。named volumeはDockerが作成して管理するストレージです。名前で参照し、最上位のvolumes:ブロックで宣言します。実際の使い分けは所有権です。自分で管理する設定にはbind mountを使用し、アプリケーションが管理するデータにはnamed volumeを使用します。
bind mountでは「permission denied」になるのに、named volumeではならないのはなぜですか?
空のnamed volumeにはイメージの内容がコピーされます。そのため、イメージで設定された所有権を引き継ぎ、コンテナのユーザーが書き込めます。bind mountではホスト上のディレクトリがそのまま表示されます。Dockerがそのディレクトリを作成する必要があった場合、その所有者はrootになります。docker compose exec <service> idを実行してコンテナが使用する数値IDを確認し、次にホスト上のディレクトリでsudo chown -R <uid>:<gid>を実行してください。または、サービスでuser: "1000:1000"を設定してください。
Dockerはnamed volumeをディスク上のどこに保存しますか?
デフォルトのlocalドライバーでは、/var/lib/docker/volumes/<volume>/_data以下に保存されます。docker volume inspect <volume>を実行すると、正確なMountpointが表示されます。確認が必要な場合は読み取ってください。ただし、書き込みはコンテナを介してのみ行ってください。ホスト上でrootとして編集すると、コンテナが想定しない形で所有権が変更されるためです。
named volumeをバックアップするにはどうすればよいですか?
volumeとホスト上のディレクトリの両方をマウントした短時間のコンテナを起動し、docker run --rm -v myvol:/data:ro -v "$PWD":/backup alpine tar czf /backup/myvol.tar.gz -C /data .で一方から他方へアーカイブします。データベースの場合は、稼働中のファイルをコピーするのではなく、データベース固有のツールでダンプしてください。書き込み中に取得したファイルコピーは、破損した状態に復元される可能性があるためです。
docker compose downはvolumeを削除しますか?
docker compose downはコンテナとネットワークを削除しますが、named volumeは残します。docker compose down -vは、プロジェクトで宣言されたすべてのnamed volumeも削除します。この削除は完全です。どちらのコマンドでもbind mountは削除されません。そのディレクトリはDockerではなくホストに属するためです。