
ただ、実際に必要になるのは構造の暗記ではない。開発環境をどこまで分離したいのか、手元のPCにどれだけ余裕があるのか、データをどう残すのか、運用や商用利用の条件をどう考えるのか。判断を迫られる場面では、こうした現実的な条件のほうが重要になる。
Dockerと仮想マシンは、どちらが優れているかで決めるものではない。プロセス単位の隔離で足りるならDockerが向いているし、OSやカーネルまで分離しなければならないなら仮想マシンが候補になる。本稿では、両者の構造差を起点に、起動速度、性能、メモリ、データ永続化、商用利用、イメージ設計まで、個人開発で迷いやすい判断材料を整理する。
カーネル共有とプロセス隔離——構造から理解する「コンテナ」と「仮想マシン」の差
Dockerと仮想マシンの最も深い違いは、ホストOSのカーネルを共有するか、ゲストOSを丸ごと持つかという点にある。
Dockerのコンテナは、ホスト側のカーネル上でプロセスを隔離して動かす。Linuxでは、名前空間、cgroups、ケイパビリティなどの仕組みを組み合わせることで、プロセスから見えるファイルシステム、プロセス一覧、ネットワーク、利用できるCPUやメモリを分離する。コンテナの中から見ると独立した環境のように見えるが、カーネルそのものがコンテナごとに用意されているわけではない。
一方、仮想マシンはハイパーバイザーの上でゲストOSを起動する。ゲストOSは独自のカーネルを持ち、仮想CPU、仮想メモリ、仮想ディスク、仮想ネットワークなどを通して動作する。ホストOSとは異なるOSを動かせること、カーネルレベルで環境を分離しやすいことが、仮想マシンの大きな特徴だ。
この差は、単なる仕組みの違いでは終わらない。コンテナはゲストOSを起動しないため、一般に起動が速く、必要なリソースも少ない。アプリケーションやミドルウェアを複数立ち上げて開発する場合でも、環境を作り直す心理的な負担が小さい。
仮想マシンはOSをブートするコストを抱える。その代わり、ホスト側から独立したOS環境を持ちやすい。異なるカーネルを必要とするソフトウェア、レガシーなOS、特殊なドライバー、厳格なテナント分離など、コンテナでは扱いにくい要件に対応できる。
コンテナが隔離しているのは、カーネル空間そのものではない。主にプロセスの見え方、利用できるリソース、ネットワークやファイルシステムの名前空間である。通常、コンテナ内部からホストのカーネルモジュールを自由に操作することはできないが、ホストカーネルの脆弱性がコンテナによって消えるわけでもない。
そのため、複数の利用者が同じホスト上で動作するマルチテナント環境では、Dockerだけで十分な分離を実現できるかを慎重に検討する必要がある。信頼できるアプリケーションを自分の開発PCや単一サービスのサーバーで動かす場合と、互いに信頼できない利用者の処理を同じ基盤で実行する場合では、求められる境界線が違う。
コンテナは隔離されたプロセスであり、仮想化されたOSではない。この違いを理解すると、Dockerに任せてよい仕事と、仮想マシンに任せるべき仕事が見えやすくなる。
ここで重要なのは、コンテナと仮想マシンを対立させないことだ。実際のクラウド環境では、仮想マシンの上でコンテナを動かす構成も一般的である。仮想マシンでホスト全体の境界を作り、その上でコンテナによってアプリケーションを分離する。両者は置き換え関係になる場合もあるが、役割を分担させることもできる。
起動時間と性能ロス——数字だけで判断しないための見方
構造差を理解したら、次に気になるのは起動時間と性能だ。Dockerは軽量で、仮想マシンは重い。この説明は大筋では間違っていない。ただし、環境を無視して数字だけを並べると、実際の開発体験とはずれやすい。
コンテナは、すでに存在するイメージからプロセスを起動する。ゲストOSのブートを待たなくてよいため、単純なWebサーバーやデータベースであれば、起動処理はかなり短くできる。開発中に設定を変更してコンテナを作り直す場合でも、OS全体を再起動する必要はない。
仮想マシンでは、仮想ハードウェアの初期化からゲストOSの起動、サービスの立ち上げまでが必要になる。OSの起動時間は、ディスク性能、割り当てたCPUやメモリ、ゲストOSのサービス数によって変わる。単純に数分かかることもあれば、適切に調整すればそれより短くできる。ここで大事なのは、Dockerの起動時間を常に数秒、仮想マシンを常に数分と固定して考えないことだ。
性能についても同じである。Linux上でLinuxコンテナを動かす場合、アプリケーションの処理はホストカーネルを使うため、CPU処理のオーバーヘッドは比較的小さい。PHPやNode.jsなどのアプリケーションをコンテナで動かしても、処理内容によってはホスト上で直接動かす場合との差を感じにくい。
一方、仮想マシンでは、CPU、メモリ、ストレージ、ネットワークを仮想化する層が入る。最近の仮想化支援機能によって差は小さくなっているが、I/Oが多い処理では影響が出やすい。データベースの読み書き、大量の小さなファイルを扱う処理、ファイル共有を介した開発環境などは、CPU性能だけでは判断できない。
特にmacOSやWindowsでDocker Desktopを使う場合、Linuxコンテナを動かすための軽量なLinux仮想環境が背後に動いている。見た目はコンテナでも、完全にホストOS上で直接プロセスを動かしているわけではない。ファイル共有の方式やマウントする場所によって、ソースコードの監視や依存ファイルの読み込みが遅くなることもある。
比較するときは、次のような項目を分けて考えるとよい。
| 比較項目 | Dockerコンテナ | 仮想マシン |
|---|---|---|
| 起動の考え方 | コンテナ内のプロセスを起動する | ゲストOSを起動してからサービスを立ち上げる |
| CPU処理 | ホストカーネルを使うため比較的軽い | 仮想化の層を通る |
| メモリ | プロセスと必要なサービスが中心 | ゲストOSの常駐分も必要 |
| OSの自由度 | 基本的にホストカーネルの制約を受ける | 異なるOSやカーネルを動かしやすい |
| 作り直し | イメージから再作成しやすい | OSイメージやディスクの管理が必要 |
| 分離の単位 | プロセスやサービス | OSや仮想ハードウェア |
「Dockerなら必ず速い」と考えるより、起動、CPU、ディスクI/O、ファイル共有のどこがボトルネックになっているかを切り分けるほうが実践的だ。Dockerのコンテナ起動は速くても、イメージのビルドに時間がかかることはある。仮想マシンの起動が速くても、ゲストOS内のデータベース設定が重ければ開発体験はよくならない。
CIでのテストやデプロイでも、差が積み重なる。毎回大きなイメージをビルドし、依存関係を最初から取得し、不要なサービスまで起動していれば、コンテナを使っていても処理は遅くなる。キャッシュを使い、イメージを分割し、実行に必要なサービスだけを起動する。こうした設計のほうが、方式そのものより結果に影響することも多い。
メモリと開発PC——個人開発者の現実的なリソース制約
個人開発でDockerを使うとき、最初にぶつかりやすいのがメモリ不足だ。コンテナ単体は軽く見えるが、実際のWeb開発では一つのコンテナだけを動かすことは少ない。
Laravelであれば、Webサーバー、PHP実行環境、データベース、キャッシュサーバー、メール確認用のサービスなどを組み合わせることがある。WordPressでも、Webサーバー、PHP、データベースを同時に動かす。さらにブラウザ、エディター、画像編集ツール、データベースクライアントなどがホスト側で動く。コンテナの使用量だけを見て、PC全体の余裕を判断することはできない。
目安として、メモリ8GBのPCでも単体のコンテナや小規模な構成なら動かせる。ただし、複数サービスを常用しながらブラウザで確認し、別のアプリケーションも並行して使うと、余裕がなくなりやすい。メモリ16GB以上あれば、開発環境を止めたりサービスを選び直したりする場面が減る。大量の依存関係をビルドする、複数のプロジェクトを同時に開く、仮想マシンも併用する、といった使い方なら、さらに余裕が欲しくなる。
Docker Desktopを使う場合は、Docker側に割り当てられたCPUやメモリの上限も確認したい。ホストPCに十分なメモリがあっても、Docker Desktop側の上限が低ければ、コンテナ群の合計がその範囲に収まるよう制限される。ビルド中だけ急に遅くなったり、複数のサービスを起動したときだけ応答が悪くなったりする場合は、設定上限と実際の使用量を確認すると原因を特定しやすい。
ただし、上限を無制限に近づければよいわけではない。Dockerに多く割り当てすぎると、ホストOS、ブラウザ、エディターが使えるメモリが減る。開発PC全体の作業を止めない範囲で、コンテナ側に必要な分を割り当てるのが基本になる。
メモリが不足したとき、すぐにPCの買い替えを考える前に、次のような整理ができる。
- 常時起動する必要のないサービスを、必要なときだけ立ち上げる
- データベースやキャッシュを一つの開発環境で共有し、プロジェクトごとの重複を減らす
- ビルド用のサービスと実行用のサービスを分け、不要なツールを常駐させない
- コンテナごとにメモリ上限を設定し、特定のサービスが全体を圧迫しないようにする
- 使い終わったコンテナやイメージ、ボリュームを定期的に整理する
メモリ不足をスワップだけで解決しようとするのも危険だ。スワップによって処理を継続できる場合はあるが、ストレージへの退避が増えれば、ビルドやファイルアクセスの待ち時間が目立つ。動作しているように見えても、開発サイクル全体が遅くなっていることがある。
本番環境では、さらに明示的なリソース管理が必要になる。コンテナにメモリ制限を設けていなければ、一つのプロセスの異常でホスト全体が圧迫される可能性がある。メモリ制限を設ける場合も、単に小さな値を指定するのではなく、通常時の使用量、起動時の一時的な増加、負荷が高い時間帯を見て決めなければならない。
データが消える罠と商用利用の注意点
コンテナはステートレスであるという落とし穴
Docker初心者が一度はつまずきやすいのが、コンテナの削除とデータの消失だ。
コンテナの書き込み層に保存したデータは、コンテナが存在している間は通常保持される。単にプロセスを停止してから同じコンテナを再起動しただけで、データベースの内容が消えるわけではない。
問題になるのは、コンテナを削除して再作成した場合や、永続ボリュームを使わずにデータをコンテナ内部へ保存していた場合だ。開発中にデータベースへテストデータを入れたのに、コンテナを削除・再作成したら全部消えた。このケースは、コンテナ再起動による消失ではなく、コンテナの書き込み層にだけデータを置き、永続ボリュームを割り当てていなかったことが原因になる。
Dockerのコンテナは、壊れたら捨ててイメージから作り直す運用と相性がよい。設定やアプリケーションの実体をイメージや設定ファイルに寄せ、実行中に変化するデータをコンテナの内部だけに残さない。これがステートレスな構成の基本だ。
データベースの中身、アップロードされた画像、ユーザーが作成したファイルなどは、コンテナの寿命から切り離して保存する必要がある。そのために使うのがボリュームである。ボリュームをコンテナへマウントすれば、コンテナを作り直しても、ボリューム側に保存されたデータを引き継げる。
ただし、ボリュームを使えば自動的に安全になるわけではない。ボリュームを誤って削除すればデータは失われるし、ホスト障害やディスク障害への備えにもならない。データの永続化とバックアップは別の問題として考える必要がある。
ボリュームには、大きく分けてバインドマウントと名前付きボリュームがある。
- バインドマウントは、ホスト側の特定ディレクトリをコンテナへ見せる方式。ソースコードをエディターで編集し、その変更をコンテナ内のWebサーバーへ反映したい場合に向いている
- 名前付きボリュームは、Dockerが管理する領域を使う方式。データベースのデータディレクトリのように、ホスト側で直接編集する必要がないデータに向いている
ソースコードはバインドマウント、データベースのデータは名前付きボリューム。この組み合わせは、個人開発でも扱いやすい。ただし、OSやファイル共有の方式によってはバインドマウントの性能が落ちることがある。特に大量の小さなファイルを扱う依存ディレクトリをそのまま共有すると、ファイル監視や読み込みが遅くなる場合がある。
Laravelの開発環境であれば、アプリケーションのソースコードは編集しやすい形で共有し、依存関係やキャッシュの扱いは開発環境に合わせて分ける方法がある。WordPressでは、テーマやプラグインを編集するディレクトリと、データベースやアップロードファイルの保存領域を同じ考え方で分離すると、コンテナを作り直すときの事故が減る。
データが守られるのは、コンテナが壊れにくいからではない。コンテナの外側に永続ボリュームを用意し、さらにバックアップを取っているからだ。
マイグレーションも同じくらい重要になる。データベースを手動で変更していると、環境を再作成したときに同じ状態を再現できない。Laravelのマイグレーションのように、スキーマ変更を履歴として管理できる仕組みを使えば、データベースの構造をコードと一緒に扱える。テストデータについても、必要なものはシーダーや初期化処理として再現できるようにしておくと、削除・再作成を恐れずに済む。
Docker Desktopの商用利用
個人開発や学習用途では見落としやすいが、Docker Desktopには利用条件がある。一定規模以上の組織で商用利用する場合は、有料サブスクリプションが必要になる条件が定められている。従業員数や年間売上高によって扱いが変わるため、個人開発の延長で法人案件や受託開発へ移るときは確認しておきたい。
ここで注意したいのは、Dockerのコンテナ技術そのものと、Docker Desktopという配布・管理用アプリケーションを分けて考えることだ。サーバー上でコンテナを動かすことと、開発PCでDocker Desktopを使うことは同じではない。商用利用の条件も、利用する製品や契約、組織の規模によって確認すべき点が変わる。
代替手段として、Rancher Desktopなど別のコンテナ実行環境を検討することもできる。コマンドライン中心の環境を構築する方法もある。ただし、単に別のソフトウェアをインストールすれば終わりではない。コンテナランタイム、仮想化方式、ネットワーク、ボリュームの扱いが変わる可能性があるため、チーム内で手順を揃える必要がある。
受託開発では、開発環境に必要なソフトウェアのライセンスを誰が負担するのかを、契約や社内ルールで曖昧にしないほうがよい。Docker Desktopを使う前提であれば、その利用条件と費用の扱いを確認しておく。後からライセンス費用の話が出ると、技術ではなく契約の問題で手戻りが発生する。
マルチステージビルドで攻めと守りを両立する
Dockerfileを書くときに、開発環境で動いたものをそのまま本番へ持ち込む設計になりやすい。コンパイラー、テストツール、パッケージマネージャー、デバッグ用のユーティリティまで入ったイメージは、作るのは簡単だが、実行環境としては余分なものが多い。
ここで使えるのがマルチステージビルドだ。ビルド用のステージと、実際にアプリケーションを動かすステージを分ける。ビルド用ステージで依存関係の解決やアセット生成を行い、最終ステージには実行に必要なファイルだけをコピーする。
この方法には、主に二つの利点がある。
イメージサイズを抑えられる
ビルド時にしか使わないコンパイラーや開発用ライブラリを、最終イメージに含めずに済む。最終イメージが小さくなれば、レジストリからの取得、サーバーへの転送、デプロイ時の展開にかかる時間を抑えやすい。
Laravelであれば、依存関係を解決するための処理と、実行時に必要なPHP拡張やアプリケーション本体を分けられる。フロントエンドのアセットをビルドする場合も、Node.js環境で生成した公開用ファイルだけを実行用イメージへ渡せる。
イメージを小さくすることは、単に容量を減らすだけではない。変更されていないレイヤーをキャッシュしやすくなり、CIで毎回すべての処理をやり直す必要がなくなる。依存関係のファイルを先に配置し、アプリケーション本体のコピーを後にするなど、Dockerfileの順序を調整するだけでもキャッシュの効き方は変わる。
攻撃対象領域を狭められる
最終イメージにビルドツールやテスト用のコマンドを残さなければ、実行環境に存在するプログラムの数を減らせる。存在するパッケージが減れば、脆弱性の確認対象も絞りやすくなる。
ただし、マルチステージビルドを使えば脆弱性がなくなるわけではない。実行に必要なライブラリに脆弱性が残ることはあるし、ベースイメージの更新も必要だ。脆弱性スキャナーで最終イメージを確認し、重要度や実際の利用状況を見ながら対応する。検出数を減らすためだけにパッケージを隠したり、スキャン対象から除外したりするのは、対策ではない。
Laravel、PHP-FPM、Nginxを組み合わせる構成では、依存関係を準備するステージ、フロントエンドのアセットを生成するステージ、アプリケーションを実行するステージに分ける考え方が使える。NginxとPHP-FPMを同じコンテナへ詰め込むか、役割ごとに分けるかは、運用の複雑さとのバランスで決めればよい。
開発環境と本番環境を同じイメージにする必要もない。開発時にはデバッグやテストに便利なツールが必要だが、本番では不要な場合がある。一方で、環境差が大きすぎると、開発では動くのに本番で動かない問題が起きる。ベースイメージ、PHPのバージョン、主要な拡張、設定値など、互換性に関わる部分は揃えたうえで、開発用の追加機能だけを分けるのが現実的だ。
Dockerを選ぶべきケース、仮想マシンを選ぶべきケース
ここまでの話を、実際の選定に落とし込む。
Dockerを選びやすいのは、アプリケーションと依存サービスを同じ構成で再現したい場合だ。個人開発でも、Laravel、WordPress、Node.js、データベースをそれぞれ手元へ直接インストールしていくと、プロジェクトごとのバージョン差で環境が崩れやすい。コンテナに役割を分ければ、OSを汚さずに構成を切り替えられる。
次のような条件なら、Dockerの利点が出やすい。
- チームや将来の自分が同じ開発環境を再現できるようにしたい
- 開発者ごとのPHPやデータベースのバージョン差を減らしたい
- CIでテスト環境を自動的に作成したい
- Webサーバー、アプリケーション、データベースを役割ごとに分けたい
- 必要なサービスを短時間で作り直したい
- 同じアプリケーションを複数の環境へ展開したい
- 本番環境へ不要なビルドツールを持ち込みたくない
WordPressのテーマやプラグインを開発する場合も、データベースやPHPのバージョンをプロジェクトごとに固定しやすい。Laravelでは、依存関係やアプリケーションの実行環境をそろえ、初めて参加する人が環境構築で長時間つまずく状況を減らせる。
一方、仮想マシンのほうが適している条件もある。
- ホストOSとは異なるOSやカーネルを動かす必要がある
- カーネルモジュールや特殊なドライバーを利用する
- 信頼関係のない処理を、より強い境界で分離したい
- レガシーなOSや特定のシステム構成を維持する必要がある
- コンテナの制約では扱えないハードウェアやシステムコールに依存する
- OS全体を一つの単位としてスナップショットや移行を管理したい
仮想マシンを選んだ場合でも、その中でDockerを使うことはできる。たとえば、ホストOSから環境を分ける必要がある場合は仮想マシンを用意し、その内部でアプリケーションごとのコンテナを動かす。これにより、OS単位の隔離と、サービス単位の再現性を両立できる。
選択の軸は、最終的には何を隔離したいかである。アプリケーションの依存関係やプロセスを分離したいなら、Dockerが扱いやすい。OSやカーネル、仮想ハードウェアまで分けたいなら、仮想マシンを検討する。セキュリティ要件が厳しい場合は、軽量さだけを理由にコンテナを選ばないほうがよい。
もう一つの判断材料は、環境を再現できるかどうかだ。Dockerでは、イメージ定義、サービス構成、環境変数、ボリューム、初期化手順をまとめて管理できる。完全に自動化できるとは限らないが、何を使って環境を作ったのかを記録しやすい。
仮想マシンでも、イメージや構成管理ツールを使えば再現性を高められる。ただし、OSイメージ、ディスク容量、ネットワーク、起動設定など、管理する対象は増える。個人開発の規模では、その管理コストがアプリケーション開発そのものを上回ることもある。
個人開発で最初に触るなら、Dockerから始めるのは合理的だ。LaravelやWordPressの開発環境を作り、コンテナを停止する、削除する、再作成する。ボリュームを使った場合と使わなかった場合で、何が残り何が消えるのかを確認する。こうした操作を通じて、コンテナをOSの代用品ではなく、再作成可能な実行環境として理解できる。
ただし、Dockerを使えばインフラの問題がなくなるわけではない。メモリ不足、ファイル共有の遅さ、ボリュームの管理、イメージの脆弱性、ライセンス条件など、別の問題が現れる。仮想マシンからDockerへ移行する場合も、単に設定ファイルを置き換えればよいわけではない。データの保存先、ネットワーク、監視、バックアップまで含めて移行する必要がある。
Dockerと仮想マシンの選択に、常に通用する正解はない。カーネル共有とプロセス隔離、起動と性能、メモリ消費、データ永続化、商用利用、イメージの作り方。この順番で条件を確認すれば、流行しているからDocker、昔から使っているから仮想マシン、といった理由だけで決めずに済む。
最も避けたいのは、とりあえず動いた状態を完成形だと思うことだ。コンテナを削除・再作成してもデータを戻せるのか。別のPCやCI環境で同じ構成を作れるのか。本番イメージにビルド用の不要なツールが残っていないか。自分の環境がどの境界で分離され、どこを共有しているのか。
その確認までできて初めて、Dockerは便利な箱ではなく、判断可能なインフラ構成になる。個人開発では、必要な範囲から小さく使い始めればよい。要件がOS単位の隔離を求めるようになったら、仮想マシンを組み合わせる。選ぶべきなのは製品名ではなく、守りたい境界と、将来も維持できる運用である。
Related reading: Docker・インフラ構築をわかりやすく解説 and Docker Composeの内部ネットワークでコンテナ間通信ができる仕組み.