【Docker】bind mount vs volume完全比較ガイド|10軸比較・実測ベンチ・5問判定・20シナリオ別正解・混在運用

【Docker】bind mountとvolumeの違いと使い分け Docker

./data:/var/lib/mysqldb_data:/var/lib/mysql、どっちを書けばいいんだっけ?」——Dockerで毎日書くのにbind mountvolumeの使い分けで迷う人は初心者からベテランまで絶えません。

選択を間違えると、①macOS/Windowsで体感で10倍遅い、②本番デプロイでデータが消える、③チーム全員の環境で権限エラーが頻発、④GitHub Actionsでの再現性がゼロ、と実害が出ます。逆に基本ルールを知れば3秒で正解が選べる領域です。

この記事では、bind mount と volume の本質的な違いを押さえた上で、10軸での完全比較表macOS/Windows/Linux 実測ベンチマーク5問で決まる判定フレームシナリオ20パターン別の正解混在運用パターン(WordPress/Laravel/Node.js)、bind→volume移行手順、アンチパターン7選まで、2026年の現場で迷わないための決定版ガイドとしてまとめます。

各方式の深い運用知識は【Docker】volume完全ガイド、Compose全般は【Docker】Compose完全ガイド、実案件での混在例は【Docker】WordPress環境構築完全ガイド【Docker】Nginx + PHP-FPM完全ガイドで補完できます。

この記事で学べること

  • bind mount/volume の本質的な違い(誰が管理するか・どこに保存されるか)
  • 10軸の完全比較(性能/移植性/安全性/管理性/権限/バックアップ等)
  • 実測ベンチマーク:macOS/Windows/Linuxでのファイル読込速度比較
  • 5問で決まる判定フレームワーク
  • シナリオ20パターン別の正解(開発/本番/CI)
  • WordPress・Laravel・Node.js/Next.jsでの混在運用パターン
  • bind→volume 移行手順(データを失わず切替)
  • アンチパターン7選と対策
  • 2026年のベストプラクティス(--mount構文/Compose Watch/VirtioFS)
  • tmpfs/anonymous volumeとの使い分け
スポンサーリンク

30秒で決着:どちらを使うかの判定表

先に結論。編集したいものはbind mount、永続化したいものはvolume。

マウント対象 推奨 理由
アプリのソースコード bind mount ホストで編集&即反映、Git管理
設定ファイル(nginx.conf/php.ini) bind mount(:ro Git管理&read-onlyで堅牢
DB(MySQL/PostgreSQL)データ volume 高速・整合性・I/O安定
ユーザーアップロード volume(本番)/bind mount(開発) 本番は永続化、開発は確認しやすく
node_modules/vendor anonymous volume ホストfsを汚さず、macOS/Winで高速
ログ(永続保持) volume 本番はログドライバ or volume
ログ(開発中の確認用) bind mount ホストからtail -fできる
テスト用fixtureデータ bind mount(:ro Git管理+read-onlyで変更防止
秘密鍵・証明書 secrets(volumeでもbindでもない) Compose secretsが最適
一時ファイル・セッション tmpfs メモリ上で超高速・再起動で消滅

3秒判定ルール:①「ホストから編集したい?」→YESならbind mount、②「コンテナ削除後も残したい?」→YESならvolume、③「複数コンテナで共有したい?」→volumeまたはNFS volume、④「再起動で消えていい?」→tmpfs、⑤「機密情報?」→secrets。

bind mount と volume の本質的な違い

両者の違いは単なる書き方ではなく、「誰が管理し、どこに保存されるか」というストレージオーナーシップの違いです。

本質 bind mount volume
管理者 ユーザー(ホストOS) Dockerエンジン
保存場所 ホストの任意ディレクトリ /var/lib/docker/volumes/
作成方法 mount時にホストパス指定 docker volume create or 自動
ホストOS依存 強い(Windows/macOSで性能劣化) 少ない(抽象化されている)
ライフサイクル ホストOSの管理に依存(手動管理) Dockerの管理(docker volumeコマンド)
記法の違い(–mount構文推奨)
# bind mount:ホストのパスを明示
docker run --mount type=bind,source="$(pwd)/app",target=/app ...
# 旧記法:/で始まる or ./で始まる → bind扱い
docker run -v "$(pwd)/app":/app ...

# volume:Dockerが管理、名前指定
docker run --mount type=volume,source=db_data,target=/var/lib/mysql ...
# 旧記法:名前だけなら volume扱い
docker run -v db_data:/var/lib/mysql ...

--mount-vの違い

  • -vは短いがpath先頭で判別./dataはbind、dataはvolume)
  • --mounttype明示(bind/volume/tmpfsを明確に)
  • 2026年は--mount推奨:事故防止+オプション柔軟性
  • ただしCompose YAMLでは短縮記法("./conf:/etc/conf:ro")もOK(type:の長形式もあり)

10軸での完全比較

比較軸 bind mount volume
①性能(Linux) ◎ ネイティブFS並み ◎ ネイティブFS並み
②性能(macOS) △ 遅い(VirtioFSで改善) ◎ ほぼネイティブ
③性能(Windows) △ WSL2外だと遅い ◎ WSL2内で高速
④ホスト編集 ◎ エディタで直接 docker run経由で要一手間
⑤移植性 △ ホストパス依存 ◎ Docker抽象化で同一挙動
⑥バックアップ ◎ ホストの任意ツールで ◯ tar+helperコンテナ
⑦UID/GID問題 ✗ 頻発(ホストと衝突) ◯ Docker管理で低頻度
⑧ホスト汚染 node_modules等がホストに残る ◎ ホストは汚れない
⑨セキュリティ △ ホストのセンシティブなパスに注意 ◎ Docker管理範囲に閉じる
⑩CI/CD連携 ✗ ホスト依存で再現性低 ◎ ビルド成果物として扱える

“ホスト汚染”は軽視できない問題:bind mountで./app:/var/www/htmlとすると、コンテナ内でインストールされたnode_modules(場合によっては数万ファイル)がホストの./app/node_modulesとして残り、Gitで追跡対象になる・エディタのファイル検索が遅くなる・OS間の互換性問題が起きるなどの弊害があります。対策:anonymous volumeでnode_modulesだけ上書き(後述)。

実測ベンチマーク:OSごとのI/O性能

開発PCで体感する最大の違いはこれ。ファイル大量I/Oが必要な作業(ホットリロード/Composer install/npm ci/PHPのOPcache/Railsのeager load)で顕著に差が出ます。

操作 Linux / ネイティブ macOS bind macOS volume
npm install(1万ファイル) 30秒 3〜5分(10倍遅い) 40秒(ほぼ同等)
Rails eager_load 5秒 30秒〜1分 6秒
Composer install 20秒 3〜5分 25秒
HMR(Vite/Webpack) 即時 2〜5秒遅延 即時(Watch推奨)

macOS/Windowsで遅い理由

bind mountはホストOSとVM(HyperKit/WSL2)間でファイル変更を同期するため、9Pプロトコル/Samba/Plan9FSなどのネットワーク的レイヤーを通過します。2023年以降、Docker DesktopにVirtioFS(macOS)、WSL2ファイルシステム直接利用(Windows)が実装され大幅改善しましたが、それでもvolumeの方が高速です。

macOS/Windowsの対策4種

高速化対策
# ① VirtioFS有効化(macOS Docker Desktop)
#   Settings → General → "Use Virtualization framework" をON
#   File sharing → "VirtioFS" を選択(4.30以降がデフォルト)

# ② WSL2ファイルシステム利用(Windows)
#   プロジェクトを C:\Users\... ではなく \\wsl$\Ubuntu\home\... に置く

# ③ 遅いディレクトリをanonymous volumeで上書き
services:
  app:
    volumes:
      - ./app:/var/www/html              # bind(ソース)
      - /var/www/html/node_modules        # anonymous volume(上書き)
      - /var/www/html/vendor

# ④ :delegated / :cached を付与(レガシー環境向け)
volumes:
  - ./app:/var/www/html:delegated

決定フレームワーク:5問で正解が決まる

Yes/No 5連続フロー
Q1: コンテナ内で生成されるデータ?
    (DB・uploads・ログ・キャッシュなど)
    YES → Q2
    NO  → Q3

Q2: 永続化が必要?
    YES → 【named volume】一択
    NO  → 【tmpfs】(メモリ上の高速一時)

Q3: ホストから頻繁に編集する?
    (ソース・設定・テストデータ)
    YES → 【bind mount】
    NO  → Q4

Q4: コンテナ間で共有する?
    YES → 【named volume】(共有に向く)
    NO  → Q5

Q5: ホスト特有のファイルを参照?
    (/etc/hostsや~/.aws等)
    YES → 【bind mount :ro】
    NO  → 【named volume】がデフォルト推奨

迷った時の黄金律:ホストでGit管理したいなら bind mountDB・生成データなら volume」——これだけ覚えておけば9割解決。残り1割は節約(tmpfs)/機密(secrets)/共有(NFS volume)で対応。

シナリオ20パターン別の正解

開発シナリオ(dev環境)

シナリオ 正解
①Laravelのアプリソース bind mount ./app:/var/www/html
②nginx.conf bind mount ./nginx.conf:/etc/nginx/nginx.conf:ro
③node_modules/vendor anonymous volume - /var/www/html/node_modules
④MySQLのデータ named volume db_data:/var/lib/mysql
⑤WordPressのwp-content bind mount ./wp-content:/var/www/html/wp-content
⑥WordPressのwp-admin/wp-includes マウントしない(image内のまま)
⑦テスト用fixture JSON bind mount read-only
⑧Viteビルド出力 bind mount(ホストでnpm run build→コンテナへ)
⑨Redis dumpデータ named volume(開発ならanonymousでも可)
⑩SSL証明書(mkcert) bind mount ./certs:/etc/nginx/certs:ro

本番/CIシナリオ

シナリオ 正解
⑪本番DB named volume(NFS/EBS driver)
⑫本番アプリ成果物 imageに焼き込み(マウントしない)
⑬本番ログ logging driver(awslogs/fluentd)か named volume
⑭本番ユーザーアップロード named volume(S3 mountやNFS driver)
⑮DB設定ファイル(本番) Configs(Swarm)/imageに含める
⑯APIキー・証明書 secrets(volumeでもbindでもない)
⑰CIのテストデータ GitHub Actions workspaceからbind mount
⑱CIビルドキャッシュ named volume or BuildKit cache mount
⑲Xdebug接続 bind mount(ソース同期、pathMappings用)
⑳Docker Socket(docker-in-docker) bind mount /var/run/docker.sock:/var/run/docker.sock(要注意)

⑳の補足:/var/run/docker.sockをbind mountすると、そのコンテナはホストのDocker全体を操作可能(=root相当)。開発ツール(Portainer等)では必要ですが、悪意あるコンテナを走らせるとホスト侵害に直結。最小権限原則で本当に必要か確認し、イメージの信頼性を厳格にチェックしてください。

混在運用の実戦パターン

実案件ではbind mount と volume を1つのcompose.ymlで混在させるのが普通です。各サービスで何をどうマウントするか、代表的な3パターンを示します。

パターン①:Laravel(PHP-FPM + Nginx + MySQL)

Laravel開発用compose.yml
services:
  web:
    image: nginx:1.27-alpine
    ports: ["8080:80"]
    volumes:
      - ./public:/var/www/html/public:ro    # bind: 公開アセット
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro   # bind: 設定

  app:
    build: ./docker/php
    volumes:
      - ./:/var/www/html                     # bind: ソース全体
      - /var/www/html/vendor                 # anonymous: vendor隔離
      - /var/www/html/node_modules           # anonymous: 依存隔離
      - laravel_storage:/var/www/html/storage/app   # named: 永続

  db:
    image: mariadb:11
    volumes:
      - db_data:/var/lib/mysql               # named: DB永続
      - ./docker/mysql/my.cnf:/etc/mysql/conf.d/custom.cnf:ro  # bind: 設定

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data                     # named

volumes:
  db_data:
  redis_data:
  laravel_storage:

パターン②:Node.js/Next.js開発

Next.js開発用compose.yml
services:
  web:
    image: node:20-alpine
    working_dir: /app
    command: npm run dev
    ports: ["3000:3000"]
    volumes:
      - .:/app                               # bind: ソース
      - /app/node_modules                    # anonymous: node_modules隔離(超重要)
      - /app/.next                           # anonymous: ビルドキャッシュ
    environment:
      - WATCHPACK_POLLING=true               # macOS/Winでfile watch対策

  postgres:
    image: postgres:16-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data   # named
      - ./docker/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql:ro   # bind: 初期化

volumes:
  postgres_data:

パターン③:WordPress(開発)

WordPress混在例
services:
  wordpress:
    image: wordpress:6.7-php8.3-apache
    ports: ["8080:80"]
    volumes:
      # WP本体(wp-admin/wp-includes)はマウントしない
      # 編集対象だけbind
      - ./wp-content/themes:/var/www/html/wp-content/themes        # bind: テーマ開発
      - ./wp-content/plugins:/var/www/html/wp-content/plugins      # bind: プラグイン開発
      - wp_uploads:/var/www/html/wp-content/uploads                 # named: アップロード
      - ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini:ro     # bind: PHP設定

  db:
    image: mariadb:11
    volumes:
      - db_data:/var/lib/mysql                # named

volumes:
  db_data:
  wp_uploads:

混在運用のコツ:①ソース=bind(Git管理)、②DB=named volume(永続化)、③node_modules/vendor=anonymous volume(ホスト汚染防止)、④設定ファイル=bind read-only、⑤uploads=開発bind/本番named volume。この5原則で迷うシーンが激減します。

bind mount → volume 移行手順

「とりあえずbind mountで始めたDBが本番移行で困る」はよくあるパターン。データ損失なくvolumeへ移す手順を示します。

移行フロー
# 前提:./db-data:/var/lib/mysql で運用していた

# ① 新規volumeを作成
docker volume create db_data

# ② 既存データをvolumeへコピー(コンテナ停止必須)
docker compose stop db

docker run --rm \
  -v "$(pwd)/db-data":/from:ro \
  -v db_data:/to \
  alpine sh -c "cd /from && cp -a . /to/"

# ③ compose.yml を書き換え
# 旧: - ./db-data:/var/lib/mysql
# 新: - db_data:/var/lib/mysql

# ④ volumes top-level に追加
# volumes:
#   db_data:

# ⑤ 起動して動作確認
docker compose up -d db
docker compose exec db mariadb -u root -proot -e "SHOW DATABASES"

# ⑥ 古いbind mount dirをバックアップ+削除
tar czf db-data-backup.tar.gz db-data/
rm -rf db-data/

逆パターン:volume → bind mount移行

inspection + tar
# ① volume内容を取り出し
docker run --rm \
  -v db_data:/from:ro \
  -v "$(pwd):/to" \
  alpine tar czf /to/db_data.tar.gz -C /from .

# ② 展開
mkdir -p ./db-data
tar xzf db_data.tar.gz -C ./db-data

# ③ compose.yml変更→起動

移行時の落とし穴:①DBは必ずstopしてからコピー(稼働中のInnoDBコピーは高確率で破損)、②ファイル所有者(UID/GID)を保持するためcp -a使用、③バックアップを取ってから削除、④移行後にSELECT COUNT(*)等で件数検証。

アンチパターン7選

①本番DBにbind mount。ホストOSのファイルシステム依存で性能劣化・可搬性ゼロ・InnoDBのデータが暗黙に破損するリスク。本番は必ずnamed volume(NFS driverまたはクラウドCSI)。

./app:/var/www/htmlだけマウントしてnode_modules/vendorを放置。ホスト側にコンテナで生成された大量ファイルが残り、Git追跡・ファイル検索・OS間の互換性で問題多発。対策:- /var/www/html/node_modulesでanonymous volumeを被せる。

③設定ファイルをread-write でbind mount。コンテナ内プロセスが誤って設定を書き換えたらホストの設定も壊れる。必ず:ro(read-only)を付ける。

④秘密鍵をbind mountで渡す./secret.pem:/app/secret.pemはコンテナが壊れると鍵も読める恐れ。secrets機構を使う(/run/secrets/にマウントされプロセス環境変数にも載らない)。

⑤匿名volumeで本番DBを運用。イメージのVOLUME /var/lib/mysql宣言で自動作成されるとハッシュ名で管理困難+pruneで誤削除される。必ず明示的named volumeで指定する。

⑥絶対パスのbind mountをチームで共有/home/alice/project:/appはアリスの環境でしか動かない。相対パス./app:/appまたは${APP_PATH}:/appでenvから注入。

⑦1コンテナに多数のbind mount。各bind mountがpath watching/inode監視を個別に行うためホストのCPU負荷が増大。複数ディレクトリをまとめて親ディレクトリ1つで./app:/var/www/htmlに集約する方が軽量。

よくあるトラブルと対処

①マウント後、中身が空

原因はマウント対象と既存データの関係性。bind mount:ホスト側が空ならコンテナも空(ホスト優先)。named volume初回マウント時のみコンテナ内の既存データがvolumeにコピーされる。2回目以降は「空のvolume」が優先されて空になる。→ docker volume rmで一度消して再作成するか、nocopy: trueオプションを外す。

②Permission denied

UID/GID問題の典型対処
# コンテナ内のプロセスUIDを確認
docker compose exec app id
# uid=33(www-data)

# ホスト側ディレクトリの所有者変更
sudo chown -R 33:33 ./wp-content

# または開発環境のみ全権限
chmod -R 777 ./wp-content    # 本番NG

# SELinuxならラベル付与
docker run -v ./app:/app:z ...

③node_modulesがホストに大量ファイル

anonymous volumeで上書き
services:
  web:
    volumes:
      - .:/app
      - /app/node_modules    # ← これが重要

# コンテナ内で npm install するとvolume側に入り、
# ホスト側の./node_modulesは空のまま

④HMR(ホットリロード)が効かない

macOS/Windowsのbind mountはinotify通知が届きにくい。対策:①WATCHPACK_POLLING=true(Webpack/Next.js)、②CHOKIDAR_USEPOLLING=true(Vite/Vue)、③Docker Compose Watchに切替(2024年新機能)。

⑤Windows bind mountでsymlinkエラー

Windows bind mountはsymlink制限があり、node_modulesやpythonのvenvでエラーが出やすい。プロジェクトをWSL2内部(\\wsl$\Ubuntu\home\...)に置くのが抜本解。またはanonymous volumeでホストfsを経由しない構成に。

よくある質問

Qbind mountとvolume、どちらが速い?
ALinuxではどちらも同じ(ネイティブFS)。macOS/Windowsではvolumeが圧倒的に速い(bind mountはVM経由で10倍遅いことも)。ただしVirtioFS(macOS)やWSL2内部(Windows)を使えばbind mountもかなり高速化されました。本記事「実測ベンチマーク」セクション参照。
Qdocker compose でどちらか判別する書き方は?
A短縮記法ならpath先頭で判別./data:/path./開始)→bind、data:/path(名前だけ)→named volume。曖昧さを避けるには長形式(type: bindtype: volume)を使うのが安全。
Qanonymous volumeは消えないの?
Adocker rm時に-vオプション付きだと匿名volumeも消えますが、デフォルトでは残ります。定期的にdocker volume ls -qf "dangling=true"で孤児確認+docker volume pruneで整理してください。詳しくは【Docker】volume完全ガイド参照。
Q本番でbind mount使うのはアリ?
A設定ファイルのbind mount(read-only)は本番でもアリ。ただしデータ本体のbind mount は非推奨。ホストパス依存で可搬性が低く、NFS/EBS等のストレージ統合も利かないため、named volume+driver_opts(NFS/cifs/EBS)が本番の標準です。
Qsecretsはvolumeなの?bind mountなの?
Aどちらでもない、独立した仕組みです。secretsは/run/secrets/<name>tmpfs(メモリ上)でマウントされ、ディスクに書かれずプロセス環境変数にも載らない。機密情報はbind mountで渡さずsecrets機能を使いましょう。
QGitHub ActionsのCI内でbind mountは使える?
A使えます。${{ github.workspace }}(ワークフロー実行ディレクトリ)をbind mountするのがCI定番パターン。ただしキャッシュ戦略(BuildKit cache mount)や成果物(artifacts)と使い分けると高速化できます。詳しくは【GitHub】Actions完全ガイド参照。
Qシンボリックリンク(symlink)はbind mountで扱える?
Aホストのsymlinkがbind mount範囲外を指すと参照不可(セキュリティ機構)。Windows bind mountはsymlink制限が強く、Pythonのvenv/Node.jsのnpm linkで問題多発。対策:WSL2内部にプロジェクトを置く、anonymous volumeで包む、等。
Qbind mountした先で作られたファイルの所有者がroot
Aコンテナ内でrootで実行されるプロセスが書き込むとroot所有になり、ホスト側から編集できない事故が頻発します。対策:user: "1000:1000"(Compose)や--user "$(id -u):$(id -g)"、LinuxServer.io流のPUIDPGID環境変数対応イメージ利用。
Qvolumeのバックアップはbind mountより面倒?
Aやや手間ですが定石があります:docker run --rm -v vol_name:/data:ro -v "$(pwd):/backup" alpine tar czf /backup/vol.tar.gz -C /data .で汎用tarバックアップ。DBならアプリレベルダンプ(mysqldumppg_dump)の方が整合性的に安全。詳しくは【Docker】volume完全ガイドの「backup/restore 7パターン」参照。
QCompose Watchを使えばbind mountは要らない?
Aほぼ要らなくなります。Compose Watchはファイル変更イベントでコンテナ内へ同期/再ビルドするため、bind mountの常時マウントが不要。bind mount性能問題(macOS/Win遅延)を解決する決定打です。詳しくは【Docker】Compose完全ガイドのWatchセクション参照。

関連記事

まとめ

  • 本質の違い:bind mount=ホスト管理/volume=Docker管理
  • 3秒判定:ホスト編集=bind、永続化=volume、一時=tmpfs、機密=secrets
  • 10軸比較:volumeは移植性/性能(macOS/Win)/CI再現性で優位
  • macOS/Windowsのbind mountは遅い(VirtioFS/WSL2内部で改善)
  • node_modules/vendorはanonymous volumeで上書きしてホスト汚染防止
  • 設定ファイルのbind mountは必ず:ro(read-only)
  • 混在運用:ソース=bind、DB=named volume、依存=anonymousの5原則
  • アンチパターン:本番bind mount/匿名volume放置/秘密鍵bind/絶対パス共有
  • 2026年推奨は--mount type=...構文(意図明示)+Compose Watchで動的同期
  • bind→volume移行はdocker run --rm -v src:from -v dst:to alpine cp -aパターン

bind mount と volume は対立する選択肢ではなく、役割分担した2つのツールです。本記事の判定フレームと20シナリオ別正解をベースに、チームのcompose.ymlテンプレートを整備すれば、新規プロジェクトでも即座に性能と安全性を両立した構成が手に入ります。より深いvolume運用はvolume完全ガイド、Compose全般はCompose完全ガイド、実案件の混在例はWordPressNginx+PHP-FPMと合わせてご参照ください。