<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Infrastructure on kenji.blog</title><link>http://kenji.blog/categories/infrastructure/</link><description>Recent content in Infrastructure on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ja</language><copyright>kenjinote</copyright><lastBuildDate>Sun, 13 Sep 2026 07:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/categories/infrastructure/index.xml" rel="self" type="application/rss+xml"/><item><title>Hyper-V vs WSL2：Windows上の仮想化技術を比較</title><link>http://kenji.blog/p/hyper-v-vs-wsl2-windows-virtualization/</link><pubDate>Sun, 13 Sep 2026 07:00:00 +0900</pubDate><guid>http://kenji.blog/p/hyper-v-vs-wsl2-windows-virtualization/</guid><description>&lt;img src="http://kenji.blog/p/hyper-v-vs-wsl2-windows-virtualization/img/eyecatch.jpg" alt="Featured image of post Hyper-V vs WSL2：Windows上の仮想化技術を比較" />&lt;h2 id="1-はじめにwindowsにおける仮想化の進化">1. はじめに：Windowsにおける仮想化の進化
&lt;/h2>&lt;p>Windowsプラットフォームにおける仮想化技術は、ここ数十年で劇的な進化を遂げました。かつてはサードパーティ製のType 2ハイパーバイザ（VMware WorkstationやVirtualBoxなど）が主流でしたが、MicrosoftがWindows Server 2008で「Hyper-V」を導入して以来、Type 1ハイパーバイザがデスクトップOSであるWindows 10/11にも組み込まれるようになりました。&lt;/p>
&lt;p>そして近年、開発者の間で最も注目されているのが「WSL2 (Windows Subsystem for Linux 2)」です。WSL1がシステムコールの変換（トランスレーション）に依存していたのに対し、WSL2はHyper-Vの技術を応用した「軽量ユーティリティVM (Lightweight Utility VM)」を採用し、完全なLinux互換性と飛躍的なパフォーマンス向上を実現しました。&lt;/p>
&lt;p>本記事では、これら2つの強力な仮想化技術――フル機能の「Hyper-V」と、開発者体験に特化した「WSL2」――のアーキテクチャ、パフォーマンス（CPU、メモリ、ディスクI/O）、ネットワーク構成、そして最適なユースケースについて、深淵なる技術的詳細とともに徹底的に比較・解説します。&lt;/p>
&lt;hr>
&lt;h2 id="2-ハイパーバイザの基礎理論とアーキテクチャの比較">2. ハイパーバイザの基礎理論とアーキテクチャの比較
&lt;/h2>&lt;p>仮想化技術を理解する上で欠かせないのが、ハイパーバイザ（仮想マシンモニター: VMM）のタイプ分類です。&lt;/p>
&lt;h3 id="21-type-1-と-type-2-ハイパーバイザの違い">2.1. Type 1 と Type 2 ハイパーバイザの違い
&lt;/h3>&lt;p>ハイパーバイザは、ハードウェアへのアクセスを抽象化し、複数のOS（ゲストOS）を単一の物理マシン上で同時に実行させるソフトウェア層です。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Type 1（ベアメタル型）&lt;/strong>: ハードウェア上で直接実行されます。ホストOSという概念が存在せず（厳密には特権を持つ管理OSが存在する場合があります）、極めてオーバーヘッドが低く、高いパフォーマンスとセキュリティを提供します。例：Hyper-V, VMware ESXi, Xen。&lt;/li>
&lt;li>&lt;strong>Type 2（ホスト型）&lt;/strong>: ホストOS（WindowsやmacOSなど）上のアプリケーションとして実行されます。すべてのハードウェアアクセスはホストOSを経由するため、オーバーヘッドが大きくなります。例：VMware Workstation, Oracle VirtualBox。&lt;/li>
&lt;/ul>
&lt;p>WindowsのHyper-Vは、純粋な &lt;strong>Type 1ハイパーバイザ&lt;/strong> です。Hyper-Vを有効にすると、実は普段ユーザーが操作しているWindows OS自体も、「ルートパーティション (Root Partition)」と呼ばれる特殊な仮想マシンの中で動作するようになります。&lt;/p>
&lt;h3 id="22-hyper-vのアーキテクチャ詳細">2.2. Hyper-Vのアーキテクチャ詳細
&lt;/h3>&lt;p>Hyper-Vのアーキテクチャは、マイクロカーネル設計を採用しており、パーティション（Partition）と呼ばれる論理的な分離単位に基づいています。&lt;/p>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;Hardware (CPU, RAM, Disk, NIC)&amp;#34;] --&amp;gt; B[&amp;#34;Windows Hypervisor (Ring -1)&amp;#34;]
B --&amp;gt; C[&amp;#34;Root Partition (Windows OS)&amp;#34;]
B --&amp;gt; D[&amp;#34;Child Partition 1 (Windows VM)&amp;#34;]
B --&amp;gt; E[&amp;#34;Child Partition 2 (Linux VM)&amp;#34;]
C --&amp;gt; F[&amp;#34;VMBus (Virtual Machine Bus)&amp;#34;]
D --&amp;gt; F
E --&amp;gt; F
C --&amp;gt; G[&amp;#34;VID (Virtualization Infrastructure Driver)&amp;#34;]
C --&amp;gt; H[&amp;#34;VMWP.exe (Worker Process)&amp;#34;]
&lt;/pre>
&lt;ul>
&lt;li>&lt;strong>Windows Hypervisor&lt;/strong>: CPUの最も特権レベルの高い状態（Ring -1 または VMX Root Mode）で動作し、メモリの割り当てとCPUのスケジューリングのみを担当します。デバイスドライバは含まれていません。&lt;/li>
&lt;li>&lt;strong>Root Partition&lt;/strong>: ホストWindows OSが動作するパーティションです。すべてのデバイスドライバを持ち、ハードウェアを直接制御します。また、子パーティションの管理機能（WMIプロバイダやVMWP.exeなど）を提供します。&lt;/li>
&lt;li>&lt;strong>Child Partition&lt;/strong>: ゲストOSが動作するパーティションです。ハードウェアへの直接アクセスは許可されておらず、「VMBus」と呼ばれる論理的なメモリ共有バスを介して、ルートパーティションにI/Oリクエストを送信（Synthetic I/O）します。&lt;/li>
&lt;/ul>
&lt;h3 id="23-wsl2とlightweight-utility-vmの仕組み">2.3. WSL2とLightweight Utility VMの仕組み
&lt;/h3>&lt;p>WSL2は、Hyper-Vと同じType 1ハイパーバイザの基盤技術を利用していますが、フル機能のHyper-V仮想マシンとは異なる「仮想マシンプラットフォーム (Virtual Machine Platform: VMP)」と呼ばれるサブセット機能を利用しています。&lt;/p>
&lt;p>WSL2で採用されている「軽量ユーティリティVM (Lightweight Utility VM)」は、従来のVMが持つレガシーハードウェアのエミュレーション（仮想BIOSや仮想マザーボードなど）を一切排除しています。&lt;/p>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;Windows Host OS (User Space)&amp;#34;]
B[&amp;#34;NTFS File System&amp;#34;]
C[&amp;#34;9P Protocol Server (Plan 9)&amp;#34;]
D[&amp;#34;Lightweight Utility VM (Linux Kernel)&amp;#34;]
E[&amp;#34;ext4.vhdx (Virtual Disk)&amp;#34;]
F[&amp;#34;Linux User Space (WSL2 Distributions)&amp;#34;]
A --&amp;gt; C
C --&amp;gt;| &amp;#34;Cross-OS File Sharing&amp;#34; | D
D --&amp;gt; E
D --&amp;gt; F
&lt;/pre>
&lt;p>WSL2の最大の特徴は、&lt;strong>起動の速さ&lt;/strong>と&lt;strong>ホストOSとのシームレスな統合&lt;/strong>です。数秒未満でLinuxカーネルが起動し、Windows側のファイルシステム（NTFS）にはPlan 9の &lt;code>9P&lt;/code> ネットワークファイルシステムプロトコルを介してアクセスします。&lt;/p>
&lt;hr>
&lt;h2 id="3-パフォーマンスの徹底分析計算資源とio">3. パフォーマンスの徹底分析：計算資源とI/O
&lt;/h2>&lt;p>仮想マシンのパフォーマンスは、CPU、メモリ、およびディスクI/Oの各コンポーネントにおけるオーバーヘッドの総和として表されます。&lt;/p>
&lt;h3 id="31-cpuとコンテキストスイッチオーバーヘッド">3.1. CPUとコンテキストスイッチ・オーバーヘッド
&lt;/h3>&lt;p>Hyper-VおよびWSL2はともにハードウェア支援仮想化（Intel VT-x / AMD-V）を使用しています。CPU命令は基本的にネイティブ速度で実行されますが、特権命令の実行時やI/O処理時には「VM Exit」と呼ばれる割り込みが発生し、ハイパーバイザへコンテキストスイッチが行われます。&lt;/p>
&lt;p>このときのCPUオーバーヘッド $T_{overhead}$ は、次のような数学的モデルで表現できます。&lt;/p>
$$ T_{overhead} = \sum_{i=1}^{N} (t_{vm\_exit} + t_{hypercall\_process} + t_{vm\_entry}) $$&lt;p>ここで：&lt;/p>
&lt;ul>
&lt;li>$N$: 単位時間あたりのVM Exit発生回数&lt;/li>
&lt;li>$t_{vm\_exit}$: ゲストからハイパーバイザへの移行時間&lt;/li>
&lt;li>$t_{hypercall\_process}$: VMBusを介したI/O処理や割り込みの処理時間&lt;/li>
&lt;li>$t_{vm\_entry}$: ハイパーバイザからゲストへの復帰時間&lt;/li>
&lt;/ul>
&lt;p>WSL2は、レガシーエミュレーションがないため $t_{hypercall\_process}$ が極めて小さく最適化されています。そのため、純粋なCPU演算（例えばカーネルのコンパイルや機械学習モデルの推論）においては、ベアメタル環境と比較しても数％以内の性能劣化に収まります。&lt;/p>
&lt;h3 id="32-メモリ割り当てのメカニズム">3.2. メモリ割り当てのメカニズム
&lt;/h3>&lt;p>メモリの管理手法においては、両者に明確な設計思想の違いがあります。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Hyper-V (Dynamic Memory)&lt;/strong>: ゲストVMのメモリ需要に応じて、ルートパーティションが動的にメモリを割り当て・回収します。しかし、ゲストOS内でページキャッシュとして確保されたメモリは、システムが逼迫していない限り解放されにくい傾向があります。&lt;/li>
&lt;li>&lt;strong>WSL2 (動的メモリ回収)&lt;/strong>: WSL2は独自の仕組みを持ち、LinuxVM内で不要になったメモリ（キャッシュを含む）を、定期的にWindowsホストに返却（Reclaim）します。初期のWSL2ではLinuxのページキャッシュがWindowsのメモリを食い潰す問題（Vmmemプロセスの肥大化）がありましたが、現在はカーネルパッチにより改善されています。&lt;/li>
&lt;/ul>
&lt;h3 id="33-ディスクioの特性vhdx-vs-ext4vhdx">3.3. ディスクI/Oの特性（VHDX vs ext4.vhdx）
&lt;/h3>&lt;p>仮想マシンのパフォーマンスにおいて最もボトルネックになりやすいのがディスクI/Oです。&lt;/p>
&lt;p>I/Oのレイテンシ $L_{total}$ は以下のように計算されます。&lt;/p>
$$ L_{total} = L_{guest\_fs} + L_{vmbus} + L_{host\_fs} + L_{physical\_disk} $$&lt;p>&lt;strong>Hyper-Vの場合&lt;/strong>:
一般的なHyper-Vゲストは &lt;code>VHDX&lt;/code> フォーマットの仮想ディスクを使用します。ゲストOS内のファイルシステム（ext4やNTFS）から発行されたI/O要求は、VMBusのブロックデバイスストレージドライバ（storvsc）を通過し、Windows側のNTFS上でVHDXファイルへのアクセスとして処理されます。&lt;/p>
&lt;p>&lt;strong>WSL2の場合&lt;/strong>:
WSL2のLinuxディストリビューションは、専用の &lt;code>ext4.vhdx&lt;/code> ファイル内に構築されたネイティブのext4ファイルシステム上で動作します。Linux内でのファイル操作（&lt;code>~&lt;/code> ディレクトリ内など）は、上記のHyper-Vと同等のネイティブパフォーマンスを発揮します。
しかし、&lt;strong>WSL2のLinuxからWindows側のファイル（&lt;code>/mnt/c/&lt;/code> など）にアクセスする場合&lt;/strong>、またはその逆の場合、処理は大きく異なります。このクロスOSアクセスには &lt;code>9P (Plan 9 File System Protocol)&lt;/code> が使用されます。&lt;/p>
$$ L_{cross\_os} = L_{9p\_client} + L_{socket\_transfer} + L_{9p\_server} + L_{ntfs} $$&lt;p>この9Pプロトコルを経由したアクセスはシリアライズ処理のオーバーヘッドが大きく、小さなファイルを大量に読み書きする用途（例：Windows側のディレクトリに置かれたNode.jsプロジェクトでの &lt;code>npm install&lt;/code> やGit操作）では、パフォーマンスが著しく低下（時には10倍以上の遅延）します。
そのため、&lt;strong>WSL2を使用する際は、必ずプロジェクトファイルをLinuxのネイティブファイルシステム（&lt;code>~/&lt;/code> 配下）に配置することが鉄則&lt;/strong>です。&lt;/p>
&lt;hr>
&lt;h2 id="4-ネットワーク構造natdefault-switchbridged">4. ネットワーク構造：NAT、Default Switch、Bridged
&lt;/h2>&lt;p>ネットワーク機能の柔軟性は、Hyper-VとWSL2の大きな違いの一つです。&lt;/p>
&lt;h3 id="41-wsl2のネットワーク-natベース">4.1. WSL2のネットワーク (NATベース)
&lt;/h3>&lt;p>WSL2のネットワークは、デフォルトでHyper-Vの仮想スイッチテクノロジーを使用した「NAT（Network Address Translation）」構成になっています。
Linux VMには、Windowsホストとは異なるプライベートIPアドレス（例：&lt;code>172.20.x.x&lt;/code>）が自動的に割り当てられます。Windowsホストからは &lt;code>localhost&lt;/code> でWSL2内で起動したサービス（ポート）にフォワードされる仕組みが組み込まれており、開発者はネットワークを意識せずにWebサーバーなどをテストできます。&lt;/p>
&lt;p>近年、WSL2には「Mirrored モード」という新しいネットワークモードがプレビュー版で導入されました。これにより、IPv6のサポートやVPN接続の互換性向上が図られています（&lt;code>.wslconfig&lt;/code> にて設定可能）。&lt;/p>
&lt;h3 id="42-hyper-vの仮想スイッチ-virtual-switch">4.2. Hyper-Vの仮想スイッチ (Virtual Switch)
&lt;/h3>&lt;p>Hyper-Vは、エンタープライズレベルの高度なネットワーク構築が可能です。「仮想スイッチマネージャー」を通じて、主に3つのモードを提供します。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>外部 (External)&lt;/strong>: ホストマシンの物理NICを仮想スイッチにバインドし、ゲストVMを物理ネットワーク上に直接参加させます（ブリッジ接続）。VMはDHCPサーバーから物理ネットワークと同じサブネットのIPを取得します。&lt;/li>
&lt;li>&lt;strong>内部 (Internal)&lt;/strong>: ホストOSとVM間、およびVM同士の通信のみを許可します。外部ネットワークには直接出られません。&lt;/li>
&lt;li>&lt;strong>プライベート (Private)&lt;/strong>: VM同士の通信のみを許可し、ホストOSとの通信も遮断します。隔離された検証環境の構築に使用されます。&lt;/li>
&lt;/ol>
&lt;h3 id="43-powershellによる高度なhyper-vネットワーク構築">4.3. PowerShellによる高度なHyper-Vネットワーク構築
&lt;/h3>&lt;p>開発やテスト環境において、VM用にカスタマイズされたNATネットワークを構築したい場合、PowerShellを使用することで詳細な制御が可能です。以下は、内部仮想スイッチを作成し、そこにNATを構成してVMにインターネットアクセスを提供するスクリプトの例です。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 1. 内部仮想スイッチの作成&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$SwitchName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;HyperV-NatSwitch&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">New-VMSwitch&lt;/span> &lt;span class="n">-SwitchName&lt;/span> &lt;span class="nv">$SwitchName&lt;/span> &lt;span class="n">-SwitchType&lt;/span> &lt;span class="n">Internal&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 2. ホスト側の仮想NICにIPアドレスを設定 (ゲートウェイとなるIP)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$GatewayIP&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;192.168.100.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$NetPrefix&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mf">24&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$InterfaceAlias&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;vEthernet (&lt;/span>&lt;span class="nv">$SwitchName&lt;/span>&lt;span class="s2">)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">New-NetIPAddress&lt;/span> &lt;span class="n">-IPAddress&lt;/span> &lt;span class="nv">$GatewayIP&lt;/span> &lt;span class="n">-PrefixLength&lt;/span> &lt;span class="nv">$NetPrefix&lt;/span> &lt;span class="n">-InterfaceAlias&lt;/span> &lt;span class="nv">$InterfaceAlias&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 3. NATネットワークの構成&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$NatName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;HyperV-NatNetwork&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$NatSubnet&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;192.168.100.0/24&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">New-NetNat&lt;/span> &lt;span class="n">-Name&lt;/span> &lt;span class="nv">$NatName&lt;/span> &lt;span class="n">-InternalIPInterfaceAddressPrefix&lt;/span> &lt;span class="nv">$NatSubnet&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 確認用コマンド&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Get-NetNat&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>この構成により、指定したHyper-Vゲストに手動で &lt;code>192.168.100.x&lt;/code> のIPと、ゲートウェイ &lt;code>192.168.100.1&lt;/code> を設定することで、ホストを経由して外部と通信可能な独自のNATセグメントを構築できます。&lt;/p>
&lt;hr>
&lt;h2 id="5-ユースケースと実践的な選択ガイド">5. ユースケースと実践的な選択ガイド
&lt;/h2>&lt;p>これまでのアーキテクチャやパフォーマンスの違いを踏まえ、どのような状況でどちらの技術を採用すべきかを定義します。&lt;/p>
&lt;h3 id="51-wsl2を選択すべきシナリオ">5.1. WSL2を選択すべきシナリオ
&lt;/h3>&lt;p>WSL2は、「開発者の生産性向上」に特化して設計されています。以下のような用途に最適です。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Web開発およびクラウドネイティブ開発&lt;/strong>: Docker Desktop（WSL2バックエンド）やPodmanを使用したコンテナ開発。&lt;/li>
&lt;li>&lt;strong>Linux専用ツールの利用&lt;/strong>: bash、grep、awk、sed、またはLinux向けのGCCやClangコンパイラを日常的に使用する場合。&lt;/li>
&lt;li>&lt;strong>GUIアプリケーション (WSLg)&lt;/strong>: LinuxのX11/WaylandアプリケーションをWindowsデスクトップ上でシームレスに動かしたい場合。&lt;/li>
&lt;li>&lt;strong>機械学習とAI開発&lt;/strong>: GPUパススルー機能（NVIDIA CUDA on WSL）を利用したTensorFlowやPyTorchの高速な学習。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>注意点&lt;/strong>: カーネルを細かくカスタマイズしたい場合や、systemdに強く依存する複雑なサービス（現在systemdはサポートされていますが、デフォルトでは無効または制限あり）を構築する場合は制約を受けることがあります。&lt;/p>
&lt;h3 id="52-hyper-vを選択すべきシナリオ">5.2. Hyper-Vを選択すべきシナリオ
&lt;/h3>&lt;p>Hyper-Vは、「インフラストラクチャの仮想化と完全な分離」を目的としています。以下のような用途に必須となります。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Windows VMの実行&lt;/strong>: 異なるバージョンのWindows（Windows Serverや古いWindows 10など）をテスト環境として実行する場合。&lt;/li>
&lt;li>&lt;strong>ネステッド・バーチャライゼーション（入れ子仮想化）&lt;/strong>: 仮想マシンの中でさらに仮想マシン（Hyper-VやKVM）を実行したい場合。インフラエンジニアの検証環境に不可欠です。&lt;/li>
&lt;li>&lt;strong>高度なネットワーク要件&lt;/strong>: 外部ブリッジ接続（同一LANへの参加）、VLANタギング、複数NICの割り当てなど、ネットワーク構成を厳密にコントロールする必要がある場合。&lt;/li>
&lt;li>&lt;strong>スナップショット（チェックポイント）&lt;/strong>: VMの特定時点の状態を保存し、いつでも瞬時にロールバックできる機能。ソフトウェアの破壊的なテストやマルウェア解析などに極めて有用です。&lt;/li>
&lt;li>&lt;strong>固定のリソース割り当て&lt;/strong>: CPUコア数やメモリ量を厳格に固定し、ホストOSへの影響を最小限に抑えたい場合。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="6-数理モデルによるioスループットの考察-付録">6. 数理モデルによるI/Oスループットの考察 (付録)
&lt;/h2>&lt;p>システムエンジニアとして両者のI/O性能限界を見極める際、スループット $S$ とブロックサイズ $B$ の関係を理論的に把握することは重要です。&lt;/p>
&lt;p>データ転送のスループット $S$ は、単位時間あたりのデータ転送量であり、次のようにモデル化されます。&lt;/p>
$$ S(B) = \frac{B}{L_{setup} + \frac{B}{R_{max}}} $$&lt;ul>
&lt;li>$B$: ブロックサイズ (Bytes)&lt;/li>
&lt;li>$L_{setup}$: I/O要求のセットアップおよびコンテキストスイッチに伴う固定レイテンシ&lt;/li>
&lt;li>$R_{max}$: コピーやデバイス転送におけるハードウェアの最大帯域幅&lt;/li>
&lt;/ul>
&lt;p>WSL2の9Pプロトコルを介したファイルアクセスでは、この $L_{setup}$ が非常に大きくなります（ソケット通信とプロトコルのシリアライズ/デシリアライズのため）。したがって、ブロックサイズ $B$ が小さい（数KB程度の細かいファイルの大量読み書き）場合、分母における $L_{setup}$ の影響が支配的となり、スループット $S$ は劇的に低下します。
逆に、Hyper-VのVMBusを経由するVHDXアクセスでは $L_{setup}$ がハードウェア割り込みに近いレベルまで最適化されているため、小規模ブロックでも高いIOPSを維持できます。&lt;/p>
&lt;p>この数学的現実が、「WSL2ではプロジェクトファイルをWindows側に置いてはいけない」というベストプラクティスの論理的根拠となっています。&lt;/p>
&lt;hr>
&lt;h2 id="7-まとめ共存する2つの仮想化技術">7. まとめ：共存する2つの仮想化技術
&lt;/h2>&lt;p>Hyper-VとWSL2は、どちらか一方が優れているというものではなく、**「目的が異なる2つのソリューション」**です。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>WSL2&lt;/strong> は、WindowsというOSの殻を破り、Linuxのエコシステムをシームレスかつ高速にWindowsユーザーの手に届けるための「最良の統合ツール」です。開発者のための究極のCLI環境と言っても過言ではありません。&lt;/li>
&lt;li>&lt;strong>Hyper-V&lt;/strong> は、エンタープライズのデータセンターで培われた強固な分離性と管理能力をデスクトップに持ち込む「本格的なハイパーバイザ」です。ネットワークの構築、Windows OSのテスト、インフラ環境のシミュレーションにおいて右に出るものはありません。&lt;/li>
&lt;/ul>
&lt;p>現代のWindows環境では、これら2つの技術は互角に競合するのではなく、同じVMプラットフォーム上で美しく共存します。用途に応じて適材適所で使い分けることで、Windowsは世界で最も強力で柔軟なエンジニアリング・ワークステーションとなるでしょう。&lt;/p></description></item><item><title>Dockerを使った再現可能なローカル開発環境の構築手順</title><link>http://kenji.blog/p/docker-reproducible-local-dev-environment/</link><pubDate>Sun, 13 Sep 2026 01:00:00 +0900</pubDate><guid>http://kenji.blog/p/docker-reproducible-local-dev-environment/</guid><description>&lt;img src="http://kenji.blog/p/docker-reproducible-local-dev-environment/img/eyecatch.jpg" alt="Featured image of post Dockerを使った再現可能なローカル開発環境の構築手順" />&lt;h2 id="1-はじめに私の環境では動くのにからの脱却">1. はじめに：「私の環境では動くのに」からの脱却
&lt;/h2>&lt;p>ソフトウェア開発の現場において、開発者間で環境が異なることに起因する「私の環境では動くのに（It works on my machine）」という問題は、長きにわたり多くのプロジェクトで時間を浪費させる要因となってきました。OSの違い、インストールされている言語のバージョン、ライブラリの依存関係、グローバルにインストールされたツールの競合など、ローカル環境は常に「状態の不確実性」に晒されています。&lt;/p>
&lt;p>こうした課題を根本から解決するのが &lt;strong>Docker&lt;/strong> をはじめとするコンテナ技術と、&lt;strong>Infrastructure as Code (IaC)&lt;/strong> のパラダイムです。ローカル開発環境をコンテナ化することで、OSレベルでの分離を実現し、コードベースと共に環境そのものをバージョン管理することが可能になります。&lt;/p>
&lt;p>本記事では、Docker、Docker Compose、そしてVSCode DevContainersを駆使し、**「誰が、いつ、どのマシンで立ち上げても、寸分違わず同じ状態になる再現可能なローカル開発環境」**を構築するための手順と、その背後にある深い技術的メカニズムについて、数理的な視点も交えながら徹底的に解説します。&lt;/p>
&lt;hr>
&lt;h2 id="2-infrastructure-as-code-iac-とコンテナ技術の親和性">2. Infrastructure as Code (IaC) とコンテナ技術の親和性
&lt;/h2>&lt;h3 id="iacの原則とローカル環境への適用">IaCの原則とローカル環境への適用
&lt;/h3>&lt;p>Infrastructure as Code (IaC) とは、インフラストラクチャの設定やプロビジョニングを、手動のプロセスではなく、機械可読な定義ファイルを通じて管理するアプローチです。IaCのコアとなる原則には以下の要素が含まれます。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>宣言的アプローチ (Declarative Approach)&lt;/strong>: 「どのように状態を変更するか」ではなく「最終的にどのような状態であるべきか」を定義します。&lt;/li>
&lt;li>&lt;strong>冪等性 (Idempotency)&lt;/strong>: 何度スクリプトを実行しても、常に同じ結果（状態）が保証されます。&lt;/li>
&lt;li>&lt;strong>バージョン管理 (Version Control)&lt;/strong>: インフラの状態がコードとしてGitなどのVCSに保存され、変更履歴の追跡やピアレビューが可能になります。&lt;/li>
&lt;/ol>
&lt;p>ローカル開発環境においてIaCを実践するということは、&lt;code>Dockerfile&lt;/code> や &lt;code>docker-compose.yml&lt;/code>、&lt;code>devcontainer.json&lt;/code> を使って開発環境の「あるべき姿」をコード化することを意味します。これにより、新しくチームに加わったメンバーも、リポジトリをクローンしてコマンドを1つ叩くだけで、即座に開発をスタートできるオンボーディング体験を実現できます。&lt;/p>
&lt;h3 id="コンテナ技術を支えるカーネル機能">コンテナ技術を支えるカーネル機能
&lt;/h3>&lt;p>コンテナ技術は、仮想マシン（VM）のようなハイパーバイザ型の仮想化とは異なり、ホストOSのカーネルを共有しながらプロセスを隔離（アイソレーション）する軽量な仮想化技術です。これを実現するために、主にLinuxカーネルの以下の機能が利用されています。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Namespaces&lt;/strong>: プロセスごとにシステムリソース（PID、ネットワーク、マウントポイント、ユーザーなど）の独立したビューを提供します。&lt;/li>
&lt;li>&lt;strong>Cgroups (Control Groups)&lt;/strong>: プロセスが使用できる物理リソース（CPU、メモリ、ディスクI/Oなど）の制限と割り当てを行います。&lt;/li>
&lt;li>&lt;strong>UnionFS (Union File System)&lt;/strong>: 複数のディレクトリツリー（レイヤー）を透過的に重ね合わせて、1つのファイルシステムとして見せる技術です。Dockerのイメージレイヤーはこの技術に依存しています。&lt;/li>
&lt;/ul>
&lt;p>リソース制限の数理モデルを考えてみましょう。ホストマシンの総メモリ容量を $M_{\text{total}}$ とし、ホスト上で動作する $n$ 個のコンテナのメモリ制限を $m_i$ とします。システムが安定して稼働するための必要条件は、ホストOSやその他のプロセスが消費するベースメモリ $M_{\text{os}}$ を考慮すると、次のような不等式で表すことができます。&lt;/p>
$$ \sum_{i=1}^{n} m_i \le M_{\text{total}} - M_{\text{os}} $$&lt;p>Cgroupsを利用してコンテナごとに $m_i$ を厳格に定義することで、特定のコンテナがメモリリークを起こした際にも、OOM (Out Of Memory) Killer によって他のコンテナやホストシステム全体がダウンするのを防ぐことができます。&lt;/p>
&lt;hr>
&lt;h2 id="3-効率的な-dockerfile-の設計マルチステージビルドを極める">3. 効率的な Dockerfile の設計：マルチステージビルドを極める
&lt;/h2>&lt;p>再現可能な環境の第一歩は、アプリケーションの実行環境を定義する &lt;code>Dockerfile&lt;/code> の設計です。ここでは、Python（FastAPI）を例に、&lt;strong>マルチステージビルド&lt;/strong> を活用したセキュアで軽量な Dockerfile のベストプラクティスを解説します。&lt;/p>
&lt;p>マルチステージビルドは、1つの &lt;code>Dockerfile&lt;/code> の中で複数の &lt;code>FROM&lt;/code> 命令を使用し、ビルド環境（コンパイラや開発ツールが含まれる重い環境）と、実行環境（必要な成果物だけを持つ軽量な環境）を分離する手法です。&lt;/p>
&lt;h3 id="実践的な-python-fastapi-用-dockerfile">実践的な Python FastAPI 用 Dockerfile
&lt;/h3>&lt;p>以下のコードは、Poetryを使った依存関係管理と、マルチステージビルドを組み合わせた高度な &lt;code>Dockerfile&lt;/code> の例です。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># ---------------------------------------------------------&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Stage 1: Builder (ビルド環境)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># ---------------------------------------------------------&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="s"> python:3.11-slim AS builder&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 必要な環境変数の設定&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENV&lt;/span> &lt;span class="nv">PYTHONUNBUFFERED&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">PYTHONDONTWRITEBYTECODE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">POETRY_VERSION&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span>.6.1 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">POETRY_HOME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/opt/poetry&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">POETRY_VIRTUALENVS_IN_PROJECT&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">POETRY_NO_INTERACTION&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 依存パッケージのインストール&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> apt-get update &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> apt-get install -y --no-install-recommends &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> curl build-essential &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> curl -sSL https://install.python-poetry.org &lt;span class="p">|&lt;/span> python3 - &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> apt-get clean &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> rm -rf /var/lib/apt/lists/*&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENV&lt;/span> &lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$POETRY_HOME&lt;/span>&lt;span class="s2">/bin:&lt;/span>&lt;span class="nv">$PATH&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">WORKDIR&lt;/span>&lt;span class="s"> /app&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 依存関係ファイルのコピーとインストール&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> pyproject.toml poetry.lock ./&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> poetry install --no-root --only main&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># ---------------------------------------------------------&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Stage 2: Runtime (実行環境)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># ---------------------------------------------------------&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="s"> python:3.11-slim AS runtime&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENV&lt;/span> &lt;span class="nv">PYTHONUNBUFFERED&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">PYTHONDONTWRITEBYTECODE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/app/.venv/bin:&lt;/span>&lt;span class="nv">$PATH&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 最小限の非特権ユーザーを作成&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> groupadd -r appuser &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> useradd -r -g appuser appuser&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">WORKDIR&lt;/span>&lt;span class="s"> /app&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># ビルダーから仮想環境（依存関係）のみをコピー&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> --from&lt;span class="o">=&lt;/span>builder --chown&lt;span class="o">=&lt;/span>appuser:appuser /app/.venv /app/.venv&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># アプリケーションコードのコピー&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> --chown&lt;span class="o">=&lt;/span>appuser:appuser ./src /app/src&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 非特権ユーザーへの切り替え&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">USER&lt;/span>&lt;span class="s"> appuser&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># コンテナ起動時のデフォルトコマンド&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENTRYPOINT&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;uvicorn&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;src.main:app&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--host&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;0.0.0.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--port&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;8000&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="マルチステージビルドによるイメージサイズの数学的評価">マルチステージビルドによるイメージサイズの数学的評価
&lt;/h3>&lt;p>単一ステージでビルドした場合のイメージサイズを $S_{\text{single}}$、マルチステージビルドを適用した場合のイメージサイズを $S_{\text{multi}}$ とします。サイズ削減率 $R$ は次のように計算されます。&lt;/p>
$$ R = \left( 1 - \frac{S_{\text{multi}}}{S_{\text{single}}} \right) \times 100 \ (\%) $$&lt;p>例えば、$S_{\text{single}}$ にはOSのベースイメージ（約110MB）、開発用パッケージ（gccなど約150MB）、Poetry本体（約40MB）、プロジェクトの依存ライブラリ（約80MB）、ソースコード（約5MB）が含まれ、合計で385MBになったとします。
一方、$S_{\text{multi}}$ では、ベースイメージ（110MB）に依存ライブラリ（80MB）とソースコード（5MB）のみがコピーされるため、合計195MBとなります。&lt;/p>
$$ R = \left( 1 - \frac{195}{385} \right) \times 100 \approx 49.35\% $$&lt;p>このように、マルチステージビルドを導入することで、イメージサイズを約半分に削減できます。イメージサイズの削減は、レジストリからのPull時間の短縮、ディスク容量の節約、そして攻撃対象領域（Attack Surface）の縮小によるセキュリティ向上に直結します。&lt;/p>
&lt;hr>
&lt;h2 id="4-docker-composeによる複数コンテナのオーケストレーション">4. Docker Composeによる複数コンテナのオーケストレーション
&lt;/h2>&lt;p>最新のWebアプリケーション開発では、Webサーバー、データベース、キャッシュサーバーなど、複数のコンポーネントが連携するマイクロサービスアーキテクチャが一般的です。ローカル環境でこれらを一元管理するために &lt;code>docker-compose.yml&lt;/code> を使用します。&lt;/p>
&lt;p>今回は、「Web (FastAPI)」「Database (PostgreSQL)」「Cache (Redis)」の3層構造のシステムをローカルに構築します。&lt;/p>
&lt;h3 id="アーキテクチャ図mermaid">アーキテクチャ図（Mermaid）
&lt;/h3>&lt;p>以下の図は、ローカルマシンにおける各コンテナ、ネットワーク、そしてボリュームの関係性を表したブロックダイアグラムです。&lt;/p>
&lt;pre class="mermaid">
graph TD
User[&amp;#34;Host Machine (Browser/curl)&amp;#34;] --&amp;gt;|Localhost:8000| Web[&amp;#34;FastAPI Web Container&amp;#34;]
subgraph &amp;#34;Docker Bridge Network (app-network)&amp;#34;
Web --&amp;gt;|Port 5432| DB[&amp;#34;PostgreSQL Container&amp;#34;]
Web --&amp;gt;|Port 6379| Redis[&amp;#34;Redis Container&amp;#34;]
end
DB --&amp;gt; Volume1[&amp;#34;Named Volume (postgres_data)&amp;#34;]
Redis --&amp;gt; Volume2[&amp;#34;Named Volume (redis_data)&amp;#34;]
HostDir[&amp;#34;Host Source Code (./src)&amp;#34;] -.-&amp;gt;|Bind Mount| Web
&lt;/pre>
&lt;h3 id="docker-composeyml-の実装と詳細解説">docker-compose.yml の実装と詳細解説
&lt;/h3>&lt;p>以下に、実践的な環境構築に耐えうる堅牢な &lt;code>docker-compose.yml&lt;/code> の例を示します。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;3.8&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">web&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">build&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">context&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">target&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">runtime&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dev_web&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;8000:8000&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./src:/app/src:ro &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># ホストのコードを読み取り専用でマウント（ホットリロード用）&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">REDIS_URL=redis://redis:6379/0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env_file&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">.env&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">depends_on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">db&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_healthy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">redis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_started&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">app-network&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;uvicorn&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;src.main:app&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--host&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;0.0.0.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--port&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;8000&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--reload&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">db&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres:15-alpine&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dev_db&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;5432:5432&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_USER&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_PASSWORD&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${POSTGRES_PASSWORD}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_DB&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${POSTGRES_DB}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">postgres_data:/var/lib/postgresql/data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">app-network&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">healthcheck&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">test&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;CMD-SHELL&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;pg_isready -U postgres -d ${POSTGRES_DB}&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">5s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timeout&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">5s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">retries&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">redis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">redis:7-alpine&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dev_redis&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;6379:6379&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">redis_data:/data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">app-network&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;redis-server&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--appendonly&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;yes&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">postgres_data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">redis_data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app-network&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">driver&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">bridge&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="ボリューム-volumes-とデータの永続化">ボリューム (Volumes) とデータの永続化
&lt;/h3>&lt;p>コンテナは原則として「ステートレス（状態を持たない）」かつ「エフェメラル（短命）」な存在です。コンテナを破棄すると、内部のデータも消失します。データベースのデータやキャッシュを保持するためには、ホストマシンのファイルシステム領域をコンテナにマウントする必要があります。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Bind Mount (バインドマウント)&lt;/strong>: 上記の &lt;code>web&lt;/code> サービスにおける &lt;code>./src:/app/src:ro&lt;/code> がこれに該当します。ホストの特定ディレクトリをコンテナ内に直接マッピングします。ローカルでのコード編集を即座にコンテナ（ホットリロード）に反映させるために使用します。セキュリティ上の観点から &lt;code>:ro&lt;/code> (Read-Only) オプションを付与し、コンテナ側からホストのソースコードを改変できないようにすることがベストプラクティスです。&lt;/li>
&lt;li>&lt;strong>Named Volume (名前付きボリューム)&lt;/strong>: &lt;code>postgres_data&lt;/code> や &lt;code>redis_data&lt;/code> が該当します。Dockerが内部的（&lt;code>/var/lib/docker/volumes/&lt;/code> など）に管理する領域で、バインドマウントよりもI/Oパフォーマンスに優れ、OS間のファイルシステムの差異を吸収してくれます。データベースの永続化には必ずこちらを使用します。&lt;/li>
&lt;/ul>
&lt;h3 id="ネットワーク-networking-とサービスディスカバリ">ネットワーク (Networking) とサービスディスカバリ
&lt;/h3>&lt;p>Docker Composeはデフォルトでプロジェクトごとに独自のブリッジネットワークを作成します。上記の &lt;code>app-network&lt;/code> です。
同じネットワークに属するコンテナ同士は、IPアドレスではなく「サービス名（例：&lt;code>db&lt;/code>, &lt;code>redis&lt;/code>）」をホスト名として名前解決（DNS解決）できます。
例えば、Webコンテナからは &lt;code>postgresql://postgres:password@db:5432/mydb&lt;/code> というURLでデータベースにアクセス可能です。これにより、ローカル環境でも本番環境でも、環境変数を通じて接続先を透過的に切り替えることができるようになります。&lt;/p>
&lt;h3 id="ヘルスチェックと起動順序の制御">ヘルスチェックと起動順序の制御
&lt;/h3>&lt;p>&lt;code>depends_on&lt;/code> ディレクティブはコンテナの起動順序を制御しますが、単に &lt;code>depends_on&lt;/code> を指定しただけでは「DBコンテナが起動した」段階でWebコンテナが起動してしまいます。実際にはDBの初期化プロセス（PostgreSQLのプロセス起動やテーブルの準備）が完了するまで数秒かかるため、WebコンテナからのDB接続がエラーになることがあります。
これを防ぐため、&lt;code>healthcheck&lt;/code> を定義し、&lt;code>condition: service_healthy&lt;/code> を指定することで、「DBが接続リクエストを受け付けられる状態になったこと」を確認してからWebコンテナを起動させることが可能です。&lt;/p>
&lt;hr>
&lt;h2 id="5-環境変数の管理とセキュリティ-env">5. 環境変数の管理とセキュリティ (.env)
&lt;/h2>&lt;p>データベースのパスワードやAPIキーなどの機密情報を &lt;code>docker-compose.yml&lt;/code> にハードコーディングすることは、絶対に避けるべきアンチパターンです。代わりに、環境変数ファイル &lt;code>.env&lt;/code> を使用してこれらの値を注入します。&lt;/p>
&lt;p>プロジェクトルートに &lt;code>.env&lt;/code> ファイルを作成します。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># .env ファイル (Gitの管理対象外にするため .gitignore に追加すること)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">POSTGRES_PASSWORD&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">supersecretpassword&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">POSTGRES_DB&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">devdb&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">API_SECRET_KEY&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">dev_secret_key_12345&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Docker Compose はデフォルトで実行ディレクトリにある &lt;code>.env&lt;/code> ファイルを読み込み、YAMLファイル内の &lt;code>${VAR_NAME}&lt;/code> というプレースホルダーを展開します。この手法により、ローカル、ステージング、本番といった環境ごとに異なる設定値を、インフラコードを変更することなく安全に管理することが可能になります。&lt;/p>
&lt;hr>
&lt;h2 id="6-vscode-devcontainers-による究極の開発体験">6. VSCode DevContainers による究極の開発体験
&lt;/h2>&lt;p>ここまでで、Dockerを使った堅牢なバックエンド環境が構築できました。しかし、もう一歩踏み込むことができます。&lt;strong>VSCode DevContainers (Remote - Containers)&lt;/strong> 機能を使用すると、エディタ（VSCode）自体のバックエンドをコンテナ内部で実行することが可能になります。&lt;/p>
&lt;p>これにより、ローカルマシンにはPythonやNode.jsすらインストールする必要がなくなり、Linter（flake8/eslint）やフォーマッター（black/prettier）、IDEの拡張機能に至るまで、すべてをコードベース内に定義してチーム全員で共有できます。&lt;/p>
&lt;h3 id="devcontainerjson-の設定">devcontainer.json の設定
&lt;/h3>&lt;p>プロジェクトルートに &lt;code>.devcontainer&lt;/code> ディレクトリを作成し、その中に構成ファイルを配置します。&lt;/p>
&lt;p>&lt;code>.devcontainer/devcontainer.json&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Python FastAPI Dev Environment&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;dockerComposeFile&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;../docker-compose.yml&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;service&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;web&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;workspaceFolder&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/app&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;customizations&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;vscode&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;settings&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;python.defaultInterpreterPath&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/app/.venv/bin/python&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;python.formatting.provider&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;black&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;editor.formatOnSave&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;extensions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ms-python.python&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ms-python.vscode-pylance&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ms-python.black-formatter&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;tamasfe.even-better-toml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;forwardPorts&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">8000&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5432&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">6379&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;remoteUser&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;appuser&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;postCreateCommand&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;poetry install&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>このファイルをリポジトリに含めておくことで、VSCodeでプロジェクトを開いた瞬間に「Reopen in Container」というプロンプトが表示され、クリックするだけで必要なすべてのコンテナが立ち上がり、拡張機能がインストールされ、即座にコーディングを開始できる状態になります。まさに魔法のような体験です。&lt;/p>
&lt;hr>
&lt;h2 id="7-リクエスト処理のシーケンスとパフォーマンスモデリング">7. リクエスト処理のシーケンスとパフォーマンスモデリング
&lt;/h2>&lt;p>構築したローカル開発環境における、Webアプリケーションのリクエスト処理のライフサイクルをシーケンス図で確認し、そのパフォーマンスの数理モデルを考察します。&lt;/p>
&lt;h3 id="シーケンス図リクエストフロー">シーケンス図（リクエストフロー）
&lt;/h3>&lt;pre class="mermaid">
sequenceDiagram
participant Client as &amp;#34;Browser / VSCode&amp;#34;
participant Web as &amp;#34;FastAPI (Web)&amp;#34;
participant Redis as &amp;#34;Redis Cache&amp;#34;
participant DB as &amp;#34;PostgreSQL&amp;#34;
Client-&amp;gt;&amp;gt;Web: &amp;#34;GET /api/users/123&amp;#34;
activate Web
Web-&amp;gt;&amp;gt;Redis: &amp;#34;Check Cache for user:123&amp;#34;
activate Redis
alt &amp;#34;Cache Hit (データあり)&amp;#34;
Redis--&amp;gt;&amp;gt;Web: &amp;#34;Return Cached User Data&amp;#34;
Web--&amp;gt;&amp;gt;Client: &amp;#34;200 OK (Fast Response)&amp;#34;
else &amp;#34;Cache Miss (データなし)&amp;#34;
Redis--&amp;gt;&amp;gt;Web: &amp;#34;Null (Not Found)&amp;#34;
deactivate Redis
Web-&amp;gt;&amp;gt;DB: &amp;#34;SELECT * FROM users WHERE id = 123&amp;#34;
activate DB
DB--&amp;gt;&amp;gt;Web: &amp;#34;Return Database Row&amp;#34;
deactivate DB
Web-&amp;gt;&amp;gt;Redis: &amp;#34;SET user:123 Data (TTL: 60s)&amp;#34;
activate Redis
Redis--&amp;gt;&amp;gt;Web: &amp;#34;OK&amp;#34;
deactivate Redis
Web--&amp;gt;&amp;gt;Client: &amp;#34;200 OK (Standard Response)&amp;#34;
end
deactivate Web
&lt;/pre>
&lt;h3 id="処理遅延レイテンシの数理モデル">処理遅延（レイテンシ）の数理モデル
&lt;/h3>&lt;p>上記システムにおける平均リクエスト処理時間 $T_{\text{total}}$ を数理的にモデル化します。
各処理のレイテンシを以下のように定義します。&lt;/p>
&lt;ul>
&lt;li>$T_{\text{net}}$: クライアントとWebコンテナ間のネットワークレイテンシ&lt;/li>
&lt;li>$T_{\text{app}}$: アプリケーション側の純粋な処理時間（シリアライズ等）&lt;/li>
&lt;li>$T_{\text{cache}}$: Redisからの読み書きにかかる時間&lt;/li>
&lt;li>$T_{\text{db}}$: PostgreSQLへのクエリ実行にかかる時間&lt;/li>
&lt;li>$p_{\text{miss}}$: キャッシュミス率（$0 \le p_{\text{miss}} \le 1$）&lt;/li>
&lt;/ul>
&lt;p>このとき、平均的なレスポンスタイムは以下の期待値計算式で表されます。&lt;/p>
$$ T_{\text{total}} = T_{\text{net}} + T_{\text{app}} + T_{\text{cache}} + p_{\text{miss}} \times (T_{\text{db}} + T_{\text{cache\_write}}) $$&lt;p>ローカル開発環境（Docker内）では、$T_{\text{net}}$ はほぼ 0 に近くなりますが、注目すべきは&lt;strong>バインドマウント時のI/Oパフォーマンス&lt;/strong>です。特にWindows/macOS上でDocker Desktopを使用している場合、ホストOSとVM（コンテナ）間のファイル共有オーバーヘッドにより、$T_{\text{app}}$（コードの読み込み時間等）が肥大化する傾向があります。このボトルネックを解消するために、前述の DevContainers を利用してソースコード全体を名前付きボリューム内に配置するか、WSL2（Windows Subsystem for Linux 2）環境ネイティブでDockerエンジンを動作させるアーキテクチャが強く推奨されます。&lt;/p>
&lt;hr>
&lt;h2 id="8-dockerビルドのパフォーマンス最適化レイヤーキャッシュ戦略">8. Dockerビルドのパフォーマンス最適化：レイヤーキャッシュ戦略
&lt;/h2>&lt;p>Dockerfileを記述する際、「レイヤーキャッシュ」の仕組みを理解しているかどうかで、ビルド時間は劇的に変化します。
Dockerは、Dockerfileの各命令（&lt;code>FROM&lt;/code>, &lt;code>RUN&lt;/code>, &lt;code>COPY&lt;/code> など）ごとにファイルシステムの差分（レイヤー）を作成し、キャッシュとして保持します。再ビルド時には、変更がないレイヤーのキャッシュが再利用されます。&lt;/p>
&lt;p>重要な原則は、**「変更頻度の低いものから順番に記述する」**ことです。&lt;/p>
&lt;p>ソースコードの変更がビルド時間に与える影響のモデル化を考えます。総ビルド時間を $T_{\text{build}}$、各ステップの実行時間を $T_{\text{layer}_i}$、キャッシュヒットの有無をブール値 $c_i \in \{0, 1\}$ （キャッシュヒット時に 1）とします。&lt;/p>
$$ T_{\text{build}} = T_{\text{init}} + \sum_{i=1}^{n} (1 - c_i) \times T_{\text{layer}_i} $$&lt;p>一度レイヤー $k$ でキャッシュミス（$c_k = 0$）が発生すると、それ以降のすべてのレイヤー $j > k$ においてキャッシュが無効化（$c_j = 0$）されます。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 悪い例 (ソースコードを先にコピーしている)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> ./src /app/src&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> pyproject.toml poetry.lock ./&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> poetry install&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上記の場合、コードを一行修正しただけで最初の &lt;code>COPY&lt;/code> がキャッシュミスとなり、時間のかかる &lt;code>RUN poetry install&lt;/code> が毎回実行されてしまいます。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 良い例 (依存関係の解決を先に行う)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> pyproject.toml poetry.lock ./&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> poetry install&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> ./src /app/src&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>このように記述すれば、ソースコードを変更しても &lt;code>poetry install&lt;/code> のレイヤーキャッシュ ($c_i = 1$) が効くため、ビルド時間は数分から数秒へと劇的に短縮されます。&lt;/p>
&lt;hr>
&lt;h2 id="9-トラブルシューティングとtips">9. トラブルシューティングとTips
&lt;/h2>&lt;p>ローカル環境運用時によく遭遇する問題と解決策を挙げます。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>ポート競合エラー&lt;/strong>
&lt;code>Bind for 0.0.0.0:8000 failed: port is already allocated&lt;/code> のようなエラーが出た場合、ローカルマシン上で別のプロセスがそのポートを使用しています。ホスト側のポート番号を &lt;code>ports: - &amp;quot;8080:8000&amp;quot;&lt;/code> のように変更することで回避できます。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>ディスク容量の枯渇&lt;/strong>
長期間Dockerを使用していると、使われていないイメージやボリューム（Dangling Images / Volumes）が蓄積され、数十GBのディスク領域を圧迫することがあります。定期的に以下のコマンドでシステムをクリーンアップすることが推奨されます。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker system prune -a --volumes
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>ファイルのパーミッション問題&lt;/strong>
Linux環境でバインドマウントを使用する場合、コンテナ内で作成されたファイルの所有者が &lt;code>root&lt;/code> になり、ホスト側で編集できなくなることがあります。Dockerfile内で非特権ユーザーを作成し、ホストOSの自身のUID/GID（例：1000:1000）と一致させることでこの問題を解決できます。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="10-おわりに再現可能性がもたらす開発速度の向上">10. おわりに：再現可能性がもたらす開発速度の向上
&lt;/h2>&lt;p>Docker、Docker Compose、そしてVSCode DevContainersを組み合わせることで、「誰が環境を立ち上げても完全に同じ状態になる」堅牢なローカル開発環境が実現します。&lt;/p>
&lt;p>IaCのパラダイムをローカル環境に持ち込むことは、単に最初のセットアップ時間を短縮するだけではありません。インフラストラクチャの設定変更に対する不安を取り除き、新しい技術スタックの実験を容易にし、CI/CDパイプラインへのスムーズな移行を可能にするなど、開発サイクル全体の速度と品質を飛躍的に向上させます。&lt;/p>
&lt;p>本記事で解説したマルチステージビルドによるイメージサイズの最適化や、ヘルスチェックを用いた依存関係の制御、レイヤーキャッシュを意識したDockerfileの記述などのベストプラクティスを活用し、ぜひご自身のプロジェクトにも最高の開発体験（DX: Developer Experience）を導入してみてください。&lt;/p></description></item></channel></rss>