<?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/en/categories/infrastructure/</link><description>Recent content in Infrastructure on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>kenjinote</copyright><lastBuildDate>Sun, 13 Sep 2026 07:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/en/categories/infrastructure/index.xml" rel="self" type="application/rss+xml"/><item><title>Hyper-V vs WSL2: Comparing Virtualization Technologies on Windows</title><link>http://kenji.blog/en/p/hyper-v-vs-wsl2-windows-virtualization/</link><pubDate>Sun, 13 Sep 2026 07:00:00 +0900</pubDate><guid>http://kenji.blog/en/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: Comparing Virtualization Technologies on Windows" />&lt;h2 id="1-introduction-the-evolution-of-virtualization-on-windows">1. Introduction: The Evolution of Virtualization on Windows
&lt;/h2>&lt;p>Virtualization technology on the Windows platform has evolved dramatically over the past few decades. In the past, third-party Type 2 hypervisors (such as VMware Workstation and VirtualBox) were the mainstream. However, since Microsoft introduced &amp;ldquo;Hyper-V&amp;rdquo; in Windows Server 2008, Type 1 hypervisors have also been integrated into desktop OSs like Windows 10/11.&lt;/p>
&lt;p>In recent years, the technology that has garnered the most attention among developers is &amp;ldquo;WSL2 (Windows Subsystem for Linux 2)&amp;rdquo;. While WSL1 relied on system call translation, WSL2 employs a &amp;ldquo;Lightweight Utility VM&amp;rdquo; based on Hyper-V technology, achieving full Linux compatibility and a dramatic leap in performance.&lt;/p>
&lt;p>In this article, we will thoroughly compare and explain the architecture, performance (CPU, memory, disk I/O), network configuration, and optimal use cases of these two powerful virtualization technologies—the full-featured &amp;ldquo;Hyper-V&amp;rdquo; and the developer-experience-focused &amp;ldquo;WSL2&amp;rdquo;—along with deep technical details.&lt;/p>
&lt;hr>
&lt;h2 id="2-basic-theory-of-hypervisors-and-architecture-comparison">2. Basic Theory of Hypervisors and Architecture Comparison
&lt;/h2>&lt;p>To understand virtualization technologies, classifying hypervisor (Virtual Machine Monitor: VMM) types is essential.&lt;/p>
&lt;h3 id="21-differences-between-type-1-and-type-2-hypervisors">2.1. Differences between Type 1 and Type 2 Hypervisors
&lt;/h3>&lt;p>A hypervisor is a software layer that abstracts hardware access, allowing multiple OSs (guest OSs) to run simultaneously on a single physical machine.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Type 1 (Bare-metal)&lt;/strong>: Runs directly on the hardware. There is no concept of a host OS (strictly speaking, a privileged management OS may exist), offering extremely low overhead, high performance, and high security. Examples: Hyper-V, VMware ESXi, Xen.&lt;/li>
&lt;li>&lt;strong>Type 2 (Hosted)&lt;/strong>: Runs as an application on a host OS (such as Windows or macOS). Since all hardware access goes through the host OS, the overhead is larger. Examples: VMware Workstation, Oracle VirtualBox.&lt;/li>
&lt;/ul>
&lt;p>Windows Hyper-V is a pure &lt;strong>Type 1 hypervisor&lt;/strong>. When Hyper-V is enabled, the Windows OS that the user normally operates actually starts running inside a special virtual machine called the &amp;ldquo;Root Partition&amp;rdquo;.&lt;/p>
&lt;h3 id="22-hyper-v-architecture-details">2.2. Hyper-V Architecture Details
&lt;/h3>&lt;p>The Hyper-V architecture adopts a microkernel design and is based on logical isolation units called Partitions.&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>: Runs at the most privileged level of the CPU (Ring -1 or VMX Root Mode) and is responsible only for memory allocation and CPU scheduling. It does not contain device drivers.&lt;/li>
&lt;li>&lt;strong>Root Partition&lt;/strong>: The partition where the host Windows OS runs. It possesses all device drivers and controls the hardware directly. It also provides management functions for child partitions (such as WMI providers and VMWP.exe).&lt;/li>
&lt;li>&lt;strong>Child Partition&lt;/strong>: The partition where a guest OS runs. Direct access to hardware is not permitted, and it sends I/O requests (Synthetic I/O) to the root partition via a logical memory sharing bus called &amp;ldquo;VMBus&amp;rdquo;.&lt;/li>
&lt;/ul>
&lt;h3 id="23-mechanism-of-wsl2-and-lightweight-utility-vm">2.3. Mechanism of WSL2 and Lightweight Utility VM
&lt;/h3>&lt;p>Although WSL2 utilizes the same underlying Type 1 hypervisor technology as Hyper-V, it uses a subset of features called the &amp;ldquo;Virtual Machine Platform (VMP)&amp;rdquo; which differs from full-featured Hyper-V virtual machines.&lt;/p>
&lt;p>The &amp;ldquo;Lightweight Utility VM&amp;rdquo; adopted in WSL2 completely eliminates the emulation of legacy hardware (such as virtual BIOS or virtual motherboards) present in traditional VMs.&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;| Cross-OS File Sharing | D
D --&amp;gt; E
D --&amp;gt; F
&lt;/pre>
&lt;p>The greatest features of WSL2 are its &lt;strong>fast startup&lt;/strong> and &lt;strong>seamless integration with the host OS&lt;/strong>. The Linux kernel boots in less than a second, and it accesses the Windows file system (NTFS) via Plan 9&amp;rsquo;s &lt;code>9P&lt;/code> network file system protocol.&lt;/p>
&lt;hr>
&lt;h2 id="3-thorough-performance-analysis-computational-resources-and-io">3. Thorough Performance Analysis: Computational Resources and I/O
&lt;/h2>&lt;p>Virtual machine performance is expressed as the sum of overheads across CPU, memory, and disk I/O components.&lt;/p>
&lt;h3 id="31-cpu-and-context-switch-overhead">3.1. CPU and Context Switch Overhead
&lt;/h3>&lt;p>Both Hyper-V and WSL2 use hardware-assisted virtualization (Intel VT-x / AMD-V). CPU instructions are basically executed at native speed, but when privileged instructions are executed or I/O operations occur, an interrupt called &amp;ldquo;VM Exit&amp;rdquo; is triggered, resulting in a context switch to the hypervisor.&lt;/p>
&lt;p>The CPU overhead $T_{overhead}$ at this time can be expressed by the following mathematical model:&lt;/p>
$$ T_{overhead} = \sum_{i=1}^{N} (t_{vm\_exit} + t_{hypercall\_process} + t_{vm\_entry}) $$&lt;p>Where:&lt;/p>
&lt;ul>
&lt;li>$N$: Number of VM Exits occurring per unit time&lt;/li>
&lt;li>$t_{vm\_exit}$: Transition time from the guest to the hypervisor&lt;/li>
&lt;li>$t_{hypercall\_process}$: Processing time for I/O operations or interrupts via VMBus&lt;/li>
&lt;li>$t_{vm\_entry}$: Return time from the hypervisor to the guest&lt;/li>
&lt;/ul>
&lt;p>Because WSL2 lacks legacy emulation, $t_{hypercall\_process}$ is heavily optimized and extremely small. Therefore, for pure CPU operations (such as kernel compilation or machine learning model inference), the performance degradation remains within a few percent compared to a bare-metal environment.&lt;/p>
&lt;h3 id="32-memory-allocation-mechanisms">3.2. Memory Allocation Mechanisms
&lt;/h3>&lt;p>There are distinct differences in design philosophy between the two regarding memory management approaches.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Hyper-V (Dynamic Memory)&lt;/strong>: The root partition dynamically allocates and reclaims memory according to the memory demands of the guest VM. However, memory secured as page cache within the guest OS tends not to be released unless the system is under pressure.&lt;/li>
&lt;li>&lt;strong>WSL2 (Dynamic Memory Reclaim)&lt;/strong>: WSL2 has its own mechanism to periodically return (reclaim) memory—including caches—that is no longer needed inside the Linux VM back to the Windows host. While early WSL2 versions had an issue where Linux page caches exhausted Windows memory (bloat of the Vmmem process), this has now been improved by kernel patches.&lt;/li>
&lt;/ul>
&lt;h3 id="33-disk-io-characteristics-vhdx-vs-ext4vhdx">3.3. Disk I/O Characteristics (VHDX vs ext4.vhdx)
&lt;/h3>&lt;p>Disk I/O is the component most likely to become a bottleneck in virtual machine performance.&lt;/p>
&lt;p>The I/O latency $L_{total}$ is calculated as follows:&lt;/p>
$$ L_{total} = L_{guest\_fs} + L_{vmbus} + L_{host\_fs} + L_{physical\_disk} $$&lt;p>&lt;strong>In the case of Hyper-V&lt;/strong>:
A typical Hyper-V guest uses a virtual disk in the &lt;code>VHDX&lt;/code> format. I/O requests issued from the file system (ext4 or NTFS) within the guest OS pass through the VMBus block device storage driver (storvsc) and are processed as accesses to the VHDX file on NTFS on the Windows side.&lt;/p>
&lt;p>&lt;strong>In the case of WSL2&lt;/strong>:
WSL2&amp;rsquo;s Linux distributions run on a native ext4 file system built within a dedicated &lt;code>ext4.vhdx&lt;/code> file. File operations inside Linux (e.g., within the &lt;code>~&lt;/code> directory) demonstrate native performance equivalent to the aforementioned Hyper-V.
However, &lt;strong>when accessing files on the Windows side (such as &lt;code>/mnt/c/&lt;/code>) from WSL2&amp;rsquo;s Linux&lt;/strong>, or vice versa, the processing differs significantly. The &lt;code>9P (Plan 9 File System Protocol)&lt;/code> is used for this cross-OS access.&lt;/p>
$$ L_{cross\_os} = L_{9p\_client} + L_{socket\_transfer} + L_{9p\_server} + L_{ntfs} $$&lt;p>Access via this 9P protocol involves significant serialization processing overhead. In scenarios involving massive read/write operations of small files (e.g., &lt;code>npm install&lt;/code> or Git operations in a Node.js project located in a Windows directory), performance drops significantly (sometimes with more than 10 times the delay).
Therefore, &lt;strong>when using WSL2, the golden rule is to always place project files on the Linux native file system (under &lt;code>~/&lt;/code>)&lt;/strong>.&lt;/p>
&lt;hr>
&lt;h2 id="4-network-structure-nat-default-switch-bridged">4. Network Structure: NAT, Default Switch, Bridged
&lt;/h2>&lt;p>Network flexibility is one of the major differences between Hyper-V and WSL2.&lt;/p>
&lt;h3 id="41-wsl2-network-nat-based">4.1. WSL2 Network (NAT-based)
&lt;/h3>&lt;p>By default, the WSL2 network is configured with &amp;ldquo;NAT (Network Address Translation)&amp;rdquo; using Hyper-V&amp;rsquo;s virtual switch technology.
The Linux VM is automatically assigned a private IP address (e.g., &lt;code>172.20.x.x&lt;/code>) different from the Windows host. A mechanism is built-in where access to &lt;code>localhost&lt;/code> from the Windows host is forwarded to services (ports) running inside WSL2, allowing developers to test web servers without having to be mindful of the network.&lt;/p>
&lt;p>Recently, a new network mode called &amp;ldquo;Mirrored mode&amp;rdquo; was introduced in preview versions of WSL2. This aims to improve IPv6 support and VPN connection compatibility (configurable via &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 enables advanced, enterprise-level network construction. Through the &amp;ldquo;Virtual Switch Manager&amp;rdquo;, it mainly provides three modes:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>External&lt;/strong>: Binds the host machine&amp;rsquo;s physical NIC to the virtual switch, allowing the guest VM to directly join the physical network (bridge connection). The VM obtains an IP from the same subnet as the physical network via a DHCP server.&lt;/li>
&lt;li>&lt;strong>Internal&lt;/strong>: Only allows communication between the host OS and VMs, as well as between VMs. Direct access to external networks is not possible.&lt;/li>
&lt;li>&lt;strong>Private&lt;/strong>: Only allows communication between VMs, cutting off communication with the host OS. Used for building isolated testing environments.&lt;/li>
&lt;/ol>
&lt;h3 id="43-advanced-hyper-v-network-construction-using-powershell">4.3. Advanced Hyper-V Network Construction using PowerShell
&lt;/h3>&lt;p>In development or testing environments, when you want to build a customized NAT network for VMs, PowerShell allows for detailed control. Below is an example script to create an internal virtual switch, configure NAT on it, and provide internet access to a 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. Create Internal Virtual Switch&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. Set IP address to the virtual NIC on the host side (IP that acts as a gateway)&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. Configure NAT Network&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"># Verification command&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>With this configuration, by manually setting an IP of &lt;code>192.168.100.x&lt;/code> and a gateway of &lt;code>192.168.100.1&lt;/code> on a designated Hyper-V guest, you can build a custom NAT segment that can communicate externally via the host.&lt;/p>
&lt;hr>
&lt;h2 id="5-use-cases-and-practical-selection-guide">5. Use Cases and Practical Selection Guide
&lt;/h2>&lt;p>Based on the differences in architecture and performance discussed so far, we define under what circumstances which technology should be adopted.&lt;/p>
&lt;h3 id="51-scenarios-for-choosing-wsl2">5.1. Scenarios for Choosing WSL2
&lt;/h3>&lt;p>WSL2 is specifically designed to &amp;ldquo;improve developer productivity.&amp;rdquo; It is optimal for the following uses:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Web Development and Cloud-Native Development&lt;/strong>: Container development using Docker Desktop (WSL2 backend) or Podman.&lt;/li>
&lt;li>&lt;strong>Using Linux-specific Tools&lt;/strong>: When routinely using bash, grep, awk, sed, or GCC/Clang compilers meant for Linux.&lt;/li>
&lt;li>&lt;strong>GUI Applications (WSLg)&lt;/strong>: When you want to run Linux X11/Wayland applications seamlessly on the Windows desktop.&lt;/li>
&lt;li>&lt;strong>Machine Learning and AI Development&lt;/strong>: High-speed training with TensorFlow or PyTorch using GPU passthrough capabilities (NVIDIA CUDA on WSL).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Note&lt;/strong>: You may face restrictions if you wish to deeply customize the kernel, or build complex services heavily dependent on systemd (systemd is currently supported, but is disabled or restricted by default).&lt;/p>
&lt;h3 id="52-scenarios-for-choosing-hyper-v">5.2. Scenarios for Choosing Hyper-V
&lt;/h3>&lt;p>Hyper-V is intended for &amp;ldquo;infrastructure virtualization and complete isolation.&amp;rdquo; It is essential for the following uses:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Running Windows VMs&lt;/strong>: When running different versions of Windows (such as Windows Server or older Windows 10) as test environments.&lt;/li>
&lt;li>&lt;strong>Nested Virtualization&lt;/strong>: When you want to run a virtual machine (Hyper-V or KVM) inside another virtual machine. Indispensable for infrastructure engineers&amp;rsquo; validation environments.&lt;/li>
&lt;li>&lt;strong>Advanced Network Requirements&lt;/strong>: When network configurations must be strictly controlled, such as external bridge connections (joining the same LAN), VLAN tagging, or allocating multiple NICs.&lt;/li>
&lt;li>&lt;strong>Snapshots (Checkpoints)&lt;/strong>: The ability to save a VM&amp;rsquo;s state at a specific point in time and instantly roll back to it whenever needed. Extremely useful for destructive software testing or malware analysis.&lt;/li>
&lt;li>&lt;strong>Fixed Resource Allocation&lt;/strong>: When you want to strictly fix the number of CPU cores and memory amount to minimize the impact on the host OS.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="6-consideration-of-io-throughput-through-mathematical-models-appendix">6. Consideration of I/O Throughput through Mathematical Models (Appendix)
&lt;/h2>&lt;p>As a system engineer, when determining the I/O performance limits of both, it is crucial to theoretically understand the relationship between throughput $S$ and block size $B$.&lt;/p>
&lt;p>Data transfer throughput $S$ is the amount of data transferred per unit time, and is modeled as follows:&lt;/p>
$$ S(B) = \frac{B}{L_{setup} + \frac{B}{R_{max}}} $$&lt;ul>
&lt;li>$B$: Block size (Bytes)&lt;/li>
&lt;li>$L_{setup}$: Fixed latency associated with I/O request setup and context switching&lt;/li>
&lt;li>$R_{max}$: Hardware&amp;rsquo;s maximum bandwidth for copying and device transfers&lt;/li>
&lt;/ul>
&lt;p>In file accesses via WSL2&amp;rsquo;s 9P protocol, this $L_{setup}$ becomes extremely large (due to socket communication and protocol serialization/deserialization). Therefore, when the block size $B$ is small (massive read/write of fine files around a few KBs), the effect of $L_{setup}$ in the denominator becomes dominant, and the throughput $S$ degrades dramatically.
Conversely, in VHDX access via Hyper-V&amp;rsquo;s VMBus, $L_{setup}$ is optimized to a level close to hardware interrupts, allowing it to maintain high IOPS even with small blocks.&lt;/p>
&lt;p>This mathematical reality serves as the logical foundation for the best practice that &amp;ldquo;you must not place project files on the Windows side in WSL2&amp;rdquo;.&lt;/p>
&lt;hr>
&lt;h2 id="7-conclusion-two-coexisting-virtualization-technologies">7. Conclusion: Two Coexisting Virtualization Technologies
&lt;/h2>&lt;p>Hyper-V and WSL2 are not a matter of one being superior to the other; they are &lt;strong>&amp;ldquo;two solutions with different purposes&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>WSL2&lt;/strong> is the &amp;ldquo;best integration tool&amp;rdquo; that breaks the shell of the Windows OS to seamlessly and swiftly deliver the Linux ecosystem to Windows users. It is no exaggeration to call it the ultimate CLI environment for developers.&lt;/li>
&lt;li>&lt;strong>Hyper-V&lt;/strong> is a &amp;ldquo;full-fledged hypervisor&amp;rdquo; that brings the robust isolation and management capabilities cultivated in enterprise data centers to the desktop. It is second to none in network construction, Windows OS testing, and infrastructure environment simulation.&lt;/li>
&lt;/ul>
&lt;p>In modern Windows environments, these two technologies do not compete on equal terms; they beautifully coexist on the same VM platform. By using the right tool for the right job depending on the purpose, Windows can truly become the most powerful and flexible engineering workstation in the world.&lt;/p></description></item><item><title>Steps to Build a Reproducible Local Development Environment Using Docker</title><link>http://kenji.blog/en/p/docker-reproducible-local-dev-environment/</link><pubDate>Sun, 13 Sep 2026 01:00:00 +0900</pubDate><guid>http://kenji.blog/en/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 Steps to Build a Reproducible Local Development Environment Using Docker" />&lt;h2 id="1-introduction-breaking-free-from-it-works-on-my-machine">1. Introduction: Breaking Free from &amp;ldquo;It Works on My Machine&amp;rdquo;
&lt;/h2>&lt;p>In the field of software development, the &amp;ldquo;It works on my machine&amp;rdquo; problem, caused by differences in developers&amp;rsquo; environments, has long been a factor in wasting time on many projects. Local environments are constantly exposed to &amp;ldquo;state uncertainty,&amp;rdquo; such as OS differences, installed language versions, library dependencies, and conflicts between globally installed tools.&lt;/p>
&lt;p>What fundamentally solves these issues is container technology like &lt;strong>Docker&lt;/strong> and the &lt;strong>Infrastructure as Code (IaC)&lt;/strong> paradigm. By containerizing the local development environment, OS-level isolation is achieved, and the environment itself can be version-controlled alongside the codebase.&lt;/p>
&lt;p>In this article, we will thoroughly explain the steps to build a &lt;strong>&amp;ldquo;reproducible local development environment that results in the exact same state, no matter who, when, or on what machine it is launched,&amp;rdquo;&lt;/strong> by leveraging Docker, Docker Compose, and VSCode DevContainers. We will also explore the deep technical mechanisms behind it from a mathematical perspective.&lt;/p>
&lt;hr>
&lt;h2 id="2-the-synergy-between-infrastructure-as-code-iac-and-container-technology">2. The Synergy Between Infrastructure as Code (IaC) and Container Technology
&lt;/h2>&lt;h3 id="iac-principles-and-application-to-local-environments">IaC Principles and Application to Local Environments
&lt;/h3>&lt;p>Infrastructure as Code (IaC) is an approach to managing infrastructure configuration and provisioning through machine-readable definition files rather than manual processes. The core principles of IaC include the following elements:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Declarative Approach&lt;/strong>: Defines &amp;ldquo;what the final state should be&amp;rdquo; rather than &amp;ldquo;how to change the state.&amp;rdquo;&lt;/li>
&lt;li>&lt;strong>Idempotency&lt;/strong>: Guarantees the exact same result (state) no matter how many times the script is executed.&lt;/li>
&lt;li>&lt;strong>Version Control&lt;/strong>: The infrastructure state is stored as code in a VCS like Git, enabling change history tracking and peer reviews.&lt;/li>
&lt;/ol>
&lt;p>Practicing IaC in a local development environment means codifying the &amp;ldquo;ideal state&amp;rdquo; of the development environment using &lt;code>Dockerfile&lt;/code>, &lt;code>docker-compose.yml&lt;/code>, and &lt;code>devcontainer.json&lt;/code>. This provides an onboarding experience where new team members can clone the repository and start developing immediately by running a single command.&lt;/p>
&lt;h3 id="kernel-features-supporting-container-technology">Kernel Features Supporting Container Technology
&lt;/h3>&lt;p>Unlike hypervisor-based virtualization like virtual machines (VMs), container technology is a lightweight virtualization technique that isolates processes while sharing the host OS kernel. To achieve this, it primarily relies on the following Linux kernel features:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Namespaces&lt;/strong>: Provides independent views of system resources (PID, network, mount points, users, etc.) for each process.&lt;/li>
&lt;li>&lt;strong>Cgroups (Control Groups)&lt;/strong>: Limits and allocates the physical resources (CPU, memory, disk I/O, etc.) that processes can use.&lt;/li>
&lt;li>&lt;strong>UnionFS (Union File System)&lt;/strong>: A technology that transparently overlays multiple directory trees (layers) to present them as a single file system. Docker&amp;rsquo;s image layers rely on this technology.&lt;/li>
&lt;/ul>
&lt;p>Let&amp;rsquo;s consider a mathematical model of resource limitation. Let the total memory capacity of the host machine be $M_{\text{total}}$, and the memory limit of $n$ containers running on the host be $m_i$. Taking into account the base memory $M_{\text{os}}$ consumed by the host OS and other processes, the necessary condition for the system to operate stably can be expressed by the following inequality:&lt;/p>
$$ \sum_{i=1}^{n} m_i \le M_{\text{total}} - M_{\text{os}} $$&lt;p>By strictly defining $m_i$ for each container using Cgroups, even if a specific container causes a memory leak, the OOM (Out Of Memory) Killer can prevent other containers or the entire host system from going down.&lt;/p>
&lt;hr>
&lt;h2 id="3-efficient-dockerfile-design-mastering-multi-stage-builds">3. Efficient Dockerfile Design: Mastering Multi-Stage Builds
&lt;/h2>&lt;p>The first step to a reproducible environment is designing the &lt;code>Dockerfile&lt;/code> that defines the application&amp;rsquo;s runtime environment. Here, using Python (FastAPI) as an example, we will explain the best practices for a secure and lightweight Dockerfile leveraging &lt;strong>multi-stage builds&lt;/strong>.&lt;/p>
&lt;p>A multi-stage build is a technique that uses multiple &lt;code>FROM&lt;/code> instructions within a single &lt;code>Dockerfile&lt;/code> to separate the build environment (a heavy environment containing compilers and development tools) from the runtime environment (a lightweight environment holding only the necessary artifacts).&lt;/p>
&lt;h3 id="practical-python-fastapi-dockerfile">Practical Python FastAPI Dockerfile
&lt;/h3>&lt;p>The following code is an example of an advanced &lt;code>Dockerfile&lt;/code> that combines dependency management using Poetry and multi-stage builds.&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 (Build Environment)&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"># Set necessary environment variables&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"># Install dependencies&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"># Copy dependency files and install&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 (Execution Environment)&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"># Create a minimal non-root user&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"># Copy only the virtual environment (dependencies) from the 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="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"># Copy application code&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"># Switch to the non-root user&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"># Default command upon container startup&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="mathematical-evaluation-of-image-size-via-multi-stage-builds">Mathematical Evaluation of Image Size via Multi-Stage Builds
&lt;/h3>&lt;p>Let the image size when built with a single stage be $S_{\text{single}}$, and the image size when a multi-stage build is applied be $S_{\text{multi}}$. The size reduction rate $R$ is calculated as follows:&lt;/p>
$$ R = \left( 1 - \frac{S_{\text{multi}}}{S_{\text{single}}} \right) \times 100 \ (\%) $$&lt;p>For example, suppose $S_{\text{single}}$ includes the OS base image (approx. 110MB), development packages (like gcc, approx. 150MB), Poetry itself (approx. 40MB), project dependency libraries (approx. 80MB), and source code (approx. 5MB), totaling 385MB.
On the other hand, in $S_{\text{multi}}$, only the dependency libraries (80MB) and source code (5MB) are copied into the base image (110MB), resulting in a total of 195MB.&lt;/p>
$$ R = \left( 1 - \frac{195}{385} \right) \times 100 \approx 49.35\% $$&lt;p>In this way, introducing multi-stage builds can reduce the image size by about half. Reducing the image size directly leads to improved security by shortening pull times from the registry, saving disk space, and shrinking the attack surface.&lt;/p>
&lt;hr>
&lt;h2 id="4-orchestrating-multiple-containers-with-docker-compose">4. Orchestrating Multiple Containers with Docker Compose
&lt;/h2>&lt;p>In modern web application development, a microservices architecture where multiple components like web servers, databases, and cache servers collaborate is common. We use &lt;code>docker-compose.yml&lt;/code> to centrally manage these in a local environment.&lt;/p>
&lt;p>Here, we will build a 3-tier system locally consisting of &amp;ldquo;Web (FastAPI),&amp;rdquo; &amp;ldquo;Database (PostgreSQL),&amp;rdquo; and &amp;ldquo;Cache (Redis).&amp;rdquo;&lt;/p>
&lt;h3 id="architecture-diagram-mermaid">Architecture Diagram (Mermaid)
&lt;/h3>&lt;p>The following diagram is a block diagram illustrating the relationships among each container, network, and volume on the local machine.&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="implementation-and-detailed-explanation-of-docker-composeyml">Implementation and Detailed Explanation of docker-compose.yml
&lt;/h3>&lt;p>Below is an example of a robust &lt;code>docker-compose.yml&lt;/code> that can withstand practical environment construction.&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"># Mount the host code as read-only (for hot reloading)&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-and-data-persistence">Volumes and Data Persistence
&lt;/h3>&lt;p>Containers are generally &amp;ldquo;stateless&amp;rdquo; and &amp;ldquo;ephemeral&amp;rdquo; entities. When a container is destroyed, the data inside it is also lost. To retain database data and caches, it is necessary to mount an area of the host machine&amp;rsquo;s file system into the container.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Bind Mount&lt;/strong>: This corresponds to &lt;code>./src:/app/src:ro&lt;/code> in the &lt;code>web&lt;/code> service above. It directly maps a specific directory on the host into the container. This is used to immediately reflect local code edits in the container (hot reloading). For security reasons, it is a best practice to add the &lt;code>:ro&lt;/code> (Read-Only) option to prevent the container from altering the host&amp;rsquo;s source code.&lt;/li>
&lt;li>&lt;strong>Named Volume&lt;/strong>: This corresponds to &lt;code>postgres_data&lt;/code> and &lt;code>redis_data&lt;/code>. This is an area internally managed by Docker (like &lt;code>/var/lib/docker/volumes/&lt;/code>), which offers better I/O performance than bind mounts and abstracts the differences in file systems across OSes. Be sure to use this for database persistence.&lt;/li>
&lt;/ul>
&lt;h3 id="networking-and-service-discovery">Networking and Service Discovery
&lt;/h3>&lt;p>Docker Compose creates a unique bridge network for each project by default. This is the &lt;code>app-network&lt;/code> mentioned above.
Containers belonging to the same network can resolve names (DNS resolution) using the &amp;ldquo;service name&amp;rdquo; (e.g., &lt;code>db&lt;/code>, &lt;code>redis&lt;/code>) as the hostname instead of an IP address.
For example, the Web container can access the database using the URL &lt;code>postgresql://postgres:password@db:5432/mydb&lt;/code>. This allows connections to be switched transparently via environment variables, regardless of whether it&amp;rsquo;s a local or production environment.&lt;/p>
&lt;h3 id="health-checks-and-controlling-startup-order">Health Checks and Controlling Startup Order
&lt;/h3>&lt;p>The &lt;code>depends_on&lt;/code> directive controls the startup order of containers, but simply specifying &lt;code>depends_on&lt;/code> will start the Web container as soon as the &amp;ldquo;DB container has started.&amp;rdquo; In reality, the DB initialization process (starting the PostgreSQL process and preparing tables) takes several seconds, so connections from the Web container might fail.
To prevent this, you can define a &lt;code>healthcheck&lt;/code> and specify &lt;code>condition: service_healthy&lt;/code>, which ensures the Web container starts only after confirming that &amp;ldquo;the DB is ready to accept connection requests.&amp;rdquo;&lt;/p>
&lt;hr>
&lt;h2 id="5-environment-variable-management-and-security-env">5. Environment Variable Management and Security (.env)
&lt;/h2>&lt;p>Hardcoding sensitive information, such as database passwords and API keys, into &lt;code>docker-compose.yml&lt;/code> is an anti-pattern that must be strictly avoided. Instead, inject these values using an environment variable file &lt;code>.env&lt;/code>.&lt;/p>
&lt;p>Create a &lt;code>.env&lt;/code> file in the project root.&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 file (add it to .gitignore to keep it out of Git tracking)&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>By default, Docker Compose reads the &lt;code>.env&lt;/code> file in the execution directory and expands placeholders like &lt;code>${VAR_NAME}&lt;/code> in the YAML file. This method makes it possible to safely manage different configuration values for various environments like local, staging, and production without altering the infrastructure code.&lt;/p>
&lt;hr>
&lt;h2 id="6-the-ultimate-development-experience-with-vscode-devcontainers">6. The Ultimate Development Experience with VSCode DevContainers
&lt;/h2>&lt;p>So far, we have built a robust backend environment using Docker. However, we can take it a step further. By using the &lt;strong>VSCode DevContainers (Remote - Containers)&lt;/strong> feature, you can run the backend of the editor (VSCode) itself inside the container.&lt;/p>
&lt;p>This eliminates the need to install Python or Node.js on your local machine, allowing everything from linters (flake8/eslint) and formatters (black/prettier) to IDE extensions to be defined within the codebase and shared with the entire team.&lt;/p>
&lt;h3 id="configuring-devcontainerjson">Configuring devcontainer.json
&lt;/h3>&lt;p>Create a &lt;code>.devcontainer&lt;/code> directory in the project root and place the configuration file inside it.&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>By including this file in the repository, a &amp;ldquo;Reopen in Container&amp;rdquo; prompt will appear the moment you open the project in VSCode. A single click will spin up all the necessary containers, install the extensions, and make it instantly ready for coding. It&amp;rsquo;s truly a magical experience.&lt;/p>
&lt;hr>
&lt;h2 id="7-request-processing-sequence-and-performance-modeling">7. Request Processing Sequence and Performance Modeling
&lt;/h2>&lt;p>We will review the request processing lifecycle of the web application in our newly built local development environment using a sequence diagram and examine the mathematical model of its performance.&lt;/p>
&lt;h3 id="sequence-diagram-request-flow">Sequence Diagram (Request Flow)
&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 (Data exists)&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 (Data does not exist)&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="mathematical-model-of-processing-latency">Mathematical Model of Processing Latency
&lt;/h3>&lt;p>We mathematically model the average request processing time $T_{\text{total}}$ in the above system.
We define the latency of each process as follows:&lt;/p>
&lt;ul>
&lt;li>$T_{\text{net}}$: Network latency between the client and the Web container&lt;/li>
&lt;li>$T_{\text{app}}$: Pure processing time on the application side (serialization, etc.)&lt;/li>
&lt;li>$T_{\text{cache}}$: Time required to read/write from/to Redis&lt;/li>
&lt;li>$T_{\text{db}}$: Time required to execute queries on PostgreSQL&lt;/li>
&lt;li>$p_{\text{miss}}$: Cache miss rate ($0 \le p_{\text{miss}} \le 1$)&lt;/li>
&lt;/ul>
&lt;p>At this time, the average response time is represented by the following expected value formula:&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>In a local development environment (inside Docker), $T_{\text{net}}$ is close to 0, but what&amp;rsquo;s noteworthy is the &lt;strong>I/O performance during bind mounts&lt;/strong>. Especially when using Docker Desktop on Windows/macOS, the file sharing overhead between the host OS and the VM (container) tends to bloat $T_{\text{app}}$ (such as code load time). To eliminate this bottleneck, it is highly recommended to use the aforementioned DevContainers to place the entire source code inside a named volume, or to adopt an architecture that runs the Docker engine natively on a WSL2 (Windows Subsystem for Linux 2) environment.&lt;/p>
&lt;hr>
&lt;h2 id="8-performance-optimization-of-docker-builds-layer-caching-strategy">8. Performance Optimization of Docker Builds: Layer Caching Strategy
&lt;/h2>&lt;p>When writing a Dockerfile, your understanding of the &amp;ldquo;layer cache&amp;rdquo; mechanism will drastically change build times.
Docker creates file system differences (layers) for each instruction in a Dockerfile (like &lt;code>FROM&lt;/code>, &lt;code>RUN&lt;/code>, &lt;code>COPY&lt;/code>) and holds them as caches. On rebuild, cached layers that haven&amp;rsquo;t changed are reused.&lt;/p>
&lt;p>The critical principle is to &lt;strong>&amp;ldquo;write instructions in order from the least frequently changed to the most frequently changed.&amp;rdquo;&lt;/strong>&lt;/p>
&lt;p>Let&amp;rsquo;s model the impact of source code changes on build time. Let the total build time be $T_{\text{build}}$, the execution time of each step be $T_{\text{layer}_i}$, and the presence or absence of a cache hit be a boolean value $c_i \in \{0, 1\}$ (1 for a cache hit).&lt;/p>
$$ T_{\text{build}} = T_{\text{init}} + \sum_{i=1}^{n} (1 - c_i) \times T_{\text{layer}_i} $$&lt;p>Once a cache miss ($c_k = 0$) occurs at layer $k$, caches for all subsequent layers $j > k$ are invalidated ($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"># Bad example (Source code is copied first)&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>In the above case, changing even one line of code causes the first &lt;code>COPY&lt;/code> to miss the cache, resulting in the time-consuming &lt;code>RUN poetry install&lt;/code> being executed every time.&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"># Good example (Resolve dependencies first)&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>If written this way, even if the source code changes, the layer cache for &lt;code>poetry install&lt;/code> ($c_i = 1$) remains effective, drastically reducing build time from several minutes to just a few seconds.&lt;/p>
&lt;hr>
&lt;h2 id="9-troubleshooting-and-tips">9. Troubleshooting and Tips
&lt;/h2>&lt;p>Here are common problems encountered during local environment operations and their solutions.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Port Conflict Error&lt;/strong>
If you get an error like &lt;code>Bind for 0.0.0.0:8000 failed: port is already allocated&lt;/code>, another process on your local machine is using that port. You can avoid this by changing the port number on the host side, like &lt;code>ports: - &amp;quot;8080:8000&amp;quot;&lt;/code>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Disk Space Exhaustion&lt;/strong>
If you use Docker for a long period, unused images and volumes (Dangling Images / Volumes) can accumulate and consume tens of gigabytes of disk space. It is recommended to periodically clean up the system with the following command:&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>File Permission Issues&lt;/strong>
When using bind mounts in a Linux environment, files created inside the container may be owned by &lt;code>root&lt;/code>, preventing you from editing them on the host side. You can resolve this issue by creating a non-root user in your Dockerfile and matching their UID/GID to your own on the host OS (e.g., 1000:1000).&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="10-conclusion-accelerated-development-speeds-brought-by-reproducibility">10. Conclusion: Accelerated Development Speeds Brought by Reproducibility
&lt;/h2>&lt;p>By combining Docker, Docker Compose, and VSCode DevContainers, a robust local development environment is achieved, resulting in &amp;ldquo;the exact same state no matter who launches the environment.&amp;rdquo;&lt;/p>
&lt;p>Bringing the IaC paradigm into your local environment goes beyond merely reducing initial setup times. It eliminates anxiety regarding infrastructure configuration changes, facilitates experimenting with new tech stacks, enables smooth transitions to CI/CD pipelines, and dramatically improves the speed and quality of the entire development cycle.&lt;/p>
&lt;p>By leveraging the best practices explained in this article—optimizing image sizes with multi-stage builds, controlling dependencies with health checks, and writing Dockerfiles with layer caching in mind—we highly encourage you to introduce the best Developer Experience (DX) to your own projects.&lt;/p></description></item></channel></rss>