SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-26

Docker Composeのbindとnamed volumeの使い分け

Docker Composeのbind mountとnamed volumeはどちらを選ぶべきですか。設定とデータの用途、権限エラーの落とし穴、確認、バックアップ、移行方法を解説します。

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 は読み取り専用でマウントします。コンテナが書き換える必要のない設定には、これが適切なデフォルトです。トップレベルの 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 の環境ファイルと Secret と一緒に管理します。

権限エラーが bind mount でのみ発生しやすい理由

これは実務上の最大の違いであり、1 つのルールに起因します。初回使用時に空の named volume はイメージから内容を初期化されますが、bind mount は初期化されません。

Docker が、イメージ内ですでに内容があるディレクトリに空の named volume をマウントすると、その内容をイメージで設定された所有者とモードのまま volume にコピーします。公式の Postgres イメージでは、/var/lib/postgresql/data は専用の postgres ユーザーが所有しています。そのため、volume も同じ数値 ID で所有され、データベースが起動します。

bind mount は逆の動作をします。ホスト上にあるものが所有権を含めてそのままコンテナから見え、イメージ内の同じパスにある内容は隠されます。ホスト側のディレクトリが存在しない場合、Docker daemon がそれを作成します。daemon は root として動作するため、root:root が所有するディレクトリになります。非 root ユーザーとして動作するコンテナプロセスは、そこへ書き込めません。

PermissionError: [Errno 13] Permission denied: '/data/app.db'

解決策は、数値を一致させることです。bind mount 上の所有権は、名前ではなく数値の user id で比較されます。コンテナには独自の /etc/passwd があるためです。コンテナ内の app という名前のユーザーは、ホスト上では意味を持ちません。Uid 1000 は、両方の環境で uid 1000 を意味します。

id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./data

docker compose exec web id は、コンテナプロセスが実際にどの uid で動作しているかを表示します。ホスト側のディレクトリをその番号に合わせるか、サービスで user: "1000:1000" を指定してコンテナを自分の番号で固定します。自分で作成したアプリケーションでは、user: を固定する方法がより明確です。自分で作成していないイメージでは、ホスト側のディレクトリに chown を実行するほうが安全です。イメージによっては、entrypoint を root として起動してから権限を下げ、内部に特定の所有権が設定されていることを前提にするためです。

さらに、知っておくべき注意点が 2 つあります。Fedora、RHEL などで SELinux (security-enhanced Linux) が enforcing の場合、bind mount は relabel するまで拒否されます。そのため、複数のコンテナで共有するパスには :z を、1 つのコンテナだけが使用するパスには :Z を追加し、- ./data:/data:Z と記述します。また、ディレクトリではなく単一ファイルを bind mount すると、エディターがファイルをその場で書き換えずに置き換えたときに機能しなくなります。mount は元の inode を参照するためです。コンテナは再起動するまで古い内容を参照し続けます。頻繁に編集するファイルでは、親ディレクトリを mount してください。

性能差が現れる場所

Linux サーバーでは、どちらも同じカーネル経路を通るため、スループットの差は小さく、この点だけを基準に選ぶ必要はありません。デフォルトの local ドライバーを使用する named volume は、/var/lib/docker/volumes/ 配下の Docker と同じファイルシステム上に配置されます。bind mount は、指定した場所に配置されます。

差が現れるのは、コンテナが仮想マシン内で実行される macOS および Windows の Docker Desktop です。そこでの bind mount は、ファイル共有層を介してホストのファイルシステムから仮想マシンへアクセスします。そのため、Node.js の依存関係ツリーや PHP フレームワークのキャッシュなど、多数の小さなファイル操作を行うワークロードでは、処理速度が明確に低下します。named volume は仮想マシン内に保持されるため、このコストがかかりません。これが、多くの開発用 compose ファイルでソースディレクトリを bind mount し、node_modules に対して named volume を宣言する理由です。

もう1つの実際の違いは、データの配置先です。/mnt/backup への bind mount では、データはそのディスクに配置されます。named volume は、/var/lib/docker を保持するファイルシステムに配置されます。VPS では通常、root ディスクです。named volume 内でデータベースが拡大すると、システムログと同じディスクを消費します。インシデントになる前に確認してください。

docker system df -v
df -h /var/lib/docker

docker system df -v はすべての volume とそのサイズを一覧表示し、どのコンテナからも参照されなくなった volume に印を付けます。

名前付きボリュームの確認

名前付きボリュームはブラックボックスではありません。Docker に保存場所を確認させます。

docker volume inspect myapp_pgdata

Mountpoint フィールドには、実際のホスト上のパスが表示されます。通常は /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 /data

tar は、コンテナ内で root として実行する際に数値の所有権を保持します。これにより、復元した volume をアプリケーションから使用できます。

両方の種類に共通する注意点があります。データベースの実行中にファイルをコピーすると、変化し続ける状態のアーカイブになります。そのため、破損した状態で復元される可能性があります。先にサービスを停止するか、docker compose exec -T db pg_dump -U postgres appdb > appdb.sql のようにデータベース固有のツールでダンプしてください。これにより通常のファイルが作成されるため、compose ファイルとともに、通常の 暗号化された restic バックアップ手順に含められます。

bind mount を named volume に移行する

移行は名前の変更ではなくコピーです。所要時間は約 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 は所有者、モード、タイムスタンプを保持します。そのため、旧ディレクトリを読み取れたコンテナユーザーは、新しい volume も読み取れます。次に、サービスが pgdata:/var/lib/postgresql/data を使用するように変更し、トップレベルの volumes: ブロックに pgdata を追加します。その後、docker compose up -d を実行し、旧ディレクトリを削除する前にアプリケーションのログを確認します。逆方向に移行する場合は、同じコマンドで /from/to を入れ替えます。

テスト中は、1 つだけ注意してください。docker compose down は named volume をそのまま残しますが、docker compose down -v はプロジェクトで宣言されているすべての named volume を削除し、元に戻せません。bind mount はどちらの場合も残ります。Docker がそのディレクトリを所有していないためです。ライフサイクル用コマンドにまだ慣れていない場合は、VPS 向け Docker Compose 基礎ガイドで手順を確認できます。

サービスごとに選ぶ

ファイルを誰が作成するかを確認します。テキストエディターで編集し、git に commit する設定は bind mount に置きます。:ro に mount して、内容を確認できる状態にし、バージョン管理するためです。手動で開くことのないアプリケーションの状態は named volume に置きます。Docker が適切な権限を設定し、データがホストのパスに依存しないためです。

中間的なケースがメディアです。写真ライブラリはアプリケーションが書き込みますが、ユーザー自身も管理します。また、特定のディスクに置きたいほど大容量になることもあります。そのディスク上のパスに bind mount し、所有者を一度明示的に設定します。多くの self-hosted stack では、この構成に落ち着きます。データベースとキャッシュには named volume を使い、設定と、管理対象の大容量ディレクトリには bind mount を使います。VPS 上で稼働する Chatwoot のようなサポートデスクもこの構成になります。Postgres は named volume に置き、アップロードされた添付ファイルはバックアップ対象として指定できるパスに置きます。

FAQ

bind mount と named volume の違いは何ですか?

bind mount はホスト上のパスをコンテナ内にマッピングするため、両方から同じディレクトリを参照でき、通常のツールで編集できます。named volume は Docker が作成・管理するストレージで、名前で参照し、トップレベルの volumes: ブロックで宣言します。実際の使い分けは管理主体です。自分で管理する設定には bind mount、アプリケーションが管理するデータには named volume を使用します。

named volume では問題ないのに、bind mount で "permission denied" になるのはなぜですか?

空の 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 ではなくホストが所有しているためです。