<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Docker on kenji.blog</title><link>http://kenji.blog/ko/tags/docker/</link><description>Recent content in Docker on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ko</language><copyright>kenjinote</copyright><lastBuildDate>Sun, 13 Sep 2026 01:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/ko/tags/docker/index.xml" rel="self" type="application/rss+xml"/><item><title>Docker를 사용한 재현 가능한 로컬 개발 환경 구축 과정</title><link>http://kenji.blog/ko/p/docker-reproducible-local-dev-environment/</link><pubDate>Sun, 13 Sep 2026 01:00:00 +0900</pubDate><guid>http://kenji.blog/ko/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. 머리말: &amp;ldquo;내 환경에서는 되는데&amp;quot;에서 벗어나기
&lt;/h2>&lt;p>소프트웨어 개발 현장에서 개발자 간의 환경 차이로 인해 발생하는 &amp;ldquo;내 환경에서는 되는데(It works on my machine)&amp;ldquo;라는 문제는 오랫동안 많은 프로젝트에서 시간을 낭비하게 만드는 요인이었습니다. OS의 차이, 설치된 언어의 버전, 라이브러리의 의존성, 전역으로 설치된 도구의 충돌 등 로컬 환경은 항상 &amp;lsquo;상태의 불확실성&amp;rsquo;에 노출되어 있습니다.&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를 활용하여 **&amp;ldquo;누가, 언제, 어떤 머신에서 실행하더라도 한 치의 오차 없이 동일한 상태가 되는 재현 가능한 로컬 개발 환경&amp;rdquo;**을 구축하기 위한 단계와 그 이면에 있는 깊은 기술적 메커니즘에 대해 수리적 관점도 곁들여 철저하게 해설합니다.&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>: &amp;lsquo;어떻게 상태를 변경할 것인가&amp;rsquo;가 아니라 &amp;lsquo;최종적으로 어떤 상태여야 하는가&amp;rsquo;를 정의합니다.&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>을 사용하여 개발 환경의 &amp;lsquo;이상적인 모습&amp;rsquo;을 코드화하는 것을 의미합니다. 이를 통해 새로 팀에 합류한 멤버도 리포지토리를 클론하고 명령어 하나를 실행하는 것만으로 즉시 개발을 시작할 수 있는 온보딩 경험을 실현할 수 있습니다.&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>: 여러 디렉토리 트리(레이어)를 투명하게 겹쳐서 하나의 파일 시스템으로 보이게 하는 기술입니다. 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>멀티 스테이지 빌드는 하나의 &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>최신 웹 애플리케이션 개발에서는 웹 서버, 데이터베이스, 캐시 서버 등 여러 구성 요소가 연동하는 마이크로서비스 아키텍처가 일반적입니다. 로컬 환경에서 이들을 일원화하여 관리하기 위해 &lt;code>docker-compose.yml&lt;/code>을 사용합니다.&lt;/p>
&lt;p>이번에는 &amp;ldquo;Web (FastAPI)&amp;rdquo;, &amp;ldquo;Database (PostgreSQL)&amp;rdquo;, &amp;ldquo;Cache (Redis)&amp;ldquo;의 3계층 구조 시스템을 로컬에 구축합니다.&lt;/p>
&lt;h3 id="아키텍처-다이어그램-mermaid">아키텍처 다이어그램 (Mermaid)
&lt;/h3>&lt;p>다음 다이어그램은 로컬 머신에서의 각 컨테이너, 네트워크, 그리고 볼륨의 관계성을 나타낸 블록 다이어그램입니다.&lt;/p>
&lt;pre class="mermaid">
graph TD
User[&amp;#34;호스트 머신 (브라우저/curl)&amp;#34;] --&amp;gt;|Localhost:8000| Web[&amp;#34;FastAPI 웹 컨테이너&amp;#34;]
subgraph &amp;#34;Docker 브리지 네트워크 (app-network)&amp;#34;
Web --&amp;gt;|포트 5432| DB[&amp;#34;PostgreSQL 컨테이너&amp;#34;]
Web --&amp;gt;|포트 6379| Redis[&amp;#34;Redis 컨테이너&amp;#34;]
end
DB --&amp;gt; Volume1[&amp;#34;명명된 볼륨 (postgres_data)&amp;#34;]
Redis --&amp;gt; Volume2[&amp;#34;명명된 볼륨 (redis_data)&amp;#34;]
HostDir[&amp;#34;호스트 소스 코드 (./src)&amp;#34;] -.-&amp;gt;|바인드 마운트| 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>컨테이너는 원칙적으로 &amp;lsquo;상태를 가지지 않는(스테이트리스)&amp;rsquo; 동시에 &amp;lsquo;단명(에페머럴)&amp;lsquo;하는 존재입니다. 컨테이너를 파기하면 내부 데이터도 함께 사라집니다. 데이터베이스의 데이터나 캐시를 유지하기 위해서는 호스트 머신의 파일 시스템 영역을 컨테이너에 마운트해야 합니다.&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 주소가 아니라 &amp;lsquo;서비스 이름(예: &lt;code>db&lt;/code>, &lt;code>redis&lt;/code>)&amp;lsquo;을 호스트명으로 사용하여 이름 확인(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>만 지정하면 &amp;lsquo;DB 컨테이너가 시작된&amp;rsquo; 단계에서 Web 컨테이너가 시작되어 버립니다. 실제로는 DB의 초기화 프로세스(PostgreSQL의 프로세스 시작이나 테이블 준비)가 완료되기까지 몇 초가 걸리기 때문에 Web 컨테이너에서 DB로의 접속이 오류가 나는 경우가 있습니다.
이를 방지하기 위해 &lt;code>healthcheck&lt;/code>를 정의하고 &lt;code>condition: service_healthy&lt;/code>를 지정하여 &amp;lsquo;DB가 연결 요청을 받아들일 수 있는 상태가 되었음&amp;rsquo;을 확인한 후에 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에서 프로젝트를 여는 순간 &amp;ldquo;Reopen in Container&amp;quot;라는 프롬프트가 표시되고, 클릭하는 것만으로 필요한 모든 컨테이너가 켜지고 확장 기능이 설치되어 즉시 코딩을 시작할 수 있는 상태가 됩니다. 그야말로 마법 같은 경험입니다.&lt;/p>
&lt;hr>
&lt;h2 id="7-요청-처리-시퀀스와-성능-모델링">7. 요청 처리 시퀀스와 성능 모델링
&lt;/h2>&lt;p>구축한 로컬 개발 환경에서의 웹 애플리케이션 요청 처리 라이프사이클을 시퀀스 다이어그램으로 확인하고, 그 성능의 수리 모델을 고찰합니다.&lt;/p>
&lt;h3 id="시퀀스-다이어그램-요청-흐름">시퀀스 다이어그램 (요청 흐름)
&lt;/h3>&lt;pre class="mermaid">
sequenceDiagram
participant Client as &amp;#34;브라우저 / VSCode&amp;#34;
participant Web as &amp;#34;FastAPI (웹)&amp;#34;
participant Redis as &amp;#34;Redis 캐시&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;user:123에 대한 캐시 확인&amp;#34;
activate Redis
alt &amp;#34;캐시 적중 (데이터 있음)&amp;#34;
Redis--&amp;gt;&amp;gt;Web: &amp;#34;캐시된 사용자 데이터 반환&amp;#34;
Web--&amp;gt;&amp;gt;Client: &amp;#34;200 OK (빠른 응답)&amp;#34;
else &amp;#34;캐시 미스 (데이터 없음)&amp;#34;
Redis--&amp;gt;&amp;gt;Web: &amp;#34;Null (찾을 수 없음)&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;데이터베이스 행 반환&amp;#34;
deactivate DB
Web-&amp;gt;&amp;gt;Redis: &amp;#34;user:123 데이터 설정 (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 (표준 응답)&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}}$: 클라이언트와 웹 컨테이너 간의 네트워크 레이턴시&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을 작성할 때 &amp;lsquo;레이어 캐시&amp;rsquo;의 원리를 이해하고 있는지 여부에 따라 빌드 시간은 극적으로 달라집니다.
Docker는 Dockerfile의 각 명령(&lt;code>FROM&lt;/code>, &lt;code>RUN&lt;/code>, &lt;code>COPY&lt;/code> 등)마다 파일 시스템의 차분(레이어)을 생성하고 캐시로 유지합니다. 재빌드 시에는 변경이 없는 레이어의 캐시가 재사용됩니다.&lt;/p>
&lt;p>중요한 원칙은 **&amp;ldquo;변경 빈도가 낮은 것부터 순서대로 작성한다&amp;rdquo;**는 것입니다.&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-트러블슈팅과-팁">9. 트러블슈팅과 팁
&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를 결합함으로써 &amp;ldquo;누가 환경을 띄워도 완전히 같은 상태가 되는&amp;rdquo; 견고한 로컬 개발 환경이 실현됩니다.&lt;/p>
&lt;p>IaC의 패러다임을 로컬 환경에 도입하는 것은 단순히 초기 설정 시간을 단축하는 것에 그치지 않습니다. 인프라 설정 변경에 대한 불안을 해소하고 새로운 기술 스택의 실험을 용이하게 하며 CI/CD 파이프라인으로의 원활한 전환을 가능하게 하는 등, 개발 사이클 전체의 속도와 품질을 비약적으로 향상시킵니다.&lt;/p>
&lt;p>본 문서에서 해설한 멀티 스테이지 빌드에 의한 이미지 크기 최적화나, 상태 확인을 사용한 의존성 제어, 레이어 캐시를 의식한 Dockerfile 작성 등의 모범 사례를 활용하여, 꼭 여러분의 프로젝트에도 최고의 개발 경험(DX: Developer Experience)을 도입해 보시기 바랍니다.&lt;/p></description></item><item><title>WSL2(Windows Subsystem for Linux) 궁극의 개발 환경 설정 가이드</title><link>http://kenji.blog/ko/p/wsl2-ultimate-development-setup-guide/</link><pubDate>Sat, 12 Sep 2026 23:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/wsl2-ultimate-development-setup-guide/</guid><description>&lt;img src="http://kenji.blog/p/wsl2-ultimate-development-setup-guide/img/eyecatch.jpg" alt="Featured image of post WSL2(Windows Subsystem for Linux) 궁극의 개발 환경 설정 가이드" />&lt;p>Windows 상에서 Linux 네이티브 개발 환경을 제공하는 &amp;lsquo;WSL2(Windows Subsystem for Linux 2)&amp;lsquo;는 현대 소프트웨어 개발에서 필수 불가결한 도구가 되었습니다. 하지만 기본 상태로 계속 사용하는 것과 아키텍처를 이해하고 적절하게 튜닝을 적용하는 것은 성능과 개발 경험에 엄청난 차이를 가져옵니다.&lt;/p>
&lt;p>본 문서에서는 WSL2의 근간을 이루는 아키텍처 해설을 시작으로, 성능을 최대한으로 끌어올리기 위한 설정, 쾌적한 터미널 환경 구축, Docker 및 VS Code와의 원활한 연동, 그리고 고급 네트워크 설정까지 전문 엔지니어가 요구하는 &amp;lsquo;궁극의 개발 환경&amp;rsquo;을 구축하기 위한 모든 절차를 1만 자 이상의 분량으로 철저하게 해설합니다.&lt;/p>
&lt;hr>
&lt;h2 id="1-wsl2의-아키텍처와-wsl1으로부터의-진화">1. WSL2의 아키텍처와 WSL1으로부터의 진화
&lt;/h2>&lt;p>WSL2의 잠재력을 완전히 이끌어내기 위해서는 먼저 그 내부 구조를 이해하는 것이 중요합니다. 1세대 WSL(WSL1)과 WSL2는 Linux 바이너리를 Windows 상에서 실행하기 위한 접근 방식이 근본적으로 다릅니다.&lt;/p>
&lt;h3 id="wsl1-시스템-콜-변환-레이어">WSL1: 시스템 콜 변환 레이어
&lt;/h3>&lt;p>WSL1은 Linux의 시스템 콜을 실시간으로 Windows의 NT API로 변환(트랜슬레이션)하는 구조를 채택했습니다. 이는 가상 머신(VM)을 사용하지 않기 때문에 리소스 오버헤드가 매우 적다는 장점이 있었습니다. 하지만 파일 시스템의 I/O 작업 등 복잡한 시스템 콜을 완벽하게 에뮬레이션하기는 어려웠고, 특히 Node.js의 &lt;code>npm install&lt;/code>이나 Git의 리포지토리 조작 등 다량의 작은 파일을 다루는 처리에서 절망적인 성능 저하를 초래했습니다.&lt;/p>
&lt;h3 id="wsl2-경량-유틸리티-vm과-완전한-linux-커널">WSL2: 경량 유틸리티 VM과 완전한 Linux 커널
&lt;/h3>&lt;p>WSL2에서는 아키텍처가 쇄신되어 &lt;strong>Hyper-V 아키텍처의 서브셋을 이용한 &amp;lsquo;경량 유틸리티 VM&amp;rsquo;&lt;/strong> 위에서 Microsoft가 빌드한 실제 Linux 커널이 직접 가동되게 되었습니다. 이로써 시스템 콜의 100% 호환성이 보장되었고, Linux 네이티브의 ext4 파일 시스템을 이용한 가상 디스크(VHDX)를 사용함으로써 파일 I/O 성능이 WSL1과 비교하여 극적으로 향상되었습니다.&lt;/p>
&lt;p>아래의 Mermaid 다이어그램은 WSL1과 WSL2의 구조적인 차이를 보여줍니다.&lt;/p>
&lt;pre class="mermaid">
flowchart TD
subgraph &amp;#34;Windows OS Environment&amp;#34;
A[&amp;#34;Windows NT 커널&amp;#34;]
A --&amp;gt; F[&amp;#34;NTFS 파일 시스템 (C: 드라이브)&amp;#34;]
end
subgraph &amp;#34;WSL2 Architecture&amp;#34;
B[&amp;#34;Hyper-V 하이퍼바이저&amp;#34;]
B --&amp;gt; C[&amp;#34;경량 유틸리티 VM&amp;#34;]
C --&amp;gt; D[&amp;#34;Linux 커널 (Microsoft)&amp;#34;]
D --&amp;gt; E[&amp;#34;Ubuntu 사용자 공간 (glibc, bash 등)&amp;#34;]
D --&amp;gt; G[&amp;#34;ext4 가상 디스크 (.vhdx)&amp;#34;]
end
A -.-&amp;gt;|&amp;#34;Plan 9 (9P) 프로토콜 네트워크 파일 공유&amp;#34;| D
style B fill:#f9f,stroke:#333,stroke-width:2px
style D fill:#bbf,stroke:#333,stroke-width:2px
&lt;/pre>
&lt;p>이 구조에서 얻을 수 있는 중요한 교훈은 **&amp;ldquo;Linux 측의 파일(VHDX 내)에 대한 접근은 매우 빠르지만, Windows 측의 파일(&lt;code>/mnt/c/&lt;/code>)에 대한 접근은 9P 프로토콜을 거치기 때문에 매우 느리다&amp;rdquo;**는 것입니다. 프로젝트의 소스 코드는 반드시 WSL 측의 홈 디렉토리(&lt;code>~&lt;/code>) 아래에 배치해야 합니다.&lt;/p>
&lt;hr>
&lt;h2 id="2-성능의-수학적-분석-왜-wsl2는-빠른가">2. 성능의 수학적 분석: 왜 WSL2는 빠른가?
&lt;/h2>&lt;p>WSL2의 성능 향상을 수학적인 모델을 사용하여 정량적으로 평가해 보겠습니다. 소프트웨어 개발에서 가장 시간이 많이 걸리는 작업 중 하나가 다량의 파일 I/O를 수반하는 처리(예: 라이브러리 설치나 빌드)입니다.&lt;/p>
&lt;p>어떤 처리 전체의 실행 시간 $T_{total}$ 은 CPU에 의한 연산 시간 $T_{compute}$ 와 디스크 I/O에 걸리는 시간 $T_{io}$ 의 합으로 표현됩니다.&lt;/p>
$$ T_{total} = T_{compute} + T_{io} $$&lt;p>WSL1의 경우, Linux 측의 조작을 NTFS의 조작으로 변환하는 오버헤드가 발생하기 때문에, I/O 시간은 다음과 같이 모델링됩니다. 여기서 $n$ 은 파일 조작 횟수, $t_{ntfs\_syscall}$ 은 Windows 측의 시스템 콜 실행 시간, $t_{trans}$ 는 변환 레이어의 오버헤드입니다.&lt;/p>
$$ T_{wsl1\_io} = \sum_{i=1}^{n} (t_{ntfs\_syscall_i} + t_{trans_i}) $$&lt;p>반면, WSL2의 경우 ext4 파일 시스템에 대해 커널이 직접 I/O를 발행하기 때문에, 오버헤드는 가상화로 인한 아주 약간의 지연 $t_{virt}$ 뿐입니다.&lt;/p>
$$ T_{wsl2\_io} = \sum_{i=1}^{n} (t_{ext4_i} + t_{virt_i}) $$&lt;p>일반적인 파일 시스템에서 $t_{ext4} \ll t_{ntfs\_syscall} + t_{trans}$ 이므로, $n$ 이 매우 큰(수만~수십만 개의 파일 조작을 수행하는) 경우 WSL1과 WSL2의 I/O 시간 차이는 지수함수적으로 벌어집니다.&lt;/p>
&lt;p>또한, 가상화 환경에서의 CPU 연산 오버헤드 비율을 $\rho$ 라고 하면, 최신 하드웨어 지원 가상화(Intel VT-x / AMD-V)에서는 $\rho \approx 0.01 \sim 0.03$ (1~3%) 정도에 머뭅니다. 따라서 순수한 계산 작업에서도 네이티브 Linux 환경과 손색없는 $97\% \sim 99\%$ 의 성능이 발휘됩니다.&lt;/p>
&lt;hr>
&lt;h2 id="3-설치와-기반-구축">3. 설치와 기반 구축
&lt;/h2>&lt;p>Windows 10/11에서는 WSL2의 설치가 매우 간단해졌습니다. 관리자 권한으로 PowerShell을 열고 다음 명령어를 실행하기만 하면 됩니다.&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;/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"># 기본적으로 WSL2와 Ubuntu가 설치됩니다&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">wsl&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-install&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="c"># wsl --list --online 으로 확인 가능&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">wsl&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-install&lt;/span> &lt;span class="n">-d&lt;/span> &lt;span class="n">Ubuntu&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="mf">24.04&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>설치 후 재부팅을 거쳐 첫 실행 시 UNIX 사용자 이름과 비밀번호 설정을 요구받게 됩니다. 이 사용자는 Windows 사용자와 독립적이며, WSL 내에서만 유효합니다.&lt;/p>
&lt;p>이미 WSL1을 사용하고 있는 경우에는 다음 명령어로 WSL2로 변환합니다.&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;/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"># 기존 배포판을 WSL2로 변환&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">wsl&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-set-version&lt;/span> &lt;span class="n">Ubuntu&lt;/span> &lt;span class="mf">2&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"># 향후 추가할 배포판의 기본값을 WSL2로 설정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">wsl&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-set-default-version&lt;/span> &lt;span class="mf">2&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;h2 id="4-리소스-제어의-비결-wslconfig-와-wslconf">4. 리소스 제어의 비결: .wslconfig 와 wsl.conf
&lt;/h2>&lt;p>WSL2의 가장 큰 함정 중 하나가 &amp;lsquo;메모리의 무제한 소비(Vmmem 프로세스의 비대화)&amp;lsquo;입니다. WSL2는 Linux 커널의 페이지 캐시를 이용하기 때문에, I/O를 수행할 때마다 호스트(Windows)의 메모리를 끝없이 잠식해 들어갑니다. 이를 방지하기 위해 설정 파일을 통한 리소스 제한이 필수적입니다.&lt;/p>
&lt;p>WSL2의 설정 파일은 &lt;strong>Windows 전체에 영향을 미치는 &lt;code>.wslconfig&lt;/code>&lt;/strong> 와 &lt;strong>각 배포판의 내부에 영향을 미치는 &lt;code>wsl.conf&lt;/code>&lt;/strong> 의 두 가지로 나뉩니다.&lt;/p>
&lt;h3 id="41-wslconfig-windows-측">4.1. .wslconfig (Windows 측)
&lt;/h3>&lt;p>Windows의 사용자 프로필 디렉토리(&lt;code>C:\Users\&amp;lt;사용자명&amp;gt;\.wslconfig&lt;/code>)에 파일을 생성하여 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;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;/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"># C:\Users\&amp;lt;사용자명&amp;gt;\.wslconfig&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">[wsl2]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># VM에 할당할 최대 메모리 양. 호스트 총 메모리의 50%~75% 정도를 권장&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">memory&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">16GB&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="c1"># 사용할 CPU 코어 수(생략 시 모든 코어 사용)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">processors&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">8&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="c1"># 스왑 파일의 크기&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">swap&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">8GB&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="c1"># 스왑 파일의 저장 위치(C 드라이브의 용량을 절약하고 싶은 경우)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># swapfile=D:\\wsl\\swap.vhdx&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="c1"># localhost 포워딩 활성화(Windows 측에서 localhost로 WSL에 접근하기 위해)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">localhostForwarding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">true&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="c1"># 메모리를 자동으로 확보(Windows 11 전용)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 페이지 캐시를 동적으로 해제하여 Vmmem의 비대화를 방지&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">autoMemoryReclaim&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">dropcache&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="k">[experimental]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Windows 11 22H2 이후에서 이용 가능한 고급 네트워크 기능&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 이를 통해 IPv6 지원이나, WSL과 Windows 간의 동일한 IP 주소 공유가 가능해짐&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">networkingMode&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">mirrored&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">dnsTunneling&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">firewall&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">autoProxy&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">true&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="42-wslconf-linux-측">4.2. wsl.conf (Linux 측)
&lt;/h3>&lt;p>WSL 내의 &lt;code>/etc/wsl.conf&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-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># /etc/wsl.conf (WSL 내부에서 편집)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">[network]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># WSL 시작 시 자동 생성되는 /etc/resolv.conf 의 생성을 비활성화&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 자체 DNS(예: 8.8.8.8)를 설정하고 싶은 경우에 유용&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">generateResolvConf&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">false&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="c1"># 자체 호스트 이름 설정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">hostname&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">WSL-DevNode&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="k">[automount]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Windows 드라이브를 마운트할 때의 설정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">enabled&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">options&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;metadata,uid=1000,gid=1000,umask=022&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># C 드라이브의 마운트 포인트를 /mnt/c 에서 /c 로 변경(경로를 짧게 하기 위해)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">root&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">/&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="k">[boot]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># systemd 활성화(WSL 0.67.6 이후)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 이를 통해 snap이나 각종 데몬(Docker 등)이 네이티브하게 작동하게 됨&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">systemd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">true&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="k">[user]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 기본으로 로그인할 사용자&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">kenji&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>이러한 설정을 반영하기 위해서는 PowerShell에서 &lt;code>wsl --shutdown&lt;/code> 을 실행하여, WSL VM을 완전히 정지시킨 후 재시작해야 합니다.&lt;/p>
&lt;hr>
&lt;h2 id="5-궁극의-터미널-환경-zsh--powerlevel10k">5. 궁극의 터미널 환경: Zsh + Powerlevel10k
&lt;/h2>&lt;p>기본 bash 상태로는 생산성이 오르지 않습니다. 강력한 자동 완성 기능과 시인성을 자랑하는 Zsh에, 초고속 테마 &amp;lsquo;Powerlevel10k&amp;rsquo;를 결합하여 최강의 프롬프트를 구축합니다.&lt;/p>
&lt;h3 id="51-windows-terminal-도입과-설정">5.1. Windows Terminal 도입과 설정
&lt;/h3>&lt;p>Microsoft Store에서 &amp;lsquo;Windows Terminal&amp;rsquo;을 설치합니다. JSON 설정(&lt;code>settings.json&lt;/code>)을 열고 기본 프로필을 WSL(Ubuntu)로 설정한 후, 폰트를 개발용 Nerd Font(예: &lt;code>HackGen Console NF&lt;/code> 나 &lt;code>MesloLGS NF&lt;/code>)로 변경합니다.&lt;/p>
&lt;h3 id="52-zsh-및-oh-my-zsh-설치">5.2. Zsh 및 Oh My Zsh 설치
&lt;/h3>&lt;p>WSL 터미널에서 다음 명령어를 실행합니다.&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;/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">&lt;span class="c1"># 패키지 업데이트 및 Zsh 설치&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt update &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> sudo apt upgrade -y
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt install -y zsh git curl
&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="c1"># Oh My Zsh 설치 스크립트 실행&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sh -c &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="53-powerlevel10k와-플러그인-도입">5.3. Powerlevel10k와 플러그인 도입
&lt;/h3>&lt;p>Zsh를 더욱 강화하는 플러그인(구문 강조와 입력 자동 완성) 및 Powerlevel10k 테마를 도입합니다.&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;/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">&lt;span class="c1"># Powerlevel10k&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git clone --depth&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> https://github.com/romkatv/powerlevel10k.git &lt;span class="si">${&lt;/span>&lt;span class="nv">ZSH_CUSTOM&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="nv">$HOME&lt;/span>&lt;span class="p">/.oh-my-zsh/custom&lt;/span>&lt;span class="si">}&lt;/span>/themes/powerlevel10k
&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="c1"># zsh-autosuggestions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git clone https://github.com/zsh-users/zsh-autosuggestions &lt;span class="si">${&lt;/span>&lt;span class="nv">ZSH_CUSTOM&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="p">~/.oh-my-zsh/custom&lt;/span>&lt;span class="si">}&lt;/span>/plugins/zsh-autosuggestions
&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="c1"># zsh-syntax-highlighting&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git clone https://github.com/zsh-users/zsh-syntax-highlighting.git &lt;span class="si">${&lt;/span>&lt;span class="nv">ZSH_CUSTOM&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="p">~/.oh-my-zsh/custom&lt;/span>&lt;span class="si">}&lt;/span>/plugins/zsh-syntax-highlighting
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>~/.zshrc&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;/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">&lt;span class="c1"># ~/.zshrc의 변경점&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">ZSH_THEME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;powerlevel10k/powerlevel10k&amp;#34;&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="c1"># 플러그인 배열에 추가&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">plugins&lt;/span>&lt;span class="o">=(&lt;/span>git zsh-autosuggestions zsh-syntax-highlighting&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>저장한 후 &lt;code>source ~/.zshrc&lt;/code> 를 실행하면, Powerlevel10k 설정 마법사(&lt;code>p10k configure&lt;/code>)가 시작됩니다. 화면의 지시에 따라 자신이 원하는 형태의 프롬프트(프롬프트 스타일, 아이콘 유무, 표시할 정보 등)로 커스터마이즈하십시오. Git의 브랜치명이나 상태, Node.js 버전, 명령어 실행 시간 등이 실시간으로 표시되어 개발 효율이 비약적으로 향상됩니다.&lt;/p>
&lt;hr>
&lt;h2 id="6-vs-code-remote---wsl의-원활한-통합">6. VS Code Remote - WSL의 원활한 통합
&lt;/h2>&lt;p>WSL2 환경에서의 개발에 있어, Windows 측에 설치된 IDE(Visual Studio Code)에서 WSL 내의 파일에 매끄럽게 접근하는 구조가 &amp;lsquo;Remote - WSL&amp;rsquo; 확장 기능입니다.&lt;/p>
&lt;h3 id="아키텍처-해설">아키텍처 해설
&lt;/h3>&lt;p>아래의 시퀀스 다이어그램은 VS Code가 WSL2와 어떻게 통신하는지 보여줍니다.&lt;/p>
&lt;pre class="mermaid">
sequenceDiagram
autonumber
participant U as &amp;#34;Developer&amp;#34;
participant V as &amp;#34;VS Code UI (Windows)&amp;#34;
participant S as &amp;#34;VS Code Server (WSL2)&amp;#34;
participant F as &amp;#34;ext4 File System (WSL2)&amp;#34;
U-&amp;gt;&amp;gt;V: &amp;#34;WSL 터미널에서 `code .` 입력&amp;#34;
V-&amp;gt;&amp;gt;S: &amp;#34;Vsock을 통해 RPC 연결 수립&amp;#34;
Note over V,S: TCP/IP를 사용하지 않고 Hyper-V 소켓으로 통신
S-&amp;gt;&amp;gt;F: &amp;#34;소스 파일 읽기 / 린터 실행&amp;#34;
F--&amp;gt;&amp;gt;S: &amp;#34;데이터 및 분석 결과 반환&amp;#34;
S--&amp;gt;&amp;gt;V: &amp;#34;Language Server 결과를 UI로 스트리밍&amp;#34;
V--&amp;gt;&amp;gt;U: &amp;#34;구문 강조 및 오류 표시&amp;#34;
&lt;/pre>
&lt;p>Windows 측의 VS Code는 단순한 &amp;lsquo;얇은 클라이언트(UI)&amp;lsquo;로 기능하며, Language Server, 디버거, 터미널 실행 등의 무거운 처리는 모두 WSL 측의 &amp;lsquo;VS Code Server&amp;rsquo;에서 처리됩니다. 이를 통해 Windows 측에 Node.js나 Python을 설치하지 않고도, WSL 측에서만 환경을 깨끗하게 유지할 수 있습니다.&lt;/p>
&lt;h3 id="필수-vs-code-설정">필수 VS Code 설정
&lt;/h3>&lt;p>VS Code의 &amp;lsquo;확장 기능&amp;rsquo;에서 &lt;strong>&amp;ldquo;WSL&amp;rdquo; (ms-vscode-remote.remote-wsl)&lt;/strong> 을 설치합니다. 그 후 WSL의 터미널에서 프로젝트 디렉토리로 이동하여 &lt;code>code .&lt;/code> 를 실행하기만 하면, 해당 디렉토리가 열린 상태로 Windows 측의 VS Code가 시작됩니다.&lt;/p>
&lt;p>&lt;strong>중요한 주의점(줄바꿈 코드 문제):&lt;/strong>
Windows와 Linux는 줄바꿈 코드가 다릅니다(Windows는 &lt;code>CRLF&lt;/code>, Linux는 &lt;code>LF&lt;/code>). WSL 상에서 개발을 진행할 경우, Git의 &lt;code>core.autocrlf&lt;/code> 설정이나 VS Code의 파일 기본 설정을 반드시 &lt;code>LF&lt;/code> 로 통일해야 합니다. 이를 소홀히 하면 쉘 스크립트나 Docker 컨테이너 실행 시 알 수 없는 오류로 고생하게 됩니다.&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;/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">&lt;span class="c1"># WSL 측에서의 Git 줄바꿈 코드 설정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git config --global core.autocrlf input
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>VS Code의 &lt;code>settings.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;/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;files.eol&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;\n&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;terminal.integrated.defaultProfile.linux&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;zsh&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;hr>
&lt;h2 id="7-docker-desktop과-wsl2-integration-최적화">7. Docker Desktop과 WSL2 Integration 최적화
&lt;/h2>&lt;p>WSL2 환경에서 Docker를 이용하려면 주로 2가지 접근 방식이 있습니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Docker Desktop for Windows&lt;/strong> 를 설치하고, WSL2 통합 기능을 활성화한다.&lt;/li>
&lt;li>WSL2 내부(Ubuntu 등)에 &lt;strong>네이티브 Docker Engine&lt;/strong> 을 직접 설치한다.&lt;/li>
&lt;/ol>
&lt;h3 id="접근-방식-1-docker-desktop-권장">접근 방식 1: Docker Desktop (권장)
&lt;/h3>&lt;p>GUI를 통한 관리나 Windows/WSL 간의 투명한 컨테이너 접근이 용이하기 때문에, 많은 경우 이 방식이 권장됩니다. Docker Desktop의 설정(Settings)에서 다음을 확인합니다.&lt;/p>
&lt;ul>
&lt;li>&lt;code>General&lt;/code> -&amp;gt; &lt;code>Use the WSL 2 based engine&lt;/code> 에 체크합니다.&lt;/li>
&lt;li>&lt;code>Resources&lt;/code> -&amp;gt; &lt;code>WSL Integration&lt;/code> -&amp;gt; &lt;code>Enable integration with my default WSL distro&lt;/code> 에 체크하고, 토글 버튼으로 사용할 배포판(Ubuntu)을 켭니다.&lt;/li>
&lt;/ul>
&lt;p>이를 통해 WSL2의 터미널에서 직접 &lt;code>docker&lt;/code> 명령어를 실행할 수 있게 되며, Docker 데몬과의 통신은 Docker Desktop이 관리하는 전용 경량 VM(&lt;code>docker-desktop&lt;/code> 및 &lt;code>docker-desktop-data&lt;/code>)을 통해 이루어집니다.&lt;/p>
&lt;h3 id="접근-방식-2-네이티브-docker-engine-직접-도입">접근 방식 2: 네이티브 Docker Engine 직접 도입
&lt;/h3>&lt;p>기업 네트워크의 제약(Docker Desktop 유료화 회피 등)이나 성능 오버헤드를 극한까지 줄이고 싶은 경우에는, &lt;code>/etc/wsl.conf&lt;/code> 에서 &lt;code>systemd&lt;/code> 를 활성화한 뒤, 순수한 Ubuntu 서버로서 Docker를 설치합니다.&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;/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">&lt;span class="c1"># systemd가 활성화된 WSL2 Ubuntu에서의 Docker 공식 설치 절차 발췌&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt-get update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt-get install ca-certificates curl gnupg
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo install -m &lt;span class="m">0755&lt;/span> -d /etc/apt/keyrings
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl -fsSL https://download.docker.com/linux/ubuntu/gpg &lt;span class="p">|&lt;/span> sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo chmod a+r /etc/apt/keyrings/docker.gpg
&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="c1"># 리포지토리 추가&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&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="s2">&amp;#34;deb [arch=&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>dpkg --print-architecture&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34; signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>. /etc/os-release &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$VERSION_CODENAME&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34; stable&amp;#34;&lt;/span> &lt;span class="p">|&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> sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt-get update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
&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="c1"># 현재 사용자를 docker 그룹에 추가(sudo 없이 실행하기 위해)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo usermod -aG docker &lt;span class="nv">$USER&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>재부팅 후, 네이티브 Linux 환경과 완전히 동일하게 &lt;code>systemctl start docker&lt;/code> 가 작동하며, 높은 성능을 발휘합니다.&lt;/p>
&lt;hr>
&lt;h2 id="8-ssh-키-통합-windows와-wsl에서의-원활한-인증">8. SSH 키 통합: Windows와 WSL에서의 원활한 인증
&lt;/h2>&lt;p>Git의 SSH 클론이나 원격 서버에 SSH 접속을 수행할 때, Windows 측과 WSL 측에서 별도의 SSH 키를 관리하는 것은 매우 번거롭습니다. 보안과 편의성을 양립시키기 위해 Windows 측에서 가동 중인 SSH 에이전트(또는 1Password와 같은 암호 관리자)를 WSL 측에 브릿지하는 설정을 수행합니다.&lt;/p>
&lt;p>여기에서는 가장 안전하고 모던한 접근 방식으로, &lt;strong>1Password의 SSH 에이전트 기능&lt;/strong> 또는 &lt;strong>Windows의 OpenSSH Authentication Agent&lt;/strong> 를 이용하고, &lt;code>npiperelay&lt;/code> 나 &lt;code>socat&lt;/code> 을 사용하여 WSL2의 UNIX 도메인 소켓으로 포워딩하는 방법을 설명합니다.&lt;/p>
&lt;h3 id="ssh-agent의-소켓-포워딩">ssh-agent의 소켓 포워딩
&lt;/h3>&lt;p>일반적으로 Windows의 Named Pipe(명명된 파이프)로 제공되는 SSH 에이전트를 WSL 측의 소켓 파일로 변환할 필요가 있습니다. &lt;code>wsl-ssh-agent&lt;/code> 나 1Password가 제공하는 기능을 이용하면 간단합니다.&lt;/p>
&lt;p>1Password의 설정 화면에서 &amp;lsquo;Developer&amp;rsquo; -&amp;gt; &amp;lsquo;SSH 에이전트 사용&amp;rsquo;을 활성화합니다.
그 다음, WSL 측의 &lt;code>~/.zshrc&lt;/code> 또는 &lt;code>~/.bashrc&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;/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">&lt;span class="c1"># ~/.zshrc 에 추가(1Password SSH Agent를 이용할 경우의 예)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">SSH_AUTH_SOCK&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nv">$HOME&lt;/span>/.ssh/agent.sock
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># WSL 시작 시 소켓이 존재하지 않거나, 프로세스가 바인딩되지 않은 경우 socat과 npiperelay를 사용하여 포워드&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">ALREADY_RUNNING&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>ps -aux &lt;span class="p">|&lt;/span> grep &lt;span class="s2">&amp;#34;[n]piperelay.exe -ei -s //./pipe/openssh-ssh-agent&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span> wc -l&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="o">[&lt;/span> &lt;span class="nv">$ALREADY_RUNNING&lt;/span> -eq &lt;span class="m">0&lt;/span> &lt;span class="o">]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="o">[&lt;/span> -S &lt;span class="nv">$SSH_AUTH_SOCK&lt;/span> &lt;span class="o">]&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">then&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> rm &lt;span class="nv">$SSH_AUTH_SOCK&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 백그라운드에서 socat을 시작하여, Windows 측의 Named Pipe를 WSL 측의 UNIX 소켓에 연결&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">(&lt;/span>setsid socat UNIX-LISTEN:&lt;span class="nv">$SSH_AUTH_SOCK&lt;/span>,fork EXEC:&lt;span class="s2">&amp;#34;npiperelay.exe -ei -s //./pipe/openssh-ssh-agent&amp;#34;&lt;/span>,nofork &lt;span class="p">&amp;amp;&lt;/span>&lt;span class="o">)&lt;/span> &amp;gt;/dev/null 2&amp;gt;&lt;span class="p">&amp;amp;&lt;/span>&lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">fi&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>※사전에 Windows 측에 &lt;code>npiperelay.exe&lt;/code> 설치 및 환경 변수(Path) 등록 작업이 필요합니다.&lt;/p>
&lt;p>이 설정이 완료되면, WSL 터미널에서 &lt;code>ssh-add -l&lt;/code> 을 실행했을 때 1Password나 Windows 측에 등록한 SSH 키의 공개키 목록이 표시됩니다. 이를 통해 비밀키 편집을 WSL 내에 복사할 필요 없이 안전하게 인증을 통과할 수 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="9-유지-보수-비대해진-vhdx-최적화압축">9. 유지 보수: 비대해진 VHDX 최적화(압축)
&lt;/h2>&lt;p>WSL2의 가장 큰 단점 중 하나가 &amp;ldquo;Docker 이미지를 삭제하거나 파일을 삭제해도 Windows 측 가상 디스크(.vhdx)의 파일 크기가 자동으로 축소되지 않는다&amp;quot;는 사양입니다. 장기간 개발을 계속하다 보면 ext4.vhdx 파일이 수십 GB에서 수백 GB까지 부풀어 오릅니다.&lt;/p>
&lt;p>디스크 용량을 확보하기 위해서는 주기적으로 Windows 측에서 VHDX를 최적화(Compact)해야 합니다.&lt;/p>
&lt;ol>
&lt;li>먼저, WSL을 완전히 종료합니다.
&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-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">wsl&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-shutdown&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;li>관리자 권한의 PowerShell을 열고, 다음의 &lt;code>diskpart&lt;/code> 명령어, 또는 Hyper-V 모듈의 &lt;code>Optimize-VHD&lt;/code> 명령어를 실행합니다(Hyper-V가 활성화되어 있는 경우에만 후자를 사용할 수 있습니다).&lt;/li>
&lt;/ol>
&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;/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"># Hyper-V 모듈을 사용할 수 있는 경우&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Optimize-VHD&lt;/span> &lt;span class="n">-Path&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$env:LOCALAPPDATA&lt;/span>&lt;span class="s2">\Packages\CanonicalGroupLimited.Ubuntu_79rhkp1fndgsc\LocalState\ext4.vhdx&amp;#34;&lt;/span> &lt;span class="n">-Mode&lt;/span> &lt;span class="n">Full&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"># diskpart를 사용하는 경우&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">diskpart&lt;/span>
&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="n">DISKPART&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="nb">select &lt;/span>&lt;span class="n">vdisk&lt;/span> &lt;span class="n">file&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="s2">&amp;#34;C:\Users\&amp;lt;사용자명&amp;gt;\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu_79rhkp1fndgsc\LocalState\ext4.vhdx&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">DISKPART&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">attach&lt;/span> &lt;span class="n">vdisk&lt;/span> &lt;span class="n">readonly&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">DISKPART&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">compact&lt;/span> &lt;span class="n">vdisk&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">DISKPART&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">detach&lt;/span> &lt;span class="n">vdisk&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">DISKPART&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">exit&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>주기적으로 이 작업을 수행함으로써, 낭비되었던 C 드라이브 용량을 되찾을 수 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="10-맺음말">10. 맺음말
&lt;/h2>&lt;p>WSL2는 단순한 &amp;lsquo;Windows 상에서 구동되는 덤으로 주는 Linux&amp;rsquo;라는 틀을 완전히 넘어서, macOS나 네이티브 Linux 머신 못지않은, 혹은 그 이상의 강력한 개발 플랫폼으로 진화했습니다.&lt;/p>
&lt;p>이번에 해설한 설정(&lt;code>.wslconfig&lt;/code> 를 통한 리소스 최적화, Zsh + Powerlevel10k를 통한 터미널 강화, VS Code Remote를 통한 투명한 접근, 그리고 SSH 연동 및 VHDX 유지 보수)을 모두 적용함으로써, 스트레스 없고 빠르며 안전한 &amp;lsquo;궁극의 개발 환경&amp;rsquo;이 완성됩니다.&lt;/p>
&lt;p>환경 구축에는 약간 수고가 들어가지만, 한 번 설정을 확립해두면 향후 엔지니어링의 생산성이 극적으로 향상될 것임에 틀림없습니다. 부디 자신의 프로젝트나 취향에 맞춰 이 가이드를 바탕으로 더 나은 커스터마이징을 탐구해 보시기 바랍니다.&lt;/p></description></item></channel></rss>