「./data:/var/lib/mysqlとdb_data:/var/lib/mysql、どっちを書けばいいんだっけ?」——Dockerで毎日書くのにbind mountとvolumeの使い分けで迷う人は初心者からベテランまで絶えません。
選択を間違えると、①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。
3秒判定ルール:①「ホストから編集したい?」→YESならbind mount、②「コンテナ削除後も残したい?」→YESならvolume、③「複数コンテナで共有したい?」→volumeまたはNFS volume、④「再起動で消えていい?」→tmpfs、⑤「機密情報?」→secrets。
bind mount と volume の本質的な違い
両者の違いは単なる書き方ではなく、「誰が管理し、どこに保存されるか」というストレージオーナーシップの違いです。
# 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)--mountはtype明示(bind/volume/tmpfsを明確に)- 2026年は
--mount推奨:事故防止+オプション柔軟性 - ただしCompose YAMLでは短縮記法(
"./conf:/etc/conf:ro")もOK(type:の長形式もあり)
10軸での完全比較
“ホスト汚染”は軽視できない問題: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)で顕著に差が出ます。
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問で正解が決まる
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 mount、DB・生成データなら volume」——これだけ覚えておけば9割解決。残り1割は節約(tmpfs)/機密(secrets)/共有(NFS volume)で対応。
シナリオ20パターン別の正解
開発シナリオ(dev環境)
本番/CIシナリオ
⑳の補足:/var/run/docker.sockをbind mountすると、そのコンテナはホストのDocker全体を操作可能(=root相当)。開発ツール(Portainer等)では必要ですが、悪意あるコンテナを走らせるとホスト侵害に直結。最小権限原則で本当に必要か確認し、イメージの信頼性を厳格にチェックしてください。
混在運用の実戦パターン
実案件ではbind mount と volume を1つのcompose.ymlで混在させるのが普通です。各サービスで何をどうマウントするか、代表的な3パターンを示します。
パターン①:Laravel(PHP-FPM + Nginx + MySQL)
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開発
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(開発)
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移行
# ① 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を確認 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がホストに大量ファイル
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を経由しない構成に。
よくある質問
./data:/path(.や/開始)→bind、data:/path(名前だけ)→named volume。曖昧さを避けるには長形式(type: bind/type: volume)を使うのが安全。docker rm時に-vオプション付きだと匿名volumeも消えますが、デフォルトでは残ります。定期的にdocker volume ls -qf "dangling=true"で孤児確認+docker volume pruneで整理してください。詳しくは【Docker】volume完全ガイド参照。/run/secrets/<name>にtmpfs(メモリ上)でマウントされ、ディスクに書かれずプロセス環境変数にも載らない。機密情報はbind mountで渡さずsecrets機能を使いましょう。${{ github.workspace }}(ワークフロー実行ディレクトリ)をbind mountするのがCI定番パターン。ただしキャッシュ戦略(BuildKit cache mount)や成果物(artifacts)と使い分けると高速化できます。詳しくは【GitHub】Actions完全ガイド参照。user: "1000:1000"(Compose)や--user "$(id -u):$(id -g)"、LinuxServer.io流のPUID/PGID環境変数対応イメージ利用。docker run --rm -v vol_name:/data:ro -v "$(pwd):/backup" alpine tar czf /backup/vol.tar.gz -C /data .で汎用tarバックアップ。DBならアプリレベルダンプ(mysqldump/pg_dump)の方が整合性的に安全。詳しくは【Docker】volume完全ガイドの「backup/restore 7パターン」参照。関連記事
- 【Docker】volume完全ガイド — volume深掘り(driver/バックアップ/移行)
- 【Docker】Compose完全ガイド — volumes top-level定義/Compose Watch
- 【Docker】WordPress環境構築完全ガイド — WP特化のbind/volume混在例
- 【Docker】Nginxローカル開発環境構築完全ガイド — nginx.confはbind ro推奨
- 【Docker】Nginx + PHP-FPM完全ガイド — ソース=bind、DB=volumeの実例
- 【Docker】MySQL CLI完全ガイド — DBバックアップの実務パターン
- 【保存版】Docker Desktopが「Docker Desktop stopped」で起動できない時の完全解決ガイド — Docker起動トラブル
- 【GitHub】Actions完全ガイド — CIでのマウント戦略
まとめ
- 本質の違い: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完全ガイド、実案件の混在例はWordPress/Nginx+PHP-FPMと合わせてご参照ください。

