<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Ci-Cd on Chanyeol Dev</title>
    <link>https://chanyeols.com/tags/ci-cd/</link>
    <description>Recent content in Ci-Cd on Chanyeol Dev</description>
    <generator>Hugo</generator>
    <language>ko-kr</language>
    <lastBuildDate>Fri, 24 Apr 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://chanyeols.com/tags/ci-cd/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Docker Compose로 멀티 컨테이너 환경 구축하기: 단계별 튜토리얼</title>
      <link>https://chanyeols.com/posts/docker-compose-multi-container-deployment-guide/</link>
      <pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate>
      <guid>https://chanyeols.com/posts/docker-compose-multi-container-deployment-guide/</guid>
      <description>docker-compose로 멀티 컨테이너 환경을 구성하는 단계별 가이드. 네트워크, 볼륨, 의존성 관리부터 트러블슈팅까지 완벽 정리</description>
      <content:encoded><![CDATA[<h2 id="docker-compose로-멀티-컨테이너-환경-구축하기-단계별-튜토리얼">Docker Compose로 멀티 컨테이너 환경 구축하기: 단계별 튜토리얼</h2>
<blockquote>
<p><strong>주제 키워드</strong>: Docker Compose, 멀티 컨테이너, YAML 구성, 의존성 관리, CI/CD 통합</p>
</blockquote>
<p>현대 애플리케이션 개발에서 <strong>멀티 컨테이너 아키텍처</strong>는 마이크로서비스, 데이터 처리 파이프라인, 개발/운영 환경 통합에 필수적인 요소입니다. <strong>Docker Compose</strong>는 단일 YAML 파일로 여러 컨테이너의 생명주기, 네트워크, 볼륨, 환경 변수를 관리하는 도구로, 개발부터 프로덕션까지 효율성을 극대화합니다. 이 가이드에서는 DB-웹 애플리케이션 예제부터 고급 설정, 트러블슈팅까지 실제 동작 가능한 예제로 설명합니다.</p>
<h2 id="1-docker-compose의-핵심-개념과-장점">1. Docker Compose의 핵심 개념과 장점</h2>
<h3 id="11-멀티-컨테이너-아키텍처의-필요성">1.1 멀티 컨테이너 아키텍처의 필요성</h3>
<p>단일 컨테이너로 모든 기능을 구현하면 <strong>관심사 분리</strong>가 어렵고, <strong>확장성</strong>이 제한됩니다. 예를 들어, 웹 서버와 데이터베이스를 하나의 컨테이너에 묶으면:</p>
<ul>
<li>DB 업데이트 시 전체 애플리케이션 재빌드 필요</li>
<li>각 서비스의 독립적인 확장(Scale-out) 불가</li>
<li>로깅/모니터링의 복잡성 증가</li>
</ul>
<p>Docker Compose는 이런 문제를 해결하며, <strong>서비스 간 의존성</strong>을 명확히 정의하고 <strong>환경별 구성</strong>을 쉽게 전환할 수 있습니다. 공식 문서(<a href="https://docs.docker.com/compose/">Docker Compose Overview</a>)에서도 강조하는 핵심 이점은 다음과 같습니다:</p>
<ul>
<li><strong>선언적 구성</strong>: YAML 파일로 인프라 상태를 코드로 관리</li>
<li><strong>의존성 자동 처리</strong>: <code>depends_on</code>으로 컨테이너 시작 순서 제어</li>
<li><strong>환경 변수 분리</strong>: <code>.env</code> 파일로 민감 정보 관리</li>
</ul>
<h3 id="12-단일-yaml-파일의-효율성">1.2 단일 YAML 파일의 효율성</h3>
<p>예를 들어, 웹 애플리케이션과 PostgreSQL DB를 연결하는 구성은 다음과 같이 단순화됩니다:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># docker-compose.yml</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">version</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;3.8&#39;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">services</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">webapp</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">image</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">my-webapp:latest</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">ports</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">&#34;8000:8000&#34;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">environment</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">DB_HOST=db</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">DB_USER=user</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">DB_PASSWORD=secret</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">depends_on</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">db</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">db</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">image</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">postgres:15</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">environment</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">POSTGRES_USER</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">user</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">POSTGRES_PASSWORD</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">secret</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">volumes</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">postgres_data:/var/lib/postgresql/data</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">volumes</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">postgres_data</span>:<span style="color:#6e7681">
</span></span></span></code></pre></div><h2 id="2-사전-준비-개발-환경-설정">2. 사전 준비: 개발 환경 설정</h2>
<h3 id="21-docker-및-docker-compose-설치">2.1 Docker 및 Docker Compose 설치</h3>
<ul>
<li><strong>Linux (Ubuntu 기준)</strong>:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt-get update
</span></span><span style="display:flex;"><span>sudo apt-get install docker.io docker-compose
</span></span></code></pre></div><ul>
<li><strong>macOS</strong>: <a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a> 설치 후 자동 포함</li>
<li><strong>Windows</strong>: Docker Desktop 설치 시 WSL2와 함께 Compose 활성화</li>
</ul>
<blockquote>
<p><strong>확인 명령</strong>:</p>
</blockquote>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker --version
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Docker version 24.0.2, Build cb71016</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>docker-compose version
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># docker-compose version 1.29.2, build 5becea4c</span>
</span></span></code></pre></div><h3 id="22-네트워크-및-볼륨-개념">2.2 네트워크 및 볼륨 개념</h3>
<ul>
<li><strong>네트워크</strong>: 컨테이너 간 통신을 위한 가상 네트워크. 기본적으로 Compose는 프로젝트명_default 네트워크를 생성하지만, 커스텀 네트워크도 정의 가능합니다.</li>
<li><strong>볼륨</strong>: 호스트와 컨테이너 간 데이터 영구 저장. 위 예제의 <code>postgres_data</code>는 DB 데이터를 호스트에 보관하여 컨테이너 재생성 시에도 데이터 유지</li>
</ul>
<h2 id="3-단계별-본문-기본-구성-실습">3. 단계별 본문: 기본 구성 실습</h2>
<h3 id="31-간단한-웹-애플리케이션-예제">3.1 간단한 웹 애플리케이션 예제</h3>
<p>Python Flask 애플리케이션과 Redis 캐시를 연동하는 예제를 구현해 보겠습니다.</p>
<h4 id="311-애플리케이션-코드-작성">3.1.1 애플리케이션 코드 작성</h4>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># app.py</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">flask</span> <span style="color:#ff7b72">import</span> Flask
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">redis</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">os</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>app <span style="color:#ff7b72;font-weight:bold">=</span> Flask(<span style="color:#79c0ff">__name__</span>)
</span></span><span style="display:flex;"><span>redis_host <span style="color:#ff7b72;font-weight:bold">=</span> os<span style="color:#ff7b72;font-weight:bold">.</span>getenv(<span style="color:#a5d6ff">&#39;REDIS_HOST&#39;</span>, <span style="color:#a5d6ff">&#39;localhost&#39;</span>)
</span></span><span style="display:flex;"><span>redis_client <span style="color:#ff7b72;font-weight:bold">=</span> redis<span style="color:#ff7b72;font-weight:bold">.</span>StrictRedis(host<span style="color:#ff7b72;font-weight:bold">=</span>redis_host, port<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">6379</span>, db<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">0</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">@app.route</span>(<span style="color:#a5d6ff">&#39;/&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">hello</span>():
</span></span><span style="display:flex;"><span>    redis_client<span style="color:#ff7b72;font-weight:bold">.</span>incr(<span style="color:#a5d6ff">&#39;hits&#39;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;Total hits: </span><span style="color:#a5d6ff">{</span>redis_client<span style="color:#ff7b72;font-weight:bold">.</span>get(<span style="color:#a5d6ff">&#39;hits&#39;</span>)<span style="color:#ff7b72;font-weight:bold">.</span>decode(<span style="color:#a5d6ff">&#39;utf-8&#39;</span>)<span style="color:#a5d6ff">&#34;</span><span style="color:#79c0ff">\n</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> <span style="color:#79c0ff">__name__</span> <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#34;__main__&#34;</span>:
</span></span><span style="display:flex;"><span>    app<span style="color:#ff7b72;font-weight:bold">.</span>run(host<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;0.0.0.0&#39;</span>, port<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">8000</span>)
</span></span></code></pre></div><h4 id="312-docker-이미지-빌드">3.1.2 Docker 이미지 빌드</h4>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-Dockerfile" data-lang="Dockerfile"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Dockerfile</span><span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">FROM</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">python:3.9-slim</span><span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">WORKDIR</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">/app</span><span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">COPY</span> requirements.txt ./<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">RUN</span> pip install --no-cache-dir -r requirements.txt<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">COPY</span> . ./<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">CMD</span> [<span style="color:#a5d6ff">&#34;python&#34;</span>, <span style="color:#a5d6ff">&#34;app.py&#34;</span>]<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># requirements.txt</span><span style="color:#f85149">
</span></span></span><span style="display:flex;"><span>flask<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span>redis<span style="color:#f85149">
</span></span></span></code></pre></div><h4 id="313-docker-compose-파일-작성">3.1.3 Docker Compose 파일 작성</h4>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># docker-compose.yml</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">version</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;3.8&#39;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">services</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">web</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">build</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">.</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">ports</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">&#34;8000:8000&#34;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">environment</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">REDIS_HOST</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">redis</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">depends_on</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">redis</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">redis</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">image</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">redis:7.0</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">ports</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">&#34;6379:6379&#34;</span><span style="color:#6e7681">
</span></span></span></code></pre></div><h4 id="314-실행-및-테스트">3.1.4 실행 및 테스트</h4>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 빌드 및 실행</span>
</span></span><span style="display:flex;"><span>$ docker-compose up --build
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 별도 터미널에서 테스트</span>
</span></span><span style="display:flex;"><span>$ curl http://localhost:8000
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 출력: Total hits: 1</span>
</span></span></code></pre></div><h3 id="32-의존성-관리의-중요성">3.2 의존성 관리의 중요성</h3>
<p><code>depends_on</code>은 컨테이너 시작 순서만 제어할 뿐, DB 준비 완료 상태를 보장하지 않습니다. 이를 해결하려면 <a href="https://github.com/vishnubob/wait-for-it">wait-for-it.sh</a> 같은 툴을 사용하거나, 애플리케이션 수준에서 재시도 로직을 구현해야 합니다.</p>
<h2 id="4-고급-설정-프로덕션-환경-최적화">4. 고급 설정: 프로덕션 환경 최적화</h2>
<h3 id="41-커스텀-네트워크와-환경-변수-분리">4.1 커스텀 네트워크와 환경 변수 분리</h3>
<h4 id="411-네트워크-정의-예제">4.1.1 네트워크 정의 예제</h4>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># docker-compose.yml</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">version</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;3.8&#39;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">networks</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">app-network</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">driver</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">bridge</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">services</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">web</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">networks</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">app-network</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">db</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">networks</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">app-network</span><span style="color:#6e7681">
</span></span></span></code></pre></div><h4 id="412-env-파일-활용">4.1.2 .env 파일 활용</h4>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-env" data-lang="env"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># .env</span>
</span></span><span style="display:flex;"><span><span style="color:#79c0ff">DB_USER</span><span style="color:#ff7b72;font-weight:bold">=</span>admin
</span></span><span style="display:flex;"><span><span style="color:#79c0ff">DB_PASSWORD</span><span style="color:#ff7b72;font-weight:bold">=</span>strong_password
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># docker-compose.yml</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">env_file</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>- <span style="color:#a5d6ff">.env</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">services</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">web</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">environment</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">DB_USER</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">${DB_USER}</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">DB_PASSWORD</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">${DB_PASSWORD}</span><span style="color:#6e7681">
</span></span></span></code></pre></div><h3 id="42-프로필-기반-구성-devprod">4.2 프로필 기반 구성 (dev/prod)</h3>
<p>Compose 1.27+부터 <code>profiles</code>를 사용해 환경별 서비스를 활성화할 수 있습니다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># docker-compose.yml</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">version</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;3.8&#39;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">services</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">web</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">image</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">my-webapp:latest</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">db</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">image</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">postgres:15</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">devtools</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">image</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">node:18</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">command</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">npm run dev</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">profiles</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">dev</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">monitoring</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">image</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">prom/prometheus</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">profiles</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">prod</span><span style="color:#6e7681">
</span></span></span></code></pre></div><blockquote>
<p><strong>실행 방법</strong>:</p>
</blockquote>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 개발 환경</span>
</span></span><span style="display:flex;"><span>$ docker-compose --profile dev up
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 프로덕션 환경</span>
</span></span><span style="display:flex;"><span>$ docker-compose --profile prod up
</span></span></code></pre></div><h2 id="5-트러블슈팅-흔한-문제와-해결법">5. 트러블슈팅: 흔한 문제와 해결법</h2>
<h3 id="51-컨테이너-간-연결-실패">5.1 컨테이너 간 연결 실패</h3>
<ul>
<li><strong>증상</strong>: <code>Connection refused</code> 오류 발생</li>
<li><strong>원인</strong>: DB가 준비되기 전에 웹 애플리케이션이 연결을 시도</li>
<li><strong>해결</strong>:
<ol>
<li><code>depends_on</code>에 <code>condition</code> 추가</li>
<li><a href="https://github.com/avengerow/dockurize">Dockerize</a> 같은 툴로 헬스 체크</li>
</ol>
</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 조건 추가 예제</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">depends_on</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">db</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">condition</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">service_healthy</span><span style="color:#6e7681">
</span></span></span></code></pre></div><h3 id="52-볼륨-마운트-권한-문제">5.2 볼륨 마운트 권한 문제</h3>
<ul>
<li><strong>증상</strong>: <code>Permission denied</code> 오류</li>
<li><strong>해결</strong>:
<ol>
<li>호스트 디렉터리 권한 변경</li>
<li>Docker Compose에서 <code>user</code> 지정</li>
</ol>
</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 권한 문제 해결 예제</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">volumes</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>- <span style="color:#7ee787">type</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">volume</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">source</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">myvol</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">target</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">/app/data</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">volume</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">nocopy</span>:<span style="color:#6e7681"> </span><span style="color:#79c0ff">true</span><span style="color:#6e7681">
</span></span></span></code></pre></div><h3 id="53-로그-분석">5.3 로그 분석</h3>
<ul>
<li><strong>컨테이너 로그 확인</strong>:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ docker-compose logs <span style="color:#ff7b72;font-weight:bold">[</span>서비스명<span style="color:#ff7b72;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 예: docker-compose logs web</span>
</span></span></code></pre></div><ul>
<li><strong>실시간 모니터링</strong>:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ docker-compose logs -f web
</span></span></code></pre></div><h2 id="6-마치며-docker-compose의-확장성">6. 마치며: Docker Compose의 확장성</h2>
<ol>
<li><strong>CI/CD 통합</strong>: GitHub Actions, GitLab CI에서 <code>docker-compose</code> 명령어로 테스트 환경 구축 가능</li>
<li><strong>확장성</strong>: Kubernetes로의 전환을 위해 Compose 파일을 Helm 차트로 변환 가능</li>
<li><strong>모니터링</strong>: Prometheus + Grafana와 연동해 컨테이너 메트릭 수집</li>
</ol>
<blockquote>
<p><strong>추가 학습 자료</strong>:</p>
</blockquote>
<ul>
<li><a href="https://docs.docker.com/compose/reference/">Docker Compose 공식 문서</a></li>
<li><a href="https://github.com/veggiemonk/awesome-docker">Awesome Docker</a> 커뮤니티 리소스</li>
</ul>
<p>이 가이드를 통해 멀티 컨테이너 환경의 설계부터 운영까지의 핵심 역량을 습득할 수 있습니다. 실제 프로덕션 환경에서는 보안 강화(예: TLS 암호화)와 리소스 제한(예: <code>--memory</code>, <code>--cpus</code>)을 추가로 적용해야 합니다.</p>
]]></content:encoded>
    </item>
    <item>
      <title>SpringBoot &#43; GitHub Actions CI/CD 완벽 구성 &amp; 문제 해결</title>
      <link>https://chanyeols.com/posts/springboot-gh-actions-cicd-troubleshooting-guide/</link>
      <pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate>
      <guid>https://chanyeols.com/posts/springboot-gh-actions-cicd-troubleshooting-guide/</guid>
      <description>SpringBoot 프로젝트에 GitHub Actions CI/CD 파이프라인을 구성하고, 빌드/배포 과정에서 발생하는 문제를 해결하는 단계별 가이드입니다. JDK 설정부터 Docker 이미지 푸시, 트러블슈팅 기법까지 실전 예제를 포함합니다.</description>
      <content:encoded><![CDATA[<h2 id="springboot--github-actions-cicd-완벽-구성--문제-해결">SpringBoot + GitHub Actions CI/CD 완벽 구성 &amp; 문제 해결</h2>
<p>SpringBoot 애플리케이션에 GitHub Actions를 활용한 CI/CD 파이프라인을 구축하고, 실제 운영 환경에서 발생하는 문제를 해결하는 방법을 단계별로 설명합니다. 이 가이드는 JDK 설치부터 클라우드 배포까지 전체 워크플로우를 다루며, 실패 사례별 트러블슈팅 기법을 포함합니다.</p>
<h2 id="튜토리얼-springboot-cicd-기본-구성">튜토리얼: SpringBoot CI/CD 기본 구성</h2>
<h3 id="1-github-리포지토리-연결-및-기본-workflow-생성">1. GitHub 리포지토리 연결 및 기본 Workflow 생성</h3>
<ol>
<li>SpringBoot 프로젝트를 GitHub에 업로드합니다.
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git init
</span></span><span style="display:flex;"><span>git add .
</span></span><span style="display:flex;"><span>git commit -m <span style="color:#a5d6ff">&#34;Initial commit&#34;</span>
</span></span><span style="display:flex;"><span>git branch -M main
</span></span><span style="display:flex;"><span>git remote add origin https://github.com/&lt;your-id&gt;/&lt;repo-name&gt;.git
</span></span><span style="display:flex;"><span>git push -u origin main
</span></span></code></pre></div></li>
<li><code>.github/workflows/springboot-ci-cd.yml</code> 파일을 생성합니다.</li>
</ol>
<h3 id="2-기본-workflow-yaml-구성">2. 기본 Workflow YAML 구성</h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">SpringBoot CI/CD</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">on</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">push</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">branches</span>:<span style="color:#6e7681"> </span>[<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#34;main&#34;</span><span style="color:#6e7681"> </span>]<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">pull_request</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">branches</span>:<span style="color:#6e7681"> </span>[<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#34;main&#34;</span><span style="color:#6e7681"> </span>]<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">jobs</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">build</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">runs-on</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">ubuntu-latest</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">steps</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>- <span style="color:#7ee787">uses</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">actions/checkout@v4</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>- <span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Set up JDK 17</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">uses</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">actions/setup-java@v4</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">with</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">        </span><span style="color:#7ee787">java-version</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;17&#39;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">        </span><span style="color:#7ee787">distribution</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;temurin&#39;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>- <span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Build with Gradle</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">run</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">./gradlew build</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>- <span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Build Docker image</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">run</span>:<span style="color:#6e7681"> </span>|<span style="color:#a5d6ff">
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">        docker build -t my-springboot-app:latest .
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">        docker tag my-springboot-app:latest \
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">          $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/my-springboot-app:latest</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>- <span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Push to ECR</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">run</span>:<span style="color:#6e7681"> </span>|<span style="color:#a5d6ff">
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">        aws ecr get-login-password --region $AWS_REGION | \
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">          docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">        docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/my-springboot-app:latest</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">env</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">        </span><span style="color:#7ee787">AWS_ACCOUNT_ID</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">${{ secrets.AWS_ACCOUNT_ID }}</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">        </span><span style="color:#7ee787">AWS_REGION</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">${{ secrets.AWS_REGION }}</span><span style="color:#6e7681">
</span></span></span></code></pre></div><blockquote>
<p><strong>팁</strong>: Maven 사용 시 <code>./mvnw clean package</code>로 변경합니다. Dockerfile이 프로젝트 루트에 위치해야 합니다.</p>
</blockquote>
<h3 id="3-github-secrets-설정">3. GitHub Secrets 설정</h3>
<ul>
<li>AWS 계정의 <code>AWS_ACCOUNT_ID</code>와 <code>AWS_REGION</code>을 Secrets에 등록</li>
<li>Azure App Service 사용 시 <code>AZURE_WEBAPP_NAME</code>과 <code>AZURE_CREDENTIALS</code> 추가</li>
</ul>
<h2 id="트러블슈팅-cicd-실패-주요-사례">트러블슈팅: CI/CD 실패 주요 사례</h2>
<h3 id="1-빌드-실패-사례">1. 빌드 실패 사례</h3>
<ul>
<li><strong>Java 버전 불일치</strong>:
<pre tabindex="0"><code>Error: Java version &#34;1.8.0_292&#34; is not supported by Gradle
</code></pre>→ <code>setup-java</code> 액션의 <code>java-version</code>을 프로젝트 요구사항에 맞춰 수정</li>
<li><strong>Gradle wrapper 미포함</strong>:
<pre tabindex="0"><code>/bin/sh: ./gradlew: No such file or directory
</code></pre>→ <code>gradlew</code> 바이너리 포함 여부 확인 (Gradle wrapper 사용 필수)</li>
<li><strong>의존성 다운로드 실패</strong>:
<pre tabindex="0"><code>Could not transfer artifact com.example:library:jar:1.0.0
</code></pre>→ 방화벽 설정 확인 또는 <code>--no-daemon</code> 플래그 추가</li>
</ul>
<h3 id="2-배포-실패-사례">2. 배포 실패 사례</h3>
<ul>
<li><strong>IAM 권한 문제</strong>:
<pre tabindex="0"><code>User is not authorized to perform ecr:PutImage
</code></pre>→ AWS ECR 접근 권한이 있는 IAM 역할 생성 후 Secrets에 자격 증명 등록</li>
<li><strong>Docker 태그 충돌</strong>:
<pre tabindex="0"><code>manifest for my-springboot-app:latest not found
</code></pre>→ <code>docker pull</code>로 기존 이미지 삭제 후 재시도</li>
<li><strong>Helm chart 오류</strong>:
<pre tabindex="0"><code>failed: Invalid value: : invalid type for io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext
</code></pre>→ Helm 차트 버전 호환성 확인 및 <code>helm dependency update</code> 실행</li>
</ul>
<h3 id="3-캐시-미적용-문제">3. 캐시 미적용 문제</h3>
<pre tabindex="0"><code>GET https://jcenter.bintray.com/...
</code></pre><p>→ Gradle/Maven 의존성 캐시 추가:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Cache Gradle packages</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">uses</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">actions/cache@v3</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">with</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">path</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">~/.gradle/caches</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">key</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">${{ runner.os }}-gradle-${{ hashFiles(&#39;**/*.gradle*&#39;) }}</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">restore-keys</span>:<span style="color:#6e7681"> </span>|<span style="color:#a5d6ff">
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">      ${{ runner.os }}-gradle-</span><span style="color:#6e7681">
</span></span></span></code></pre></div><h2 id="문제-해결-단계별-검증-방법">문제 해결: 단계별 검증 방법</h2>
<h3 id="1-로컬에서-workflow-테스트">1. 로컬에서 Workflow 테스트</h3>
<p><code>act</code> CLI로 로컬에서 워크플로우를 시뮬레이션합니다.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>brew install nektos/act/act
</span></span><span style="display:flex;"><span>act --pull-request --pull-request-head-sha<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#ff7b72">$(</span>git rev-parse HEAD<span style="color:#ff7b72">)</span>
</span></span></code></pre></div><h3 id="2-디버그-로그-활성화">2. 디버그 로그 활성화</h3>
<p>워크플로우 단계에 <code>set -x</code> 추가:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Debug Docker build</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">run</span>:<span style="color:#6e7681"> </span>|<span style="color:#a5d6ff">
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">    set -x
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">    docker build -t my-springboot-app:latest .</span><span style="color:#6e7681">
</span></span></span></code></pre></div><h3 id="3-self-hosted-runner-로그-분석">3. Self-hosted Runner 로그 분석</h3>
<p>SSH로 러너 서버에 접속해 직접 로그 확인:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>journalctl -u github-actions-runner.service -f
</span></span></code></pre></div><h3 id="4-품질-검증-도구-연동">4. 품질 검증 도구 연동</h3>
<ul>
<li><strong>JUnit 테스트 결과</strong>: <code>gradle test jacocoTestReport</code> 추가</li>
<li><strong>SonarQube 분석</strong>:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#7ee787">name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">Run SonarQube Scanner</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">uses</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">SonarSource/sonarqube-scan-action@master</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">with</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">projectKey</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">my-springboot-project</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">projectName</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">SpringBoot Demo</span><span style="color:#6e7681">
</span></span></span></code></pre></div><h2 id="참고-문서">참고 문서</h2>
<ul>
<li><a href="https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven">GitHub Actions Java with Gradle 문서</a></li>
<li><a href="https://docs.aws.amazon.com/AmazonECR/latest/developerguide/docker-push-ecr-image.html">AWS ECR Docker 푸시 가이드</a></li>
</ul>
<blockquote>
<p><strong>주의사항</strong>: 프로덕션 환경에서는 <code>main</code> 브랜치 외에 <code>develop</code> 브랜치에 대한 별도 워크플로우를 구성하는 것이 안전합니다. 이미지 태그 전략으로는 <code>SHA</code> 기반 태그를 권장합니다.</p>
</blockquote>
]]></content:encoded>
    </item>
  </channel>
</rss>
