Docker・インフラ構築

ローカル開発環境の構築:Docker ComposeとDevcontainersのどちらを選ぶべきか

「Docker ComposeとDev Containersのどちらを使えばいいのか」と迷うとき、実際には比較する対象を少し取り違えていることが多いです。Docker Composeは複数のコンテナを動かすための仕組みで、Dev Containersはそのコンテナを開発場所として使うための仕組みです。…

ローカル開発環境の構築:Docker ComposeとDevcontainersのどちらを選ぶべきか

ローカル開発環境の構築:Docker ComposeとDev Containersのどちらを選ぶべきか

ローカル開発環境を構築するとき、Docker Composeだけでもアプリケーションは動かせます。LaravelならPHP、Webサーバー、データベース、RedisなどをComposeで起動し、ホスト側のVS Codeでソースコードを編集する構成です。一方、Dev Containersを組み合わせると、VS Codeそのものをコンテナ内の開発環境に接続できます。

この違いを理解しないまま「どちらが高機能か」で選ぼうとすると、設定だけが増えていきます。大切なのは、実行環境を管理したいのか、開発者が使う環境まで揃えたいのかを切り分けることです。

Docker ComposeとDev Containersの役割分担:実行環境と開発体験の統合

Docker Composeは、複数のコンテナをひとつのアプリケーション環境として扱うためのツールです。compose.yamlにサービスを定義しておけば、PHPのアプリケーションコンテナ、MySQLやPostgreSQLなどのデータベース、Redis、メール確認用のサービスといった構成をまとめて起動できます。

サービス間のネットワーク、依存関係、ボリューム、環境変数などを宣言的に管理できる点がComposeの強みです。開発者が個別にコンテナを起動するコマンドを覚えなくても、プロジェクトの構成をある程度再現できます。

ただし、Docker Composeはエディタの使い方までは面倒を見てくれません。ソースコードをどこで編集するか、PHPの拡張機能やComposerをどこに入れるか、VS Codeの言語サーバーをホスト側とコンテナ側のどちらで動かすかは、別途決める必要があります。

Dev Containersは、ここを担当します。VS Codeの拡張機能である「Dev Containers」を使うと、指定したコンテナにVS Codeから接続できます。見た目はいつものVS Codeですが、ターミナルで実行するPHP、Node.js、Composer、npmなどはコンテナ内のものになります。拡張機能もコンテナ内にインストールできるため、開発者のホスト環境に依存しにくくなります。

つまり、役割は次のように分かれます。

項目Docker ComposeDev Containers
主な役割複数コンテナの起動・停止・連携コンテナを開発場所として利用
中心となる設定compose.yaml.devcontainer/devcontainer.json
管理する対象アプリ、DB、キャッシュ、ネットワークなどVS Code、拡張機能、ワークスペースなど
エディタとの関係エディタを指定しないVS Codeと統合して利用
単独利用可能Composeや単一コンテナと組み合わせて利用
向いている場面サービス構成を再現したいとき開発者の作業環境まで統一したいとき

ここで注意したいのは、Dev ContainersがDocker Composeの代替とは限らないということです。複数のサービスを使うアプリケーションでは、Composeを実行環境の土台として残し、その中のひとつをDev Containerとして利用する構成が自然です。

Laravelであれば、backendappといったPHPコンテナにVS Codeを接続し、データベースやRedisはComposeのサービスとして起動します。WordPressでも、WordPress本体を動かすコンテナに接続し、データベースは別サービスとして扱う形にできます。

逆に、単一のCLIツールや小規模なライブラリを開発するだけなら、Dev Containers単独に近い構成でも問題ありません。重要なのは、アプリケーションを動かすサービスと、開発者がコマンドを実行する場所を同じ設計の中で整理することです。

Docker Composeは「何を動かすか」を決め、Dev Containersは「どこで開発するか」を決める。比較するより、役割を分けて考えたほうが構成は早く決まります。

docker composeとDev Containersを組み合わせる方法

Docker ComposeとDev Containersを連携させる中心になるのが、.devcontainer/devcontainer.jsonです。この設定ファイルで、どのComposeファイルを使うか、どのサービスにVS Codeを接続するか、コンテナ内のどのディレクトリをワークスペースとして開くかを指定します。

基本になるプロパティは、dockerComposeFileserviceworkspaceFolderです。

  • dockerComposeFile:利用するComposeファイルを指定する
  • service:VS Codeが接続するサービスを指定する
  • workspaceFolder:コンテナ内でプロジェクトを開くディレクトリを指定する

たとえば、Laravelの構成でPHPコンテナをbackendというサービス名にしている場合、Dev Containersはそのサービスに接続します。データベース用のdbやRedis用のredisに接続するのではありません。VS CodeからPHPやComposerを使う必要があるため、開発用のコマンドを実行するサービスを選びます。

設定の考え方を文章にすると、次のようになります。Composeファイルはプロジェクトのルートから見てひとつ上の階層にあるため、dockerComposeFileには相対パスでそのファイルを指定する。接続先はbackendサービスにし、コンテナ内の/workspaceをプロジェクトの作業ディレクトリとして開く。VS Code側ではPHPの言語サーバーやDocker操作用の拡張機能を利用する、といった構成です。

実際の設定では、customizations.vscode.extensionsにコンテナ内で使いたい拡張機能を指定できます。PHPの補完や静的解析を行う拡張機能、Docker関連の拡張機能、必要に応じてTailwind CSSやPrettierなどを登録しておけば、プロジェクトを開いた時点で同じ開発機能を利用できます。

ここで拡張機能を増やしすぎると、Dev Containersの起動時間やメモリ使用量にも影響します。とりあえず便利そうな拡張機能をすべて入れるのではなく、そのプロジェクトで必要なものだけを選ぶほうが扱いやすくなります。Laravelのバックエンド開発と、フロントエンドのビルド環境では必要な拡張機能が異なるため、ひとつの設定をすべての用途に合わせようとしないことも大切です。

workspaceFolderとボリュームの不一致に注意する

連携時に起きやすい問題が、workspaceFolderとCompose側のボリュームマウント先の不一致です。

Composeでホストのプロジェクトディレクトリをコンテナ内の/var/www/htmlにマウントしているのに、workspaceFolder/workspaceに指定すると、VS Codeは存在しないディレクトリや別のディレクトリを開こうとします。アプリケーションは/var/www/htmlのファイルを参照しているのに、エディタは/workspaceを開いているという状態になれば、編集内容が実行環境に反映されません。

この問題は、コンテナ自体は起動しているため見つけにくいところがあります。ブラウザからアプリケーションを確認すると古い内容が表示され、VS Codeでファイルを開くと変更したはずのコードが見つからない。こうした症状が出たら、まず次の場所を見比べます。

  • Composeのvolumesでソースコードをマウントしている先
  • workspaceFolderで指定したパス
  • Dockerfileで作業ディレクトリとして設定したパス
  • LaravelやWordPressの実行プロセスが参照しているパス

ソースコードの場所を/workspaceに統一するのか、Webアプリケーションの標準的なパスに合わせて/var/www/htmlを使うのかは、どちらでも構いません。問題は、複数の設定で別々のパスを使うことです。

ポート転送は「動く」だけでなく共有しやすさで決める

Dev Containersでは、forwardPortsを使ってコンテナ内のポートをホスト側へ転送できます。Laravelの開発サーバー、Viteの開発サーバー、Mailpitの管理画面など、ブラウザから確認したいサービスがある場合に便利です。

ポート転送は、環境によってはVS Codeが自動検出してくれます。しかし、自動検出だけに頼ると、プロジェクトを初めて開いた人がどのポートへアクセスすればいいのか分かりにくくなります。開発に必要なポートは設定ファイルに明示し、サービス名や用途が分かるようにしておくほうが親切です。

ただし、Compose側ですでにホストへのポート公開を設定している場合は、Dev Containersのポート転送と役割が重なることがあります。両方を設定したから必ず問題になるわけではありませんが、同じポートを異なる目的で公開すると、接続先の理解が難しくなります。

開発者がホストのブラウザからアクセスするだけならComposeのportsで公開し、VS Codeの接続環境として扱うポートだけをforwardPortsに任せるなど、方針を決めておくと迷いません。ポート番号そのものよりも、「どの設定が外部公開を担当するのか」を曖昧にしないことが重要です。

コンテナ内開発におけるDockerソケットの扱い

Dev Containersを使っていると、コンテナの中からDockerコマンドを実行したくなることがあります。たとえば、開発用コンテナから別のテスト用コンテナを起動する場合、複数サービスを使うCLIを動かす場合、あるいはCI/CDの処理をローカルで確認する場合です。

このとき、よく出てくるのがDockerソケットの共有です。

通常、dockerコマンドはDocker CLIだけでは動きません。CLIがDockerデーモンへ接続し、コンテナの作成やイメージの取得を依頼します。Dev Containerの中にDocker CLIだけをインストールしても、接続先のデーモンがなければ操作できません。

そこで、ホスト側のDockerソケットをコンテナ内へマウントする方法があります。一般に「Docker outside of Docker」や、実務上は「docker-from-docker」と呼ばれる構成です。Linux環境では、ホストの/var/run/docker.sockをコンテナ内の同じパスへバインドマウントします。

この構成では、Dev Container内のDocker CLIがホストのDockerデーモンへ接続します。コンテナ内からdocker psを実行すれば、ホスト側で動いているコンテナが見え、同じDocker環境を操作できます。

便利な反面、権限については慎重に考える必要があります。Dockerソケットを操作できるということは、ホストのDockerデーモンに対して強い権限を持つことを意味します。Dev Container内で実行された処理が、Dockerを経由してホスト側へ影響を及ぼす可能性があるためです。

個人開発で、自分が内容を把握しているイメージやスクリプトだけを使うのであれば、現実的な選択肢になります。開発用のCLIからComposeを操作したい場合も、ホストと同じDocker環境を使えるため、構成が分かりやすくなります。

一方、外部から取得したスクリプトを頻繁に実行する場合や、複数人が同じ開発環境を利用する場合は、ソケット共有を当然の設定として扱わないほうがよいでしょう。必要性、権限、イメージの信頼性を確認し、使わなくても済むなら共有しない設計を選びます。

Docker-in-Dockerは分離できるが、重くなる

もうひとつの方法がDocker-in-Dockerです。これはコンテナ内でDockerデーモン自体を動かす構成で、ホスト側のDocker環境とは別のDocker環境を用意できます。

ホストのDockerソケットを共有しないため、環境を分離しやすい点がメリットです。CIのジョブごとに独立したDocker環境を用意したい場合や、ホスト側のコンテナへ影響を与えたくない場合には、検討する価値があります。

ただし、構成は単純ではありません。コンテナ内でデーモンを起動する必要があり、ストレージ、権限、ネットワーク、起動順序などを考慮しなければなりません。Dockerデーモンがひとつ増えるため、リソース消費も大きくなります。

構成向いている場面注意点
Dockerソケット共有ホストのDocker環境を開発コンテナから操作したいホスト側への強い権限を持つことになる
Docker-in-DockerDocker環境を分離して検証したい設定と運用が複雑になりやすい
Dockerを使わないコンテナ操作が不要な開発不要な権限やサービスを増やさずに済む

個人開発のLaravel環境で、開発コンテナから別サービスを操作する必要があるだけなら、Docker-in-Dockerまで持ち込む必要はない場合が多いです。まずはComposeのサービス分割で解決できないか、必要なコマンドをホスト側で実行できないかを確認します。

Dockerソケット共有もDocker-in-Dockerも、導入すれば開発環境が一段上がる機能ではありません。必要な操作を実現するための接続方法です。使う理由を説明できないまま追加すると、後から権限やトラブルの原因になります。

開発環境の標準化がもたらすメリット

Dev Containersを導入する大きな理由は、開発環境の標準化です。compose.yamldevcontainer.jsonをリポジトリに含めておけば、プロジェクトの実行サービスだけでなく、開発者が使うPHPやNode.js、Composer、VS Code拡張機能の組み合わせまで定義できます。

ホスト側にPHPを直接インストールする構成では、OSやパッケージ管理ツールの違いが問題になりがちです。PHPのバージョン、拡張機能、Composerの配置、Node.jsのバージョンが開発者ごとに異なると、同じコードを扱っていても動作が揃いません。

Docker Composeでアプリケーションを動かしていても、コマンドをホスト側で実行していれば、開発ツールの差異は残ります。Dev Containersは、こうした「アプリケーションはコンテナ内、開発コマンドはホスト側」という中途半端な状態を整理するために役立ちます。

特に効果が出やすいのは、次のようなケースです。

  • LaravelのPHP拡張機能やComposerの状態を揃えたい
  • WordPressの開発者ごとにNode.jsやビルドツールの差を出したくない
  • 新しいメンバーが参加したとき、手作業のセットアップを減らしたい
  • VS Codeの拡張機能や静的解析の設定をプロジェクト単位で管理したい
  • ローカルとCIで利用するコマンドの実行環境を近づけたい

標準化の対象は、必ずしもすべての設定である必要はありません。Gitの設定や個人のキーバインドまでリポジトリで固定すると、かえって使いにくくなることがあります。プロジェクトの動作に関係するツール、拡張機能、パス、環境変数を中心に揃え、個人の好みはホスト側に残すほうがバランスを取りやすいです。

Dev Containersのデメリットは起動後に表れやすい

Dev Containersのデメリットは、設定ファイルを書く手間だけではありません。使い始めた後のリソース消費や、ファイルアクセスの速度にも目を向ける必要があります。

VS Codeからコンテナへ接続すると、コンテナ内でVS Code Serverが動きます。さらに、PHPの言語サーバー、JavaScriptやTypeScriptの解析、CSS関連の拡張機能などを有効にすると、それぞれがバックグラウンドで処理を行います。プロジェクトの規模が大きくなるほど、インデックス作成やファイル監視の負荷も増えます。

メモリに余裕のないマシンでは、Composeで複数のサービスを動かした上にDev Containerと各種言語サーバーが加わるため、操作が重くなることがあります。アプリケーション、データベース、Redis、検索サービスなどをすべて常時起動していると、実際にコードを書くためのリソースが圧迫されます。

この場合、最初に見直すべきなのは「本当に常時起動する必要があるサービスか」です。テスト時だけ必要なサービスや、特定の作業でしか使わない管理画面用のサービスは、通常の開発フローから外してもよいでしょう。runServicesを使って、Dev Containers起動時に常に立ち上げるサービスを限定する方法もあります。

たとえば、PHPアプリケーションとデータベースは常に起動し、Redisはキューやキャッシュを確認するときだけ起動する、といった分け方です。Composeに定義されていることと、毎回起動することは同じではありません。

メモリ使用量を確認するときは、OSのタスクマネージャーだけでなく、Dockerのstatsコマンドも役立ちます。どのコンテナがリソースを使っているのかを切り分ければ、Dev Containersが原因なのか、データベースや言語サーバーが原因なのかを判断しやすくなります。

ファイルアクセスの問題は、特にホストとコンテナでファイルシステムの扱いが異なる環境で表れます。ソースコードを bind mount する構成は編集内容をすぐ反映できる一方、ファイル監視や大量の依存ファイルの読み込みで速度が落ちることがあります。vendornode_modulesをどこに置くか、ソースコードと依存関係を同じボリュームにするかは、実際の作業感に影響します。

標準化には、環境差異を減らす代わりに、全員が同じ構成を起動するためのコストが発生します。再現性だけを見て導入すると、開発者のマシンで常に重い環境を作ることになります。反対に、軽さだけを優先してホストへすべてを戻すと、今度は「自分の環境では動く」問題が再発します。

開発環境の標準化は、すべてをコンテナに押し込むことではありません。揃えるべきものと、個人に任せるものの境界を決める作業です。

複数コンテナ環境をVS Codeで扱う設定の勘所

LaravelやWordPressの開発環境では、アプリケーションコンテナだけで完結しないことがよくあります。Webサーバー、PHP実行環境、データベース、キャッシュ、フロントエンドのビルド環境など、複数のサービスが関わります。

このとき、VS Codeが接続するコンテナはひとつに絞ります。VS Codeを接続する対象は、ソースコードを編集し、Composerやnpm、フレームワークのCLIを実行する場所です。データベースコンテナへ接続して開発するわけではありません。

runServicesで起動範囲を調整する

runServicesは、Dev Containersの起動時にComposeのどのサービスを立ち上げるかを指定するための設定です。接続先のアプリケーションサービス以外に、開発中に必要なデータベースやキャッシュを指定できます。

ここでComposeに定義した全サービスを起動することもできますが、必ずしもそれが正解ではありません。開発者が使わないサービスまで起動すれば、メモリやCPUを消費します。開発用、テスト用、デバッグ用のサービスを分けて考えると、構成が扱いやすくなります。

サービスの依存関係も確認が必要です。アプリケーションコンテナがデータベースへ接続する場合、データベースが起動しているだけでは不十分で、接続を受け付けられる状態になっている必要があります。Composeのdepends_onは起動順を整理できますが、アプリケーションが実際に接続可能になるまで待つ仕組みとは限りません。

LaravelのマイグレーションやWordPressの初期化処理を起動時に自動実行する構成では、データベースの準備状態をどう確認するかまで決めておく必要があります。コンテナが起動したことと、アプリケーションが利用可能になったことは別の状態です。

ボリュームは便利だが、役割を分ける

ソースコードのbind mount、依存関係を保持するnamed volume、データベースの永続化ボリュームは、同じ「ボリューム」でも役割が異なります。

ソースコードは、ホストで編集した内容をコンテナ内へ反映したいため、bind mountが使われます。一方、データベースのデータはコンテナを作り直しても残したいので、永続ボリュームに置きます。vendornode_modulesは、ホストとコンテナで実行環境が異なる場合があるため、ソースコードとは別のボリュームに分ける構成もあります。

ここを雑に設計すると、依存関係がホスト側のファイルに上書きされたり、OSの違いによる実行エラーが起きたりします。特に、ホスト側でインストールしたnode_modulesをそのままLinuxコンテナで使うような構成は、ネイティブモジュールが絡むと問題になりやすい部分です。

また、ボリュームを変更した後に古いボリュームが残っていると、設定を直したのに状態が変わらないことがあります。コンテナ、イメージ、ボリュームのどれを作り直す必要があるのかを切り分ける習慣を持つと、無用な初期化を避けられます。データベースのボリュームまで削除すると、開発用データも消えるため、破壊的な操作は対象を確認してから行います。

環境変数はComposeとDev Containersで分担する

環境変数も、二つの設定ファイルにまたがると分かりにくくなります。

データベースのホスト名やポート、ユーザー名など、サービス間通信に必要な値はCompose側で管理するのが自然です。アプリケーションコンテナがdbというサービス名でデータベースへ接続するなら、その接続先はホストのlocalhostではなく、Composeネットワーク上のサービス名になります。

一方、VS Codeで開発するときだけ必要な環境変数や、ターミナルの挙動を変える値は、Dev ContainersのremoteEnvで扱えます。たとえば、開発時のログレベルを変更したり、特定の機能を有効にしたりする用途です。

ただし、アプリケーションの動作に関わる環境変数をremoteEnvだけに置くと、VS Code経由で起動したプロセスと、Composeから起動したプロセスで差が出る可能性があります。環境変数の値をどのプロセスが必要としているのかを確認し、アプリケーションの設定とエディタの設定を混ぜないことが大切です。

秘密情報についても同じです。開発用のパスワードやAPIキーをdevcontainer.jsonへ直接書いてリポジトリにコミットするのは避けます。共有してよい初期値と、個人ごとに用意する値を分け、.envなどの扱いもプロジェクト内で決めておきます。

個人開発者にとっての現実的な選び方

個人開発では、チーム全体の標準化よりも、すぐに作業へ入れることや、マシンの負荷を抑えることが優先される場合があります。そのため、Dev Containersを導入したからといって、必ずしも開発効率が上がるとは限りません。

Docker Composeだけで十分なケースもあります。たとえば、ホスト側のVS CodeやPHP拡張機能をすでに整えていて、コンテナはデータベースやRedisなどの補助サービスとして使う場合です。アプリケーションの実行だけをコンテナに任せ、コマンドはMakefileやComposerスクリプトで統一する構成でも、無理なく運用できます。

Dev Containersが向いているのは、ホスト環境の差をできるだけ消したい場合です。複数のLaravelプロジェクトで異なるPHPバージョンを使う、WordPressのテーマ開発とNode.jsのビルド環境を同時に管理する、PCを買い替えても同じ開発環境をすぐ戻したい、といった場面では効果を感じやすいでしょう。

VS Codeを普段から使っていることも条件になります。Dev ContainersはVS Codeとの統合が強みなので、別のエディタを中心に使っているなら、その利点を十分に受けられません。チーム内でエディタが統一されていない場合は、Dev Containersを必須にするのではなく、Composeだけでも動く手順を残しておくほうが現実的です。

導入するときは、最初から完璧な開発環境を作ろうとしないことです。アプリケーションサービスに接続し、PHPやNode.jsのコマンドがコンテナ内で動くところまでを最初の目標にします。その後で拡張機能、ポート転送、デバッグ、タスク実行、Dockerソケット共有を必要に応じて追加します。

設定が増えるほど、どこで何が動いているのかを把握しにくくなります。Dev Containersの利点は、設定項目の多さではなく、開発者が意識しなくてよい差異を減らせることにあります。設定自体を維持するために毎回悩むなら、標準化のやり方を見直したほうがよいでしょう。

どちらを選ぶかではなく、境界を設計する

Docker ComposeとDev Containersの違いは、機能の優劣ではありません。Composeは複数コンテナによる実行環境を組み立て、Dev Containersはその環境へ開発者を接続します。

この二つを組み合わせる場合は、次の境界を明確にしておくと構成が崩れにくくなります。

  • Composeはサービス、ネットワーク、データ、依存関係を管理する
  • Dev ContainersはVS Codeの接続先、拡張機能、ワークスペースを管理する
  • ソースコードのパスはComposeとworkspaceFolderで統一する
  • 常時起動するサービスと、必要なときだけ起動するサービスを分ける
  • Dockerソケット共有は必要性と権限を確認してから導入する
  • 環境変数や秘密情報の管理場所を決めておく
  • メモリ使用量とファイルアクセス速度を、実際の作業環境で確認する

Docker Composeだけで構成を始め、開発コマンドや拡張機能まで揃えたくなった段階でDev Containersを加える方法もあります。逆に、最初から開発環境をリポジトリで共有する必要があるなら、Composeとdevcontainer.jsonを同時に設計しても構いません。

個人開発者にとっては、環境を標準化すること自体が目的ではありません。コードを書く、テストする、データを確認するという作業を、毎回同じ条件で繰り返せることが目的です。そのためにComposeを使い、必要ならDev Containersを使う。順番を逆にして、ツールに合わせて開発方法を変える必要はありません。

ローカル開発環境の構築で迷ったら、まずComposeで何を動かし、Dev Containersで何を揃えたいのかを書き出してみるとよいでしょう。実行環境と開発体験を分けて考えれば、「docker compose devcontainers 違い 開発環境」という検索で見かけるような単純な二択から抜け出せます。

最終的な構成は、プロジェクトの規模、使用するサービス、開発者のエディタ、マシンのリソースによって変わります。Docker Composeを土台にしながら、開発者が本当に必要とする部分だけをDev Containersへ寄せる。そのくらいの距離感が、ローカル開発環境を長く使い続けるうえではちょうどよいはずです。

よくある質問

Docker ComposeとDev Containersはどちらを使うべきですか?
これらは比較対象ではなく、役割が異なります。Docker Composeは実行環境の構築に、Dev ContainersはVS Codeを用いた開発体験の統合に使用するため、組み合わせて利用するのが一般的です。
Dev ContainersでDockerコマンドを使えるようにするにはどうすればいいですか?
ホスト側のDockerソケットをコンテナ内にバインドマウントすることで、コンテナ内からホストのDockerデーモンを操作できます。ただし、強い権限を持つことになるため、セキュリティ上のリスクを考慮する必要があります。
Dev Containersを使うとPCが重くなるのはなぜですか?
コンテナ内でVS Code Serverや各種言語サーバー、拡張機能がバックグラウンドで動作するため、リソースを消費します。不要なサービスを起動しないように設定するか、リソース使用量を確認して調整してください。
Dev ContainersとComposeでソースコードのパスが合わない場合は?
Composeのボリュームマウント先と、devcontainer.jsonのworkspaceFolder、およびDockerfileの作業ディレクトリが一致しているか確認してください。これらが異なると、エディタでの編集内容が実行環境に反映されません。
Dev Containersの起動時に特定のサービスだけを立ち上げることはできますか?
runServicesプロパティを使用することで、Dev Containers起動時に立ち上げるComposeサービスを限定できます。これにより、メモリ消費を抑え、必要なサービスのみを稼働させることが可能です。

参考情報