<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>포도쿵야의 개발 모험기</title>
    <link>https://podokungya.tistory.com/</link>
    <description>엽기발랄 포도쿵야와 함께하는 좌충우돌 개발 모험기</description>
    <language>ko</language>
    <pubDate>Thu, 21 May 2026 12:10:31 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>포도쿵야</managingEditor>
    <image>
      <title>포도쿵야의 개발 모험기</title>
      <url>https://tistory1.daumcdn.net/tistory/8619869/attach/e4b71eddb5db42018b7591dc3779b374</url>
      <link>https://podokungya.tistory.com</link>
    </image>
    <item>
      <title>GitHub 100k+ 스타 받은 AI 코딩 지침 : claude.md에 직접 적용해서 비교해보기</title>
      <link>https://podokungya.tistory.com/20</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ChatGPT Image 2026년 5월 13일 오후 03_58_43.png&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;1254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zhnn1/dJMb99M6r3b/lPRVGLqaBHWVrjTWQb4Xh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zhnn1/dJMb99M6r3b/lPRVGLqaBHWVrjTWQb4Xh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zhnn1/dJMb99M6r3b/lPRVGLqaBHWVrjTWQb4Xh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzhnn1%2FdJMb99M6r3b%2FlPRVGLqaBHWVrjTWQb4Xh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;400&quot; data-filename=&quot;ChatGPT Image 2026년 5월 13일 오후 03_58_43.png&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;1254&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  시작하며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude나 ChatGPT 같은 AI 코딩 어시스턴트를 쓰다 보면 이런 경험 있으신가요?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;간단한 함수 하나만 만들어달라고 했는데 왜 클래스를 만들어?&quot;&lt;/li&gt;
&lt;li&gt;&quot;이 함수만 수정하면 되는데 왜 전체 파일을 리팩토링해?&quot;&lt;/li&gt;
&lt;li&gt;&quot;묻지도 않은 에러 처리랑 로깅을 왜 추가해?&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저도 마찬가지였습니다. 특히 기존 프로젝트를 수정할 때 AI가 &quot;친절하게&quot; 추가한 코드들 때문에 오히려 더 복잡해지는 경우가 많았죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러던 중 &lt;a href=&quot;https://aisparkup.com/posts/9225&quot;&gt;이 아티클&lt;/a&gt;에서 흥미로운 걸 발견했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  CLAUDE.md: AI의 나쁜 습관을 고치는 지침서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Andrej Karpathy의 &lt;a href=&quot;https://github.com/forrestchang/andrej-karpathy-skills/tree/main&quot;&gt;GitHub 레포&lt;/a&gt;에 있는 CLAUDE.md는 LLM이 코딩할 때 흔히 저지르는 실수를 줄이기 위한 행동 지침입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 원칙 4가지:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Think Before Coding&lt;/b&gt;: 추측하지 말고, 불확실하면 질문하라&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Simplicity First&lt;/b&gt;: 요청받은 것만, 최소한의 코드로&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Surgical Changes&lt;/b&gt;: 필요한 부분만 수정, 관련 없는 건 건드리지 마라&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Goal-Driven Execution&lt;/b&gt;: 검증 가능한 목표를 세우고 달성할 때까지 반복&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 내용이지만 문제가 있습니다. &lt;b&gt;개발자가 매번 이걸 인지하고 지시할 수 없다는 것.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  해결책: claude.md에 지침 명시하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 원칙들을 Claude가 &lt;b&gt;자동으로 따르도록&lt;/b&gt; 프로젝트 루트의 claude.md 파일에 넣어봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본은 길어서 토큰 효율을 위해 4줄로 압축했습니다:&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;## 코딩 가이드라인
- **구현 전 확인**: 파일 형식, 저장 방식, 에러 처리 범위 등 핵심 결정사항을 '이 부분은 X가 맞나요?' 형태로 먼저 질문
- **최소 구현**: 단일 사용 코드는 추상화하지 말 것. 클래스/설정/히스토리 등 요청 없는 기능 추가 금지
- **변경 범위 제한**: 요청된 함수/파일만 수정. 주변 코드 포맷팅, 네이밍, 리팩토링 금지. 본인이 추가한 미사용 import만 제거
- **검증 계획**: 구현 전 '1) 테스트 작성 2) 구현 3) 수동 확인' 같은 2-3단계 계획 제시 후 각 단계 완료 시 결과 보고
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  실험: 날씨 대시보드 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;날씨 데이터를 가져와서 옷차림을 알려주는 대시보드&quot;를 만들어달라고 요청했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;방법 1: Auto 모드 (지침 없음)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude에게 그냥 자유롭게 작업하도록 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;416&quot; data-origin-height=&quot;367&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDDPn6/dJMcagyGTG2/teNRIgQ6fNjk2FYQeN69R1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDDPn6/dJMcagyGTG2/teNRIgQ6fNjk2FYQeN69R1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDDPn6/dJMcagyGTG2/teNRIgQ6fNjk2FYQeN69R1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDDPn6%2FdJMcagyGTG2%2FteNRIgQ6fNjk2FYQeN69R1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;416&quot; height=&quot;367&quot; data-origin-width=&quot;416&quot; data-origin-height=&quot;367&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;⏱️ &lt;b&gt;소요 시간&lt;/b&gt;: 6m&lt;/li&gt;
&lt;li&gt;  &lt;b&gt;코드 라인&lt;/b&gt;: 약 500줄&lt;/li&gt;
&lt;li&gt;✨ &lt;b&gt;특징&lt;/b&gt;:&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다양한 날씨 아이콘&lt;/li&gt;
&lt;li&gt;반응형 디자인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWw8Wm/dJMcai4iQDb/v1SM4JcZkI007alJhqhIMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWw8Wm/dJMcai4iQDb/v1SM4JcZkI007alJhqhIMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWw8Wm/dJMcai4iQDb/v1SM4JcZkI007alJhqhIMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWw8Wm%2FdJMcai4iQDb%2Fv1SM4JcZkI007alJhqhIMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1146&quot; height=&quot;844&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;방법 2: Plan 모드 (지침 있음)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;claude.md에 위 가이드라인을 추가하고 Plan 모드로 실행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmDNtj/dJMcacQBsGa/PqfzGD5tZmbkQcBc7unI3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmDNtj/dJMcacQBsGa/PqfzGD5tZmbkQcBc7unI3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmDNtj/dJMcacQBsGa/PqfzGD5tZmbkQcBc7unI3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmDNtj%2FdJMcacQBsGa%2FPqfzGD5tZmbkQcBc7unI3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;520&quot; height=&quot;359&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;359&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;429&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E50IF/dJMcacQBsHA/Ov6Kl9kQkL57cxxN64d12K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E50IF/dJMcacQBsHA/Ov6Kl9kQkL57cxxN64d12K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E50IF/dJMcacQBsHA/Ov6Kl9kQkL57cxxN64d12K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE50IF%2FdJMcacQBsHA%2FOv6Kl9kQkL57cxxN64d12K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;551&quot; height=&quot;429&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;429&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;⏱️ &lt;b&gt;소요 시간&lt;/b&gt;: 4m&lt;/li&gt;
&lt;li&gt;  &lt;b&gt;코드 라인&lt;/b&gt;: 약 57줄&lt;/li&gt;
&lt;li&gt;✨ &lt;b&gt;특징&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구현 전 계속 질문 (&quot;날씨 API는 어떤 걸 쓰시나요?&quot;, &quot;옷차림 기준은 온도만 볼까요?&quot;)&lt;/li&gt;
&lt;li&gt;요청한 기능만 정확히 구현&lt;/li&gt;
&lt;li&gt;간결한 코드&lt;/li&gt;
&lt;li&gt;명확한 검증 단계 제시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1906&quot; data-origin-height=&quot;850&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blpalG/dJMb997qmg2/MHRzvkZNYsjRKLJzAYk2PK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blpalG/dJMb997qmg2/MHRzvkZNYsjRKLJzAYk2PK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blpalG/dJMb997qmg2/MHRzvkZNYsjRKLJzAYk2PK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblpalG%2FdJMb997qmg2%2FMHRzvkZNYsjRKLJzAYk2PK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1906&quot; height=&quot;850&quot; data-origin-width=&quot;1906&quot; data-origin-height=&quot;850&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  결과 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목 Auto 모드 (지침 없음) Plan 모드 (지침 있음)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;코드 라인&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;~500줄&lt;/td&gt;
&lt;td&gt;~57줄&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;소요 시간&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;기준&lt;/td&gt;
&lt;td&gt;1.5배 빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;사전 질문&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;5-6번 질문&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;추가 기능&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;로딩, 히스토리, 애니메이션 등&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;디자인&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;풍성하고 화려함&lt;/td&gt;
&lt;td&gt;간결하고 실용적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;코드 이해도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;복잡함&lt;/td&gt;
&lt;td&gt;명확함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  언제 어떤 방식을 써야 할까?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Auto 모드 (지침 없음)를 추천하는 경우:&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;✅ &lt;b&gt;신규 토이 프로젝트&lt;/b&gt;: 빠르게 프로토타입이 필요할 때&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;초기 구축&lt;/b&gt;: 어떤 기능이 필요한지 아직 모를 때&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;탐색 단계&lt;/b&gt;: &quot;일단 뭔가 만들어봐&quot; 수준의 요청&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;디자인 중요&lt;/b&gt;: 시각적 완성도가 우선일 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Plan 모드 (지침 있음)를 추천하는 경우:&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;✅ &lt;b&gt;기존 프로젝트 수정&lt;/b&gt;: 특정 기능만 추가/수정&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;리팩토링&lt;/b&gt;: 코드 정리가 목적일 때&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;명확한 요구사항&lt;/b&gt;: 정확히 뭘 원하는지 알 때&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;코드 학습&lt;/b&gt;: 구현 과정을 이해하고 싶을 때&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;팀 프로젝트&lt;/b&gt;: 일관된 코드 스타일 유지가 중요할 때&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  개인적인 소감&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 &quot;지침 줘봤자 무슨 소용이야?&quot;라고 생각했는데, 실제로 써보니 확실히 차이가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Plan 모드에서 Claude가 계속 질문하는 과정에서 &lt;b&gt;제가 놓쳤던 부분들을 발견&lt;/b&gt;하게 되더라고요. &quot;아, 에러 처리 어떻게 할지 생각 안 해봤네?&quot; 같은 것들이요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Auto 모드는 빠르게 뭔가를 만들고 싶을 때, Plan 모드는 정확하게 원하는 걸 만들고 싶을 때. 상황에 맞게 선택하면 될 것 같습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고 링크:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://aisparkup.com/posts/9225&quot;&gt;원본 아티클&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/forrestchang/andrej-karpathy-skills/tree/main&quot;&gt;CLAUDE.md GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI</category>
      <category>Article</category>
      <category>claude</category>
      <category>claude code</category>
      <category>claude md</category>
      <category>claude plan</category>
      <category>github</category>
      <category>github star</category>
      <author>포도쿵야</author>
      <guid isPermaLink="true">https://podokungya.tistory.com/20</guid>
      <comments>https://podokungya.tistory.com/20#entry20comment</comments>
      <pubDate>Wed, 13 May 2026 15:48:29 +0900</pubDate>
    </item>
    <item>
      <title>코딩할 때 Claude 컨텍스트가 꽉 찼다면? 오염 없는 효율적인 세션 전환 가이드</title>
      <link>https://podokungya.tistory.com/19</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ChatGPT Image 2026년 4월 30일 오후 05_59_53.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/13CS4/dJMcaib7YM9/qzK81KFh6GBlX5a7PfKT2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/13CS4/dJMcaib7YM9/qzK81KFh6GBlX5a7PfKT2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/13CS4/dJMcaib7YM9/qzK81KFh6GBlX5a7PfKT2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F13CS4%2FdJMcaib7YM9%2FqzK81KFh6GBlX5a7PfKT2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1024&quot; data-filename=&quot;ChatGPT Image 2026년 4월 30일 오후 05_59_53.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제: 컨텍스트가 차면 뭐가 문제인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude CLI로 코딩하다 보면 어느 순간:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 고친 버그를 다시 언급&lt;/li&gt;
&lt;li&gt;30분 전 결정을 잊어버림&lt;/li&gt;
&lt;li&gt;똑같은 실수 반복&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트가 70-80% 넘어가면서 나타나는 증상입니다. 이때 두 가지 선택지가 있죠:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;/compact로 압축하고 계속&lt;/li&gt;
&lt;li&gt;handoff 문서 만들고 새 세션&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결론부터: 코딩 작업은 무조건 2번이 낫습니다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;/compact의 3가지 치명적 문제&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 가장 멍청한 순간에 요약한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트가 차면 모델 품질이 떨어집니다. 자동 압축은 95% 도달 시, 즉 &lt;b&gt;판단력이 최악인 순간&lt;/b&gt;에 실행되죠. 멍청한 상태에서 &quot;뭐가 중요한지&quot; 결정하니 당연히 품질이 떨어집니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 손실 압축 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대화를 요약으로 대체하면서 정보가 사라집니다. 특히 코딩에서 치명적:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에러 메시지 사라짐&lt;/li&gt;
&lt;li&gt;의존성 버전 정보 손실&lt;/li&gt;
&lt;li&gt;설정 파일 내용 누락&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 글자 차이로 작동/비작동이 갈리는 코드에서 이건 재앙입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 실패 이력이 계속 남는다&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시도한 접근법 3개 중 2개 실패&lt;/li&gt;
&lt;li&gt;디버깅 10번 중 9번 헛수고&lt;/li&gt;
&lt;li&gt;열어본 파일 5개 중 3개 관련 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 &quot;쓰레기 이력&quot;이 압축되어도 컨텍스트를 차지하고, 요약 과정에서 더 뭉개집니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Handoff 방식: 깨끗하게 넘기기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 아이디어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 끝에 handoff.md 작성 &amp;rarr; 새 세션에서 읽고 이어서 작업&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;✅ 100% 새 컨텍스트 (실패 이력 제로)&lt;/li&gt;
&lt;li&gt;✅ 핵심만 정제해서 전달&lt;/li&gt;
&lt;li&gt;✅ 뭘 피해야 하는지 명확&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude뿐 아니라 ChatGPT, Gemini, Cursor 등 &lt;b&gt;어떤 AI든&lt;/b&gt; 이동 가능합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;언제, 어떻게?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;60% 룰&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트 &lt;b&gt;60% 도달 시&lt;/b&gt; handoff 준비하세요. 80-90%는 이미 늦습니다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;/context  # 상태 확인
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Handoff 템플릿 (복붙용)&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;vala&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;# Handoff - 2026-04-30

## 완료된 것
- FastAPI 백엔드 구축 (/api/users, /api/posts)
- PostgreSQL + JWT 인증 완료

## 해결한 이슈
- CORS: origins 화이트리스트로 해결
- Pydantic v2 마이그레이션 완료

## 지금 하는 중
- WebSocket 실시간 알림 구현
- Redis pub/sub 연결 완료, 브로드캐스트 로직 필요

## 다음 할 일
1. /ws/notifications 엔드포인트 구현
2. Redis 메시지 리스너 작성
3. 프론트엔드 연결 테스트

## 중요 코드
```python
# database.py
SQLALCHEMY_DATABASE_URL = &quot;postgresql://user:pass@localhost/dbname&quot;

# config.py  
REDIS_URL = &quot;redis://localhost:6379&quot;
JWT_SECRET = os.getenv(&quot;JWT_SECRET&quot;)
```

## 시도했지만 실패 (다시 하지 말 것)
- ❌ socket.io &amp;rarr; FastAPI와 충돌
- ❌ 동기 Redis 클라이언트 &amp;rarr; async에서 블로킹&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Claude에게 요청&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;지금까지 작업을 handoff.md로 정리해줘:
1. 완료된 것
2. 해결한 이슈  
3. 지금 하는 중
4. 다음 할 일
5. 중요 코드
6. 실패한 시도 (반복 방지)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;비교표&lt;/h2&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;자동 /compact&lt;/td&gt;
&lt;td&gt;95%&lt;/td&gt;
&lt;td&gt;자동 실행&lt;/td&gt;
&lt;td&gt;최악의 순간에 요약&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;수동 /compact&lt;/td&gt;
&lt;td&gt;급할 때&lt;/td&gt;
&lt;td&gt;즉시 계속&lt;/td&gt;
&lt;td&gt;손실 + 실패 이력 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/clear&lt;/td&gt;
&lt;td&gt;다른 작업&lt;/td&gt;
&lt;td&gt;완전 리셋&lt;/td&gt;
&lt;td&gt;이전 내용 소실&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Handoff&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;같은 작업&lt;/td&gt;
&lt;td&gt;깨끗함 + 핵심 보존&lt;/td&gt;
&lt;td&gt;5분 투자 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 팁&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;LLM 간 이동도 가능 !!!&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cursor/Codex &amp;rarr; handoff.md 작성 &amp;rarr; Claude Code CLI로 이동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완벽하게 작동합니다. 구조화된 문서니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://code.claude.com/docs/en/best-practices&quot;&gt;Claude Code Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://claude.com/blog/using-claude-code-session-management-and-1m-context&quot;&gt;Session Management Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jdhodges.com/blog/ai-session-handoffs-keep-context-across-conversations/&quot;&gt;AI Handoffs Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>AI</category>
      <category>Claude cli</category>
      <category>claude code</category>
      <category>Clear</category>
      <category>compact</category>
      <category>context</category>
      <category>handoff</category>
      <author>포도쿵야</author>
      <guid isPermaLink="true">https://podokungya.tistory.com/19</guid>
      <comments>https://podokungya.tistory.com/19#entry19comment</comments>
      <pubDate>Thu, 30 Apr 2026 17:59:14 +0900</pubDate>
    </item>
    <item>
      <title>[개발 기초] Nexus란 무엇인가? 실무 개발자 관점으로 쉽게 이해하기</title>
      <link>https://podokungya.tistory.com/18</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에 처음 투입되면 팀원들에게 이런 말을 듣게 됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;패키지는 Nexus 통해서 받으세요&quot;&lt;br /&gt;&quot;빌드한 Artifact Nexus에 올려주세요&quot;&lt;br /&gt;&quot;CI 빌드가 Nexus 바라보고 있어요&quot;&lt;br /&gt;ㅤ&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 이런 생각이 들죠. &lt;b&gt;&quot;그냥 &lt;code&gt;npm install&lt;/code&gt;, &lt;code&gt;pip install&lt;/code&gt; 하면 되는데 왜 굳이...?&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저도 처음엔 그랬습니다. 하지만 이해하고 보니, Nexus는 단순한 개발 편의 도구가 아니라 &lt;b&gt;운영 안정성과 패키지 관리를 위한 핵심 인프라&lt;/b&gt;였습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Nexus란 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Sonatype Nexus Repository&lt;/b&gt;의 줄임말로, 한 줄로 정의하면 이렇습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;패키지(Dependency)와 빌드 산출물(Artifact)을 관리하는 저장소 서버&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 비유하자면 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Git:&lt;/b&gt; 소스코드 저장소 (코드 창고)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Nexus:&lt;/b&gt; 패키지/아티팩트 저장소 (패키지 창고)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❌ 많이 오해하는 부분들&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. &quot;코드에 Nexus 라이브러리를 설치하는 건가?&quot;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아닙니다.&lt;/b&gt; &lt;code&gt;npm install nexus&lt;/code&gt; 같은 개념이 아닙니다. Nexus는 별도 서버로 운영되는 '저장소 서비스'입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. &quot;내 코드에서 Nexus를 호출해야 하나?&quot;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그렇지 않습니다.&lt;/b&gt; 소스코드 내부에는 Nexus 관련 코드가 들어가지 않습니다. 대신 프로젝트의 &lt;b&gt;설정 파일&lt;/b&gt;에 저장소 위치만 명시합니다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;# .npmrc 예시
registry=http://nexus.company.com/repository/npm-group/&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Nexus의 핵심 역할 2가지&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① 패키지 프록시 (Proxy)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 저장소(npm, PyPI, Maven Central 등)에서 패키지를 받아와 사내 Nexus 서버에 &lt;b&gt;캐싱&lt;/b&gt;합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt; 외부망 장애 시에도 내부 캐시를 통해 빌드가 가능하며, 외부 서버 통신 속도 문제에서 자유로워집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② 빌드 산출물 저장 (Hosted)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 결과물(JAR, Docker Image, Python Wheel 등)을 외부가 아닌 사내 Nexus에 업로드하여 관리합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;흐름:&lt;/b&gt; &lt;code&gt;Build&lt;/code&gt; &amp;rarr; &lt;code&gt;Nexus 저장&lt;/code&gt; &amp;rarr; &lt;code&gt;배포(Deploy)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  실무에서의 개발/배포 흐름&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_b8uvzeb8uvzeb8uv.png&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdIM3n/dJMcaaE82d2/pZMGkKPmHgF10CSDzifo9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdIM3n/dJMcaaE82d2/pZMGkKPmHgF10CSDzifo9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdIM3n/dJMcaaE82d2/pZMGkKPmHgF10CSDzifo9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdIM3n%2FdJMcaaE82d2%2FpZMGkKPmHgF10CSDzifo9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1408&quot; height=&quot;768&quot; data-filename=&quot;Gemini_Generated_Image_b8uvzeb8uvzeb8uv.png&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개발할 때&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평소와 똑같이 &lt;code&gt;npm install&lt;/code&gt;, &lt;code&gt;npm run build&lt;/code&gt;를 수행합니다. 단지 외부 저장소 대신 &lt;b&gt;내부 Nexus를 바라보도록 설정&lt;/b&gt;되어 있을 뿐입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CI/CD 파이프라인&lt;/h3&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Git Push 
   &amp;darr;
Jenkins/GitLab CI 
   &amp;darr;
Build (Dependency는 Nexus에서 받고, 결과물은 Nexus에 올림)
   &amp;darr;
Deploy (Nexus에서 완성된 산출물만 가져와 배포)&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ Git과 Nexus, 역할의 차이 (핵심!)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 분이 헷갈려 하는 부분입니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;구분&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;역할&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;관리 대상&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;Git&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;&quot;어떤 버전&quot;&lt;/b&gt;을 쓸지 관리&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;package-lock.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;Nexus&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;&quot;어디서&quot;&lt;/b&gt; 받을지 관리&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;jar&lt;/code&gt;, &lt;code&gt;docker image&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;ㅤ&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;즉, &lt;b&gt;Git은 코드의 형상&lt;/b&gt;을 관리하고, &lt;b&gt;Nexus는 그 코드가 빌드/실행되기 위한 재료와 결과물&lt;/b&gt;을 관리합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  왜 Nexus를 쓰는가?&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;외부 장애 영향 최소화:&lt;/b&gt; 외부 저장소가 죽어도 내부 캐시로 빌드 유지 가능.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;빌드 재현성 보장:&lt;/b&gt; 팀원 모두가 동일한 버전의 패키지를 사용하도록 통제.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안과 통제:&lt;/b&gt; 검증되지 않은 패키지 사용 방지.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;내부 공통 라이브러리 배포:&lt;/b&gt; 팀 간 &lt;code&gt;common-utils&lt;/code&gt;, &lt;code&gt;shared-sdk&lt;/code&gt; 등 공유 패키지 관리 용이.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;폐쇄망 대응:&lt;/b&gt; 금융, 공공 프로젝트 등 외부 접속이 차단된 환경에서 필수.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nexus는 개발 편의 도구라기보다 &lt;b&gt;&quot;빌드와 배포를 안정화하는 인프라 보험&quot;&lt;/b&gt;입니다. 평소엔 존재감이 없지만, 사고가 나면 왜 있는지 비로소 보이는 도구&lt;/p&gt;</description>
      <category>DevOps/Server</category>
      <author>포도쿵야</author>
      <guid isPermaLink="true">https://podokungya.tistory.com/18</guid>
      <comments>https://podokungya.tistory.com/18#entry18comment</comments>
      <pubDate>Wed, 29 Apr 2026 14:23:05 +0900</pubDate>
    </item>
    <item>
      <title>GA4(Google Analytics 4)와 GTM(Google Tag Manager) 완벽 가이드: 개념부터 실전까지</title>
      <link>https://podokungya.tistory.com/17</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_62agkr62agkr62ag.png&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/np6GJ/dJMb99TIR3X/MHJbweKgKL3PbdizdTjuy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/np6GJ/dJMb99TIR3X/MHJbweKgKL3PbdizdTjuy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/np6GJ/dJMb99TIR3X/MHJbweKgKL3PbdizdTjuy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnp6GJ%2FdJMb99TIR3X%2FMHJbweKgKL3PbdizdTjuy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1408&quot; height=&quot;768&quot; data-filename=&quot;Gemini_Generated_Image_62agkr62agkr62ag.png&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹사이트나 앱을 운영하다 보면 &quot;방문자가 얼마나 오는지&quot;, &quot;어떤 페이지를 많이 보는지&quot; 궁금해집니다. 이런 데이터를 수집하고 분석하는 도구가 바로 Google Analytics 4(GA4)와 Google Tag Manager(GTM)입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;GA4와 GTM이 뭔가요?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GA4 (Google Analytics 4)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글이 제공하는 &lt;b&gt;웹/앱 분석 플랫폼&lt;/b&gt;입니다. 사용자 행동 데이터를 수집하고 시각화된 대시보드로 보여줍니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GTM (Google Tag Manager)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;태그(추적 코드) 관리 도구&lt;/b&gt;입니다. 코드 수정 없이 GA4, 광고 픽셀 등을 웹사이트에 추가하고 관리할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 GA4 GTM&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;역할&lt;/td&gt;
&lt;td&gt;데이터 분석 플랫폼&lt;/td&gt;
&lt;td&gt;태그 관리 도구&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;필수 여부&lt;/td&gt;
&lt;td&gt;GA4만으로도 분석 가능&lt;/td&gt;
&lt;td&gt;선택사항 (편의성 제공)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터는 어떻게 수집되나요?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 흐름&lt;/h3&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;┌─────────────┐
│ 사용자 행동  │ (클릭, 페이지 이동)
└──────┬──────┘
       &amp;darr;
┌─────────────┐
│  gtag.js    │ (자동 실행)
└──────┬──────┘
       &amp;darr;
┌─────────────┐
│ Google 서버 │ (실시간 전송)
└──────┬──────┘
       &amp;darr;
┌─────────────┐
│ GA4 대시보드 │ (자동 집계)
└──────┬──────┘
       &amp;darr; (선택)
┌─────────────┐
│  BigQuery   │ (Raw 데이터 저장)
└─────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 삽입 예시&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX');
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이게 끝!&lt;/b&gt; 코드만 넣으면 페이지뷰, 스크롤, 클릭 등이 자동으로 수집됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 이벤트가 필요하면 이렇게 추가할 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;// 특정 버튼 클릭 추적
gtag('event', 'button_click', {
  'button_name': 'signup'
});
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실시간일까요? 자동일까요?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 완전 자동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gtag.js 코드만 삽입하면 자동으로 수집됩니다. 수동 주입은 필요 없고, 커스텀 이벤트만 선택적으로 추가하면 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 실시간 전송&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 행동이 발생하면 즉시 Google 서버로 전송됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;GA4 실시간 보고서&lt;/b&gt;: 최근 30분 데이터&lt;/li&gt;
&lt;li&gt;&lt;b&gt;일반 보고서&lt;/b&gt;: 24~48시간 후 완전 집계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;BigQuery 연동&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;기본&lt;/b&gt;: 매일 1회 전날 데이터 적재 (무료)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스트리밍&lt;/b&gt;: 실시간 적재 가능 (유료)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;BigQuery는 왜 필요한가요?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GA4만 사용할 때&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;✅ 무료, 설정 간단&lt;/li&gt;
&lt;li&gt;✅ 기본 대시보드 제공&lt;/li&gt;
&lt;li&gt;❌ 복잡한 분석 불가&lt;/li&gt;
&lt;li&gt;❌ 데이터 보관 14개월만 가능&lt;/li&gt;
&lt;li&gt;❌ 다른 데이터와 결합 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;BigQuery 연동 시&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;✅ Raw 데이터 접근 가능&lt;/li&gt;
&lt;li&gt;✅ SQL로 자유로운 분석&lt;/li&gt;
&lt;li&gt;✅ 무제한 데이터 보관&lt;/li&gt;
&lt;li&gt;✅ 다른 데이터와 결합 분석&lt;/li&gt;
&lt;li&gt;✅ 커스텀 대시보드 제작&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실전 예시&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- 특정 페이지 방문자의 전환율 분석
SELECT 
  event_date,
  COUNT(DISTINCT user_pseudo_id) as visitors,
  COUNTIF(event_name = 'purchase') as conversions
FROM `프로젝트.analytics_XXXXX.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20240101' AND '20240131'
GROUP BY event_date;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결론&lt;/b&gt;: GA4는 집계된 대시보드, BigQuery는 Raw 데이터 창고입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;GA4 말고 다른 선택지는?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도구 특징 비용 장점 단점&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Matomo&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;오픈소스&lt;/td&gt;
&lt;td&gt;무료 (셀프)&lt;/td&gt;
&lt;td&gt;데이터 완전 소유&lt;/td&gt;
&lt;td&gt;설치/관리 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Plausible&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;경량&lt;/td&gt;
&lt;td&gt;$9~/월&lt;/td&gt;
&lt;td&gt;쿠키 없음, 빠름&lt;/td&gt;
&lt;td&gt;기능 단순&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Umami&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;오픈소스&lt;/td&gt;
&lt;td&gt;무료 (셀프)&lt;/td&gt;
&lt;td&gt;가볍고 빠름&lt;/td&gt;
&lt;td&gt;커뮤니티 작음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Mixpanel&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;제품 분석 특화&lt;/td&gt;
&lt;td&gt;$25~/월&lt;/td&gt;
&lt;td&gt;이벤트 추적 강력&lt;/td&gt;
&lt;td&gt;마케팅 분석 약함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;선택 가이드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;GA4&lt;/b&gt;: 무료로 시작, Google Ads 연동, BigQuery 활용 계획&lt;/li&gt;
&lt;li&gt;&lt;b&gt;오픈소스&lt;/b&gt;: 데이터 주권 중요, 쿠키 없는 추적&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유료 SaaS&lt;/b&gt;: 제품 분석 깊이 필요, 빠른 속도&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 웨어하우스 vs 일반 DB&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;BigQuery 아키텍처&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;┌──────────────────────────────────────┐
│         BigQuery 아키텍처             │
├──────────────────────────────────────┤
│                                       │
│  ┌─────────┐        ┌─────────┐     │
│  │ Storage │◄──────►│ Compute │     │
│  │(Colossus)│       │(Dremel) │     │
│  └─────────┘        └─────────┘     │
│       ▲                  ▲           │
│       │                  │           │
│       └──────┬───────────┘           │
│              │                       │
│         Jupiter Network              │
│        (초고속 네트워크)               │
└──────────────────────────────────────┘
     Storage와 Compute가 분리되어
     독립적으로 확장 가능!
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일반 데이터베이스 (MySQL, PostgreSQL)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실시간 처리&lt;/b&gt;를 위한 DB입니다. 주문, 회원가입 같은 서비스 운영에 사용됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터 웨어하우스 (BigQuery)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대규모 분석&lt;/b&gt;을 위한 DB입니다. 수억 건의 데이터를 집계하고 복잡한 분석 쿼리를 처리합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 일반 DB 데이터 웨어하우스&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;주 목적&lt;/td&gt;
&lt;td&gt;서비스 운영&lt;/td&gt;
&lt;td&gt;데이터 분석&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;쿼리 패턴&lt;/td&gt;
&lt;td&gt;간단, 빠른 응답&lt;/td&gt;
&lt;td&gt;복잡, 오래 걸려도 OK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 볼륨&lt;/td&gt;
&lt;td&gt;GB ~ TB&lt;/td&gt;
&lt;td&gt;TB ~ PB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비유&lt;/b&gt;: DB는 편의점 계산대(빠른 처리), DW는 물류창고(대량 보관/분석)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Analytics</category>
      <category>Ga</category>
      <category>Google analysis</category>
      <category>GTM</category>
      <category>대시보드</category>
      <category>집계수집</category>
      <category>추적관리</category>
      <author>포도쿵야</author>
      <guid isPermaLink="true">https://podokungya.tistory.com/17</guid>
      <comments>https://podokungya.tistory.com/17#entry17comment</comments>
      <pubDate>Tue, 28 Apr 2026 18:00:41 +0900</pubDate>
    </item>
    <item>
      <title>Sparrow 보안 검사 탈락 이유: Git에 올린 DB 비밀번호 암호화로 해결하기</title>
      <link>https://podokungya.tistory.com/16</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하다 보면 데이터베이스 비밀번호, API 키 등 민감한 정보를 .env 파일에 평문으로 저장하고 Git에 올리는 경우가 많습니다. 이번 글에서는 &lt;b&gt;환경 변수의 민감정보를 암호화하여 형상 관리 시스템에서 평문을 완전히 제거하는 방법&lt;/b&gt;을 다룹니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 암호화가 필요한가?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기존 방식의 문제점&lt;/h3&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;# .env.prd
DB_PASSWORD=myRealPassword123
API_KEY=sk-proj-abc123def456
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;형상 관리 노출&lt;/b&gt;: Git 히스토리에 영구 기록&lt;/li&gt;
&lt;li&gt;&lt;b&gt;접근 권한 통제 불가&lt;/b&gt;: 저장소 접근 권한만 있으면 모든 비밀번호 열람 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안 스캔 실패&lt;/b&gt;: Sparrow, SonarQube 등에서 취약점으로 탐지&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개선된 구조&lt;/h3&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;# .env.prd (Git에 저장)
DB_PASSWORD_ENC=gAAAAABmK9x3j2Lp4... (암호문)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;# 운영 서버 (Git과 분리)
/secure/secret/config.prd.key (복호화 키)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ Git에는 암호문만, 복호화 키는 서버에만 존재&lt;br /&gt;✅ 저장소 단독 유출 시 원문 확인 불가능&lt;br /&gt;✅ 환경별 키 분리로 다단계 보안 구성&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 개념&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대칭키 암호화 (Fernet)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python의 Fernet은 &lt;b&gt;같은 키로 암호화하고 복호화&lt;/b&gt;하는 방식입니다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;from cryptography.fernet import Fernet

key = Fernet.generate_key()
cipher = Fernet(key)

# 암호화
encrypted = cipher.encrypt(b&quot;myPassword&quot;)  # gAAAAABmK9x3j2Lp4...

# 복호화 (같은 키 필요!)
decrypted = cipher.decrypt(encrypted)  # myPassword
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;키 분리 원칙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;암호문과 복호화 키를 같은 저장소에 두지 않는 것&lt;/b&gt;이 핵심입니다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;✅ 좋은 예:
Git Repository &amp;rarr; .env (암호문만)
운영 서버 &amp;rarr; /secure/secret/config.prd.key (복호화 키)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현 가이드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 마스터 키 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경별로 각각 다른 키를 생성합니다.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;python3 -c &quot;from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())&quot;
# 출력: cHJvZHVjdGlvbmtleXNlY3VyZQ==
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 생성된 키는 &lt;b&gt;절대 Git에 올리지 마세요!&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 서버에 키 파일 배치&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 보안 디렉토리 생성
sudo mkdir -p /secure/app/secret

# 키 파일 저장
echo &quot;cHJvZHVjdGlvbmtleXNlY3VyZQ==&quot; &amp;gt; /secure/app/secret/config.prd.key

# 권한 설정 (소유자만 읽기 가능)
chmod 700 /secure/app/secret
chmod 600 /secure/app/secret/config.prd.key
chown apiuser:apiuser /secure/app/secret/config.prd.key
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 비밀번호 암호화&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;from cryptography.fernet import Fernet

key = b'cHJvZHVjdGlvbmtleXNlY3VyZQ=='
cipher = Fernet(key)

# 암호화
passwords = {
    'DB_PASSWORD': 'myDBpass123',
    'API_KEY': 'sk-proj-abc123'
}

for name, pw in passwords.items():
    encrypted = cipher.encrypt(pw.encode()).decode()
    print(f&quot;{name}_ENC={encrypted}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. .env 파일 수정&lt;/h3&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;# 변경 전
DB_PASSWORD=myDBpass123

# 변경 후
DB_PASSWORD_ENC=gAAAAABmK9x3j2Lp4QmN8vZ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. ConfigLoader 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 시작 시 암호문을 자동으로 복호화합니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# config_loader.py
import os
from dotenv import load_dotenv
from cryptography.fernet import Fernet

class ConfigLoader:
    @staticmethod
    def load_config(env_file=&quot;.env&quot;):
        # .env 파일 로드
        load_dotenv(env_file)
        
        # 키 파일 경로 확인
        key_file = os.getenv(&quot;APP_CONFIG_KEY_FILE&quot;)
        if not key_file or not os.path.exists(key_file):
            print(&quot;⚠️ 복호화 키 없음&quot;)
            return
        
        # 키 읽기
        with open(key_file, 'r') as f:
            key = f.read().strip()
        cipher = Fernet(key.encode())
        
        # _ENC로 끝나는 환경변수 복호화
        for env_var in list(os.environ.keys()):
            if env_var.endswith('_ENC'):
                try:
                    encrypted_value = os.getenv(env_var)
                    decrypted = cipher.decrypt(encrypted_value.encode()).decode()
                    
                    # DB_PASSWORD_ENC -&amp;gt; DB_PASSWORD
                    original_name = env_var[:-4]
                    os.environ[original_name] = decrypted
                    print(f&quot;✓ 복호화 완료: {original_name}&quot;)
                except Exception as e:
                    print(f&quot;✗ 복호화 실패: {env_var}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 메인 코드에 적용&lt;/h3&gt;
&lt;pre class=&quot;capnproto&quot;&gt;&lt;code&gt;# main.py
from config_loader import ConfigLoader
import os

# 앱 시작 시 가장 먼저 실행
ConfigLoader.load_config()

# 기존 코드는 수정 없이 그대로 사용
db_password = os.getenv(&quot;DB_PASSWORD&quot;)  # 자동으로 복호화된 값
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 실행 스크립트 수정&lt;/h3&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;# start.sh
#!/bin/bash

ENV=${ENV:-prd}

# 복호화 키 파일 경로 지정
export APP_CONFIG_KEY_FILE=&quot;/secure/app/secret/config.${ENV}.key&quot;

python3 main.py
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;운영 중 비밀번호 변경하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 비밀번호가 변경되면 서버에서 바로 암호화할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;# 서버 SSH 접속 후 실행
python3 &amp;lt;&amp;lt; 'EOF'
from cryptography.fernet import Fernet
import os

key_file = os.getenv('APP_CONFIG_KEY_FILE')
with open(key_file, 'r') as f:
    key = f.read().strip()

cipher = Fernet(key.encode())
new_password = input('새 비밀번호 입력: ')
encrypted = cipher.encrypt(new_password.encode()).decode()

print(f'\n=== 새로운 암호문 ===')
print(encrypted)
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력된 암호문을 .env.prd에 업데이트하고 재배포하면 끝입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;보안 스캔 대응&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sparrow나 SonarQube에서 PASSWORD_ENC=gAAAA... 패턴을 탐지할 경우:&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;해당 값은 Fernet 암호문이며, 복호화 키는 저장소에 포함되지 않고 
운영 서버의 제한된 파일 권한(600)으로 분리 보관됩니다.
저장소 단독 유출 시 원문 복호화가 불가능합니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;한계와 대안&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이 방식의 한계&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 접근 권한만 있으면 키 파일 읽기 가능&lt;/li&gt;
&lt;li&gt;중앙화된 키 관리 시스템 없음&lt;/li&gt;
&lt;li&gt;키 로테이션이 수동 작업&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;더 강력한 보안이 필요하다면&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AWS Secrets Manager / HashiCorp Vault&lt;/b&gt; 같은 전문 솔루션을 고려하세요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중앙 집중식 키 관리&lt;/li&gt;
&lt;li&gt;자동 로테이션&lt;/li&gt;
&lt;li&gt;완전한 감사 로그&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 추가 인프라와 비용이 필요합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 &lt;b&gt;중소규모 프로젝트에서 실무적으로 적용하기 좋은 균형잡힌 접근법&lt;/b&gt;입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;큰 인프라 변경 없이 즉시 적용 가능&lt;/li&gt;
&lt;li&gt;기존 코드 수정 최소화&lt;/li&gt;
&lt;li&gt;보안 인증 통과 가능&lt;/li&gt;
&lt;li&gt;운영 부담 적음&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/Security</category>
      <category>enV</category>
      <category>Fernet</category>
      <category>Sparrow</category>
      <category>보안</category>
      <category>암복호화</category>
      <author>포도쿵야</author>
      <guid isPermaLink="true">https://podokungya.tistory.com/16</guid>
      <comments>https://podokungya.tistory.com/16#entry16comment</comments>
      <pubDate>Tue, 28 Apr 2026 16:49:12 +0900</pubDate>
    </item>
    <item>
      <title>Claude에 &amp;quot;코드 리뷰해줘&amp;quot;만 입력하는 당신을 위한 프롬프트 작성법</title>
      <link>https://podokungya.tistory.com/15</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코드 리뷰 도구들이 보편화되면서 많은 개발자들이 활용하고 있지만, 정작 리뷰 품질에 대한 불만도 함께 증가하고 있습니다. &quot;쓸데없는 지적만 잔뜩&quot;, &quot;실제 버그는 못 찾고&quot;, &quot;이미 고려한 부분을 또 지적&quot; 같은 경험, 한 번쯤 있으시죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제의 핵심은 &lt;b&gt;AI가 부족한 게 아니라 컨텍스트가 부족&lt;/b&gt;하다는 점입니다. 이 글에서는 AI 리뷰의 정확도를 실질적으로 높이는 방법을 정리했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Intent + Decisions 섹션 제공하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 코드만 보고서는 &lt;b&gt;왜 이렇게 작성했는지&lt;/b&gt; 알 수 없습니다. 의도와 설계 결정을 명시하면 리뷰 품질이 급격히 개선됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;템플릿 구조&lt;/h3&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;## Intent
이 변경의 목적: [무엇을 왜 바꾸는지]

## Decisions
- A 대신 B를 선택한 이유
- 알고 있는 한계점
- 의도적으로 처리하지 않은 케이스
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실제 예시&lt;/h3&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;## Intent
사용자 검색 API의 응답 속도를 개선하기 위해 Elasticsearch 도입

## Decisions
- Redis 대신 ES를 선택한 이유: 전문 검색(fuzzy matching) 필요
- 알고 있는 한계점: 실시간 동기화 아닌 5초 지연 허용
- initial indexing 비용은 배치로 주말에 처리 예정
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 컨텍스트를 주면 AI가 &quot;왜 캐시 안 써요?&quot; 같은 이미 고려된 사항을 반복 지적하는 일이 줄어듭니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 리뷰 범위를 명시적으로 지정하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;막연한 요청은 막연한 답변을 낳습니다. &lt;b&gt;무엇을 중점적으로 볼지&lt;/b&gt; 구체적으로 지정하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❌ 나쁜 요청 ✅ 좋은 요청&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&quot;이 코드 리뷰해줘&quot;&lt;/td&gt;
&lt;td&gt;&quot;N+1 쿼리 문제와 엣지케이스 중심으로 봐줘&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&quot;어때?&quot;&lt;/td&gt;
&lt;td&gt;&quot;OWASP Top 10 보안 취약점 관점에서 리뷰해줘&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&quot;개선점 있을까?&quot;&lt;/td&gt;
&lt;td&gt;&quot;동시성 이슈와 race condition 위주로 검토해줘&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자주 쓰이는 리뷰 포커스&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;로직 오류&lt;/b&gt;: 경계값 처리, null 처리, 예외 상황&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능&lt;/b&gt;: N+1 쿼리, 불필요한 반복문, 메모리 누수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안&lt;/b&gt;: SQL Injection, XSS, 인증/인가 누락&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가독성&lt;/b&gt;: 네이밍, 함수 분리, 주석 필요성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 커버리지&lt;/b&gt;: 누락된 테스트 케이스 식별&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 관련 파일도 함께 제공하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경된 파일만 던져주면 AI는 &lt;b&gt;전체 그림을 볼 수 없습니다&lt;/b&gt;. 다음 파일들을 함께 제공하면 정확도가 크게 향상됩니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;호출하는 쪽 코드&lt;/b&gt;: 이 함수가 어떻게 사용되는지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유사한 기존 구현체&lt;/b&gt;: 프로젝트의 기존 패턴 참고&lt;/li&gt;
&lt;li&gt;&lt;b&gt;관련 테스트 코드&lt;/b&gt;: 어떤 케이스를 이미 커버했는지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설정 파일&lt;/b&gt;: 환경변수, DB 스키마 등&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시&lt;/h3&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;## 변경 파일
- src/services/payment.service.ts (새로 작성)

## 참고 파일
- src/services/order.service.ts (유사한 트랜잭션 처리 패턴)
- src/controllers/payment.controller.ts (이 서비스를 호출하는 컨트롤러)
- tests/payment.test.ts (작성한 테스트)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 AI가 프로젝트의 컨벤션을 이해하고, 기존 코드와의 일관성도 검토할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 불확실한 지적은 질문 형태로 받기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 때때로 &lt;b&gt;확신 없이 지적&lt;/b&gt;합니다. 하지만 단정적으로 표현하면 개발자는 이를 진짜 문제로 오해하게 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;효과적인 요청 방법&lt;/h3&gt;
&lt;pre class=&quot;scilab&quot;&gt;&lt;code&gt;&quot;확실하지 않은 부분은 '이 부분은 X가 맞나요?' 형태로 질문으로 알려줘.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비교&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;❌ &quot;이 코드는 race condition이 발생합니다&quot; &amp;rarr; 개발자가 검증해야 할 부담&lt;/li&gt;
&lt;li&gt;✅ &quot;동시 요청 시 race condition 가능성이 있지 않나요?&quot; &amp;rarr; 검토 유도&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식으로 **false positive(오탐)**를 줄이고, AI와 개발자 간 신뢰를 높일 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 실전 통합 템플릿&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 모든 원칙을 조합한 실전 템플릿입니다:&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;다음 코드 리뷰해줘.

## Intent
[변경 목적과 배경]

## Decisions
- [주요 설계 결정 이유]
- [알고 있는 트레이드오프]
- [의도적으로 미처리한 부분]

## 리뷰 포커스
- 로직 오류 및 엣지케이스
- 성능 문제 (특히 N+1 쿼리)
- 보안 취약점 (OWASP 기준)

## 관련 파일
- 변경: src/services/user.service.ts
- 참고: src/services/auth.service.ts, tests/user.test.ts

확실하지 않은 부분은 질문 형태로 알려줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. Claude Code의 /review 기능 활용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code를 사용한다면 내장된 리뷰 명령어를 활용하세요:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;/review&lt;/b&gt;: 현재 브랜치의 변경사항 자동 분석&lt;/li&gt;
&lt;li&gt;&lt;b&gt;/ultrareview &amp;lt;PR번호&amp;gt;&lt;/b&gt;: GitHub PR에 대한 심층 멀티에이전트 리뷰&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능들은 git diff를 자동으로 읽고, 변경된 파일들의 컨텍스트를 스스로 수집하기 때문에 위에서 설명한 &quot;관련 파일 제공&quot;을 자동화할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 피드백 루프 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 리뷰의 품질은 &lt;b&gt;지속적인 피드백&lt;/b&gt;으로 개선됩니다. 리뷰 후 다음을 기록하세요:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;✅ &lt;b&gt;실제 버그였던 지적&lt;/b&gt;: &quot;nullable 체크 누락 &amp;rarr; NPE 발생 가능&quot; &amp;rarr; 반영&lt;/li&gt;
&lt;li&gt;❌ &lt;b&gt;오해였던 지적&lt;/b&gt;: &quot;중복 코드&quot;라고 했지만 의도적 분리 &amp;rarr; 피드백&lt;/li&gt;
&lt;li&gt;  &lt;b&gt;애매했던 지적&lt;/b&gt;: &quot;성능 이슈 가능성&quot; &amp;rarr; 측정 후 판단&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인의 &lt;b&gt;리뷰 반영률&lt;/b&gt;을 추적하면서, 어떤 유형의 요청이 좋은 리뷰를 이끌어내는지 패턴을 찾아가는 것이 중요합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;검증 및 추가 고려사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;맥락 윈도우 제한&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 AI는 한 번에 처리할 수 있는 토큰(텍스트) 양에 제한이 있습니다. 대규모 PR은 여러 번 나눠서 리뷰하거나, 파일 단위로 쪼개서 요청하는 것이 효과적입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;할루시네이션(Hallucination) 주의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 존재하지 않는 라이브러리 메서드나 잘못된 베스트 프랙티스를 제안할 수 있습니다. 특히 최신 프레임워크나 마이너 라이브러리에서 자주 발생하므로, &lt;b&gt;리뷰 결과를 맹신하지 말고 공식 문서로 교차 검증&lt;/b&gt;하세요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도메인 지식 전달&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비즈니스 로직이 복잡한 경우, 도메인 용어와 규칙을 간단히 설명해주면 도움이 됩니다:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;## Domain Context
- &quot;정산&quot;은 매월 1일 00:00에 자동 실행
- &quot;파트너사&quot;는 최대 3개까지만 등록 가능
- 환불은 구매 후 7일 이내만 가능
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코드 리뷰는 &lt;b&gt;도구&lt;/b&gt;입니다. 망치가 좋다고 집이 저절로 지어지지 않듯, 컨텍스트를 얼마나 잘 제공하느냐에 따라 결과물의 질이 달라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 소개한 방법들을 적용하면 AI 리뷰의 정확도를 체감 가능한 수준으로 끌어올릴 수 있습니다. 특히 &lt;b&gt;Intent + Decisions 명시&lt;/b&gt;, &lt;b&gt;리뷰 범위 지정&lt;/b&gt;, &lt;b&gt;질문 형태 요청&lt;/b&gt;은 바로 적용 가능한 실전 팁이니 다음 코드 리뷰 때 꼭 시도해보세요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 링크&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/creatrip/ai-%EB%A6%AC%EB%B7%B0%EB%A5%BC-%EC%8B%A0%EB%A2%B0%ED%95%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C%EC%9A%94-5eac4707a852&quot;&gt;AI 리뷰를 신뢰할 수 있을까요?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>AI</category>
      <category>ai 코드리뷰</category>
      <category>claude</category>
      <category>claude code</category>
      <category>claude code review</category>
      <category>context</category>
      <category>클로드 코드</category>
      <author>포도쿵야</author>
      <guid isPermaLink="true">https://podokungya.tistory.com/15</guid>
      <comments>https://podokungya.tistory.com/15#entry15comment</comments>
      <pubDate>Fri, 24 Apr 2026 10:53:12 +0900</pubDate>
    </item>
    <item>
      <title>Claude Code &amp;middot; Codex 살살 녹는 토큰 낭비 없이 쓰는 법 - 내가 실제로 적용한 환경 설정</title>
      <link>https://podokungya.tistory.com/14</link>
      <description>&lt;h2&gt;  핵심 3줄 요약&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CLAUDE_CODE_GLOB_NO_IGNORE는 반드시 false로&lt;/strong&gt; (node_modules 유입 방지)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;출력 제한 설정&lt;/strong&gt;: BASH_MAX_OUTPUT_LENGTH=10000, MAX_MCP_OUTPUT_TOKENS=8000&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;작업 패턴에 맞게 선택 적용&lt;/strong&gt; — 일괄 적용 시 품질 저하&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;도입&lt;/h2&gt;
&lt;p&gt;Claude Code와 Codex를 쓸수록 토큰이 생각보다 빠르게 찬다는 걸 체감했습니다. 한 세션에 10만 토큰을 쓴다면, 이 중 3~4만 토큰이 &lt;strong&gt;설정 때문&lt;/strong&gt;일 수 있습니다. 분석 프롬프트로 내 설정을 점검하고, 품질 저하 없이 적용 가능한 설정들을 실제로 반영한 과정을 공유합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1. 설정 파일 위치&lt;/h2&gt;
&lt;h3&gt;Claude Code&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;파일&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/.claude/settings.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;글로벌 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.claude/settings.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;프로젝트 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Codex&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;파일&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/.codex/config.toml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;글로벌 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.codex/config.toml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;프로젝트 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;2. 토큰이 새는 주요 항목&lt;/h2&gt;
&lt;h3&gt;Claude Code&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;기본값&lt;/th&gt;
&lt;th&gt;영향&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLAUDE_CODE_GLOB_NO_IGNORE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;true 시 node_modules 탐색 → 토큰 폭증&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BASH_MAX_OUTPUT_LENGTH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;무제한&lt;/td&gt;
&lt;td&gt;Bash 출력 전량 유입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MAX_MCP_OUTPUT_TOKENS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;무제한&lt;/td&gt;
&lt;td&gt;MCP 응답 전량 유입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;includeGitInstructions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;매 세션 git 지침 주입&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Codex&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;기본값&lt;/th&gt;
&lt;th&gt;영향&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model_reasoning_effort&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;medium&lt;/td&gt;
&lt;td&gt;high 시 추론 토큰 2~3배&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;web_search&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;enabled&lt;/td&gt;
&lt;td&gt;자동 웹 검색 활성화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tool_output_token_limit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;무제한&lt;/td&gt;
&lt;td&gt;툴 응답 전량 유입&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;3. 내가 적용한 설정&lt;/h2&gt;
&lt;h3&gt;~/.claude/settings.json&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OftaF/dJMcaiJNUPm/Kacg9posSSWjetJ9oCnta0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OftaF/dJMcaiJNUPm/Kacg9posSSWjetJ9oCnta0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OftaF/dJMcaiJNUPm/Kacg9posSSWjetJ9oCnta0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOftaF%2FdJMcaiJNUPm%2FKacg9posSSWjetJ9oCnta0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;741&quot; height=&quot;275&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;275&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3&gt;선택 이유&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;CLAUDE_CODE_GLOB_NO_IGNORE: &amp;quot;false&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;모노리포에서 검색 1번이 연쇄 Read를 부름&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.gitignore&lt;/code&gt; 존중으로 불필요한 파일 원천 차단&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;BASH_MAX_OUTPUT_LENGTH: &amp;quot;10000&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;npm/pip 장황한 로그 제한&lt;/li&gt;
&lt;li&gt;오류 메시지는 보존&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;MAX_MCP_OUTPUT_TOKENS: &amp;quot;8000&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Confluence 등 긴 문서 응답 제한&lt;/li&gt;
&lt;li&gt;요약본만 가져오도록 유도&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;4.CLAUDE.md vs 환경변수 — 뭐가 다른가?&lt;/h2&gt;
&lt;p&gt;&amp;quot;CLAUDE.md에 &amp;#39;node_modules 읽지 마&amp;#39;라고 적으면 되는 거 아닌가?&amp;quot;&lt;/p&gt;
&lt;h3&gt;환경변수 설정 (CLAUDE_CODE_GLOB_NO_IGNORE: &amp;quot;false&amp;quot;)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;작동 시점&lt;/strong&gt;: 툴 실행 &lt;strong&gt;전&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;효과&lt;/strong&gt;: node_modules가 검색 결과에 아예 포함 안 됨&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;토큰&lt;/strong&gt;: 검색 결과 10개 → 500 토큰&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;CLAUDE.md 지침&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;작동 시점&lt;/strong&gt;: 세션 시작 시 자동 로드 + 응답 생성 시 참조&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;효과&lt;/strong&gt;: &lt;ul&gt;
&lt;li&gt;CLAUDE.md 자체가 매번 컨텍스트에 추가됨 (예: 200 토큰)&lt;/li&gt;
&lt;li&gt;node_modules가 검색 결과엔 포함됨, Claude가 읽지 않도록 무시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;토큰&lt;/strong&gt;: CLAUDE.md 200 토큰 + 검색 결과 1000개 5000 토큰 = &lt;strong&gt;총 5200 토큰&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;실제 토큰 소비 비교&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;방법&lt;/th&gt;
&lt;th&gt;총 토큰 소비&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;환경변수만&lt;/td&gt;
&lt;td&gt;500 토큰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLAUDE.md만&lt;/td&gt;
&lt;td&gt;5200 토큰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;둘 다 사용&lt;/td&gt;
&lt;td&gt;700 토큰&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;권장: 역할 분담&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;환경변수&lt;/strong&gt;: 명확한 제외 대상 (node_modules, dist 등) → 근본 차단&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLAUDE.md&lt;/strong&gt;: 프로젝트별 세부 지침 (legacy/ 폴더는 deprecated, 특정 파일 수정 금지 등) → 행동 유도&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;핵심&lt;/strong&gt;: 환경변수로 걸러낼 수 있는 건 환경변수로 처리하고, CLAUDE.md는 정말 필요한 프로젝트 컨텍스트만 간결하게 작성하세요.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. 적용 안 한 것 — 이유&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;이유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;includeGitInstructions: false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;커밋 작업 많아서 켜둠&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLAUDE_CODE_FILE_READ_MAX_OUTPUT_TOKENS&lt;/code&gt; 제한&lt;/td&gt;
&lt;td&gt;대형 파일 전체 분석 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex &lt;code&gt;model_reasoning_effort: medium&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;복잡한 작업 비중 높음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;설정 하나하나가 작아 보여도 &lt;strong&gt;세션이 쌓이면 20~30% 토큰 절감&lt;/strong&gt; 가능합니다. 본인 작업 패턴에 맞게 한 번에 하나씩 테스트하며 적용하세요.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;참고 글&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.stdy.blog/increasing-token-efficiency-by-setting-adjustment-in-claude-and-codex/#google_vignette&quot;&gt;Claude와 Codex의 설정 조정을 통한 토큰 효율성 증대&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;이 글이 도움이 되셨다면 공감❤️ 부탁드립니다!  &lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;</description>
      <category>AI</category>
      <category>ai tool</category>
      <category>claude code</category>
      <category>claude token</category>
      <category>codex</category>
      <category>토큰 줄이기</category>
      <author>포도쿵야</author>
      <guid isPermaLink="true">https://podokungya.tistory.com/14</guid>
      <comments>https://podokungya.tistory.com/14#entry14comment</comments>
      <pubDate>Thu, 23 Apr 2026 17:00:28 +0900</pubDate>
    </item>
    <item>
      <title>개발자를 위한 회사 보안 검사 대비하기 - 하드코딩 없애고 안전한 인증 시스템 구축하는 법</title>
      <link>https://podokungya.tistory.com/13</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  이런 경험 있으신가요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 비밀번호를 코드에 그대로 박아두셨나요?&lt;br /&gt;.env 파일이 Git에 올라간 적 있으신가요?&lt;br /&gt;회사 보안 검사에서 이런 부분들이 적발되면 프로젝트가 중단될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 &lt;b&gt;실무에서 바로 적용 가능한 안전한 인증 시스템 구축 방법&lt;/b&gt;을 다룹니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#1&quot;&gt;현재 개발 현장의 문제점&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#2&quot;&gt;인증 솔루션 비교 - 어떤 걸 선택해야 할까?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#3&quot;&gt;Vault 인증 방식 비교&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#4&quot;&gt;Vault란 무엇인가?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#5&quot;&gt;Python에서 Vault 도입하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#6&quot;&gt;실무 적용 예제&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a name=&quot;1&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣ 현재 개발 현장의 문제점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  일반적인 개발 현장의 모습&lt;/h3&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;# 하드코딩 방식
DB_HOST = &quot;192.168.1.100&quot;
DB_USER = &quot;admin&quot;
DB_PASSWORD = &quot;P@ssw0rd123!&quot;  #   코드에 그대로 노출

# .env 파일 방식
DB_PASSWORD=P@ssw0rd123!
API_KEY=sk-1234567890abcdef
JWT_SECRET=my-super-secret-key&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚠️ 이 방식이 위험한 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 코드 유출 = 비밀번호 유출&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Git에 실수로 커밋&lt;/li&gt;
&lt;li&gt;퇴사자의 로컬 저장소&lt;/li&gt;
&lt;li&gt;협력사/외주 개발자 공유&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 암호화의 함정&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;암호화 키는 어디에 저장? &amp;rarr; 결국 같은 문제 반복&lt;/li&gt;
&lt;li&gt;&quot;암호화 키를 암호화하는 키&quot;의 무한 반복&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 권한 관리 불가능&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 A는 DB만, 서비스 B는 API만 접근해야 하는데&lt;/li&gt;
&lt;li&gt;모든 서비스가 모든 정보에 접근 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4) 감사 추적 불가&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;누가, 언제, 어떤 비밀번호를 사용했는지 모름&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a name=&quot;2&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2️⃣ 인증 솔루션 비교 - 어떤 걸 선택해야 할까?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  주요 솔루션 한눈에 비교&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;솔루션&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;특징&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;장점&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;단점&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;추천 상황&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;Spring Security&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;코드 내장형&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;완전한 커스터마이징&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;구현 복잡도 높음&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;백엔드 직접 개발&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;Keycloak&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;인증 서버 분리&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;SSO, 표준 준수&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;서버 운영 필요&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;여러 서비스 통합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;Auth0&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;SaaS 외주형&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;빠른 구현&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;비용, 외부 의존&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;스타트업 빠른 출시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;Vault&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;비밀 관리 중심&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;Secret 관리 특화&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;인증은 부가 기능&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;민감 정보 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  솔루션별 역할 정리&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;Spring Security &amp;rarr; 사용자 로그인 처리
Keycloak       &amp;rarr; 통합 인증 서버 (SSO)
Vault          &amp;rarr; 비밀 정보 관리 (DB 비밀번호, API 키)

  이들은 함께 사용 가능! (상호보완)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  상황별 추천&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단일 서비스, 직접 구현하고 싶다면&lt;/b&gt;&lt;br /&gt;&amp;rarr; &lt;b&gt;Spring Security&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여러 서비스에서 통합 로그인 필요&lt;/b&gt;&lt;br /&gt;&amp;rarr; &lt;b&gt;Keycloak&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개발 시간 단축, 비용 OK&lt;/b&gt;&lt;br /&gt;&amp;rarr; &lt;b&gt;Auth0&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DB 비밀번호, API 키 등 민감 정보 관리&lt;/b&gt;&lt;br /&gt;&amp;rarr; &lt;b&gt;Vault&lt;/b&gt; (이 글의 주제)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a name=&quot;3&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3️⃣ Vault 인증 방식 비교&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Vault에서 지원하는 인증 방식들&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;방식&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;적용 대상&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;장점&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;단점&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;언제 쓸까?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;Token&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;개발/테스트&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;단순함&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;보안 취약&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;로컬 개발용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;AppRole&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;서버 앱&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;머신 인증 최적&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;SecretID 배포 필요&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;⭐ 일반 서버/VM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;Kubernetes&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;K8s Pod&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;자동 인증 가능&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;K8s 환경 필수&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;⭐ 쿠버네티스 환경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;JWT/OIDC&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;SSO 연동&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;기존 인증 활용&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;설정 복잡&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;SSO 연동 필요 시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;LDAP&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;운영자&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;사내 계정 연동&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;앱 인증 부적합&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;운영자 로그인용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  각 인증 방식 쉽게 이해하기&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1️⃣ Token 방식&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;이미 발급받은 출입증으로 바로 들어가기&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;python&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;# Vault가 미리 만들어준 토큰
vault_token = &quot;s.1234567890abcdef&quot;

# 바로 접속 (로그인 과정 없음)
client = hvac.Client(url='http://vault:8200', token=vault_token)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;언제 쓰나요?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로컬 개발할 때&lt;/li&gt;
&lt;li&gt;빠른 테스트할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;토큰 유출되면 끝&lt;/li&gt;
&lt;li&gt;운영 환경에서는 위험&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2️⃣ AppRole 방식 (⭐ 가장 많이 씀)&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;앱 전용 ID/PW로 로그인하기&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;python&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;makefile&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;# 앱의 신분증
role_id = &quot;abc-123-def-456&quot;      # 공개 가능
secret_id = &quot;xyz-789-ghi-012&quot;    # 비공개

# 로그인해서 토큰 받기
client.auth.approle.login(role_id=role_id, secret_id=secret_id)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비유:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RoleID = 사원증 번호 (누가 봐도 됨)&lt;/li&gt;
&lt;li&gt;SecretID = 사원증 비밀번호 (절대 비공개)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;언제 쓰나요?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Python 서버, Java 서버 등&lt;/li&gt;
&lt;li&gt;VM이나 일반 서버에서 실행되는 앱&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3️⃣ Kubernetes 방식&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;쿠버네티스가 자동으로 신분 증명해주기&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;python&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;# Pod 안에서는 자동으로 인증 정보가 있음
# 따로 ID/PW 안 넣어도 됨!

client.auth.kubernetes.login(
    role='my-app-role',
    jwt=open('/var/run/secrets/kubernetes.io/serviceaccount/token').read()
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비유:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회사 건물(쿠버네티스) 안에 있으면 자동으로 출입 가능&lt;/li&gt;
&lt;li&gt;따로 사원증 챙길 필요 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;언제 쓰나요?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Docker 컨테이너로 실행&lt;/li&gt;
&lt;li&gt;Kubernetes(K8s) 환경&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4️⃣ JWT/OIDC 방식&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;구글/회사 계정으로 로그인하기&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;python&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;# 구글/깃허브/회사 SSO 로그인 토큰으로 인증
jwt_token = &quot;eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...&quot;

client.auth.jwt.login(
    role='my-role',
    jwt=jwt_token
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비유:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;구글 계정으로 로그인&quot; 버튼 누르는 것&lt;/li&gt;
&lt;li&gt;이미 로그인한 정보 재활용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;언제 쓰나요?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회사에 SSO(통합 로그인)가 있을 때&lt;/li&gt;
&lt;li&gt;GitHub Actions, GitLab CI 같은 곳에서&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5️⃣ LDAP 방식&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;회사 계정(Active Directory)으로 로그인&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;python&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;# 회사 이메일/비밀번호로 로그인
client.auth.ldap.login(
    username='hong.gildong@company.com',
    password='my-company-password'
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비유:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회사 PC 켤 때 쓰는 그 계정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;언제 쓰나요?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영자가 Vault UI에 접속할 때&lt;/li&gt;
&lt;li&gt;사람이 직접 로그인할 때&lt;/li&gt;
&lt;li&gt;앱 자동 인증에는 부적합&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  권장 선택 기준&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반 서버/VM에서 Python 앱 실행&lt;/b&gt;&lt;br /&gt;&amp;rarr; &lt;b&gt;AppRole&lt;/b&gt; (가장 무난)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kubernetes 환경&lt;/b&gt;&lt;br /&gt;&amp;rarr; &lt;b&gt;Kubernetes Auth&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;운영자가 UI 접근&lt;/b&gt;&lt;br /&gt;&amp;rarr; &lt;b&gt;OIDC 또는 LDAP&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개발/테스트 환경&lt;/b&gt;&lt;br /&gt;&amp;rarr; &lt;b&gt;Token&lt;/b&gt; (임시로만)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a name=&quot;4&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4️⃣ Vault란 무엇인가?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  핵심 개념&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Vault는 &quot;비밀번호 저장소&quot;가 아니라 &quot;비밀번호를 없애는 시스템&quot;입니다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;❌ 기존 방식: 앱 안에 비밀 정보 포함 &amp;rarr; 유출 시 끝

✅ Vault 방식: 비밀은 Vault에만, 앱은 필요할 때만 요청
              &amp;rarr; 유출돼도 제한적, 추적 가능&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Vault 동작 구조&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;┌─────────────┐
│ Python 앱   │ ─────► 1. 로그인 요청 (RoleID + SecretID)
└─────────────┘
                       ▼
              ┌─────────────────┐
              │  Vault Server   │
              └─────────────────┘
                       │
                       │ 2. 인증 성공 &amp;rarr; Token 발급
                       │
                       ▼
┌─────────────┐
│ Python 앱   │ ◄───── 3. Secret 반환 (Token으로 요청)
└─────────────┘

              ┌─────────────────┐
              │  DB 비밀번호     │
              │  API Key        │
              │  암호화 키       │
              └─────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Vault의 핵심 기능&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 정적 Secret 관리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 사용 중인 비밀번호를 안전하게 보관&lt;/li&gt;
&lt;li&gt;버전 관리 지원 (잘못 바꿨을 때 롤백 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 동적 Secret 생성&lt;/b&gt; ⭐ 핵심!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필요할 때마다 임시 DB 계정 생성&lt;/li&gt;
&lt;li&gt;1시간 뒤 자동 삭제&lt;/li&gt;
&lt;li&gt;유출돼도 의미 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 권한 관리 (Policy)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스별로 접근 가능한 Secret 제한&lt;/li&gt;
&lt;li&gt;최소 권한 원칙 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4) 감사 로그&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;누가, 언제, 무엇을 가져갔는지 기록&lt;/li&gt;
&lt;li&gt;보안 사고 시 추적 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a name=&quot;5&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5️⃣ Python에서 Vault 도입하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  현재 코드 (Before)&lt;/h3&gt;
&lt;pre class=&quot;moonscript&quot;&gt;&lt;code&gt;import os
from dotenv import load_dotenv

load_dotenv()
DB_PASSWORD = os.getenv('DB_PASSWORD')  # .env에서 읽음&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⚙️ Step 1: Vault 서버 준비&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# Docker로 개발용 Vault 실행
docker run -d --name=vault \
  -p 8200:8200 \
  --cap-add=IPC_LOCK \
  -e 'VAULT_DEV_ROOT_TOKEN_ID=myroot' \
  vault

export VAULT_ADDR='http://localhost:8200'
vault login myroot&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Step 2: Secret 저장&lt;/h3&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;vault kv put secret/myapp/db \
  host=&quot;db.internal&quot; \
  port=&quot;5432&quot; \
  username=&quot;myuser&quot; \
  password=&quot;MySecurePassword123!&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Step 3: AppRole 설정&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# AppRole 인증 활성화
vault auth enable approle

# Role 생성
vault write auth/approle/role/myapp \
  secret_id_ttl=24h \
  token_ttl=1h \
  token_max_ttl=4h \
  policies=&quot;myapp-policy&quot;

# RoleID 확인
vault read auth/approle/role/myapp/role-id

# SecretID 발급
vault write -f auth/approle/role/myapp/secret-id&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Step 4: Python 코드 수정 (After)&lt;/h3&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import hvac
import os

class VaultClient:
    def __init__(self):
        self.client = hvac.Client(url=os.getenv('VAULT_ADDR'))
        self._authenticate()

    def _authenticate(self):
        &quot;&quot;&quot;AppRole로 로그인&quot;&quot;&quot;
        role_id = os.getenv('VAULT_ROLE_ID')
        secret_id = os.getenv('VAULT_SECRET_ID')

        self.client.auth.approle.login(
            role_id=role_id,
            secret_id=secret_id
        )

    def get_db_credentials(self):
        &quot;&quot;&quot;DB 접속 정보 가져오기&quot;&quot;&quot;
        secret = self.client.secrets.kv.v2.read_secret_version(
            path='myapp/db',
            mount_point='secret'
        )
        return secret['data']['data']

# 사용 예시
vault = VaultClient()
db_config = vault.get_db_credentials()

DB_HOST = db_config['host']
DB_PORT = db_config['port']
DB_USER = db_config['username']
DB_PASSWORD = db_config['password']&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Step 5: 환경변수 정리&lt;/h3&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;# .env 파일 (After) - 민감 정보 제거됨!
VAULT_ADDR=https://vault.company.com
VAULT_ROLE_ID=abc-123-def-456
# VAULT_SECRET_ID는 배포 시스템에서 안전하게 주입&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a name=&quot;6&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6️⃣ 실무 적용 예제&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  시나리오 1: FastAPI + PostgreSQL (동적 계정)&lt;/h3&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;from fastapi import FastAPI
from sqlalchemy import create_engine
import hvac

app = FastAPI()

class VaultDB:
    def __init__(self):
        self.vault = hvac.Client(url='https://vault.company.com')
        self.vault.auth.approle.login(
            role_id=ROLE_ID,
            secret_id=SECRET_ID
        )

    def get_engine(self):
        # Vault가 즉시 생성하는 임시 DB 계정
        creds = self.vault.read('database/creds/myapp-role')

        username = creds['data']['username']  # v-approle-abc123
        password = creds['data']['password']  # 랜덤 생성

        db_url = f&quot;postgresql://{username}:{password}@{DB_HOST}/{DB_NAME}&quot;

        return create_engine(db_url)

vault_db = VaultDB()
engine = vault_db.get_engine()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유출돼도 1시간 후 자동 삭제&lt;/li&gt;
&lt;li&gt;각 앱마다 다른 계정 사용&lt;/li&gt;
&lt;li&gt;접근 로그 추적 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  시나리오 2: 외부 API 키 관리&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import requests

def call_external_api():
    # API 키를 Vault에서 가져오기
    secret = vault_client.secrets.kv.v2.read_secret_version(
        path='external/api-keys'
    )

    api_key = secret['data']['data']['openai_key']

    response = requests.post(
        'https://api.openai.com/v1/chat/completions',
        headers={'Authorization': f'Bearer {api_key}'},
        json=payload
    )

    return response.json()&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안은 &quot;귀찮은 규제&quot;가 아니라 &lt;b&gt;&quot;시스템의 신뢰성을 높이는 투자&quot;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vault와 같은 도구를 도입하면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;✅ 개발자는 안전하게 개발&lt;/li&gt;
&lt;li&gt;✅ 운영팀은 안심하고 운영&lt;/li&gt;
&lt;li&gt;✅ 회사는 규정 준수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두가 &lt;b&gt;win-win&lt;/b&gt;할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 복잡해 보이지만, 한 번 구축하면 장기적으로 훨씬 안전하고 관리하기 쉬운 시스템을 운영할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.hashicorp.com/vault/docs&quot;&gt;HashiCorp Vault 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hvac.readthedocs.io/&quot;&gt;Vault Python Client (hvac)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html&quot;&gt;OWASP Secret Management Cheat Sheet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;#Vault&lt;/code&gt; &lt;code&gt;#보안&lt;/code&gt; &lt;code&gt;#Python&lt;/code&gt; &lt;code&gt;#DevSecOps&lt;/code&gt; &lt;code&gt;#Secret관리&lt;/code&gt; &lt;code&gt;#AppRole&lt;/code&gt; &lt;code&gt;#보안검사&lt;/code&gt; &lt;code&gt;#Keycloak&lt;/code&gt; &lt;code&gt;#인증시스템&lt;/code&gt; &lt;code&gt;#하드코딩&lt;/code&gt;&lt;/p&gt;</description>
      <category>DevOps/Security</category>
      <author>포도쿵야</author>
      <guid isPermaLink="true">https://podokungya.tistory.com/13</guid>
      <comments>https://podokungya.tistory.com/13#entry13comment</comments>
      <pubDate>Thu, 23 Apr 2026 10:03:57 +0900</pubDate>
    </item>
    <item>
      <title>환경설정 파일, 왜 어떤 프로젝트는 git에 올리고 어떤 프로젝트는 서버에서 따로 관리할까</title>
      <link>https://podokungya.tistory.com/12</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전 프로젝트에서는 소스코드만 배포하고 환경설정은 서버에서 직접 관리했다. 새 프로젝트에서는 환경설정도 git에 올리고 한번에 배포한다. 이게 왜 다른 건지 비교해봤다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  결론 &amp;mdash; 배포 철학 차이다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Java는 외부 config 분리 방식이고 Python은 코드에 같이 넣는 방식인가?&quot; &amp;mdash; 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python도 외부 config 분리가 가능하고, Java도 config를 코드와 함께 배포할 수 있다. 핵심 질문은 이거다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버가 설정을 관리하는가, Git/배포본이 설정을 관리하는가&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  두 가지 방식 비교&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방식 1 &amp;mdash; 외부 Config 분리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스코드만 배포하고, 환경설정 파일은 서버의 별도 경로에 둔다. 앱 실행 시점에 그 파일을 읽는다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# Java
java -jar app.jar --spring.config.location=/opt/config/application.yml

# Python
python app.py --config=/opt/config/config.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버가 상태를 가진다.&lt;/b&gt; 배포해도 서버의 config는 그대로 유지된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방식 2 &amp;mdash; 코드와 Config 함께 배포&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;config 값도 repository에 포함해서 CI/CD가 코드 + config를 함께 배포한다. 배포 시 기존 파일이 덮어쓰기된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Git/배포본이 상태를 가진다.&lt;/b&gt; 서버에서 직접 수정한 값은 다음 배포 때 사라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 상태의 주인 배포 후 서버 수정&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;외부 Config 분리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;서버&lt;/td&gt;
&lt;td&gt;유지됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;코드와 함께 배포&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Git / 배포본&lt;/td&gt;
&lt;td&gt;다음 배포 때 덮어쓰기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  내가 겪은 두 프로젝트의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예전 Java 프로젝트 (방식 1)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;[Bamboo] jar 빌드 &amp;rarr; 서버에 jar만 배포

[운영 서버] /opt/config/application.yml 별도 관리
&amp;rarr; 배포해도 config는 건드리지 않음
&amp;rarr; 환경설정 변경을 굳이 커밋할 필요 없었음
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;새 Python 프로젝트 (방식 2)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;[Bamboo] config 포함 패키징 &amp;rarr; 전체 배포 (덮어쓰기)

[운영 서버] 프로젝트 전체가 교체됨
&amp;rarr; 서버에서 직접 수정해도 다음 배포 때 사라짐
&amp;rarr; config 변경도 git으로 관리해야 함
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 프로젝트가 방식 2를 선택한 이유 &amp;mdash; 방화벽이 많아 서버 직접 접근이 까다로운 환경이었다. Git과 CI/CD로만 관리하는 게 더 현실적이었다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  프론트는 왜 다른가 &amp;mdash; config를 읽는 시점이 다르다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드는 서버에서 실행되는 프로세스라 실행 시점에 파일이나 환경변수를 읽을 수 있다. 프론트 CSR은 구조 자체가 다르다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;npm run build &amp;rarr; 정적 파일 생성 &amp;rarr; nginx 서빙 &amp;rarr; 브라우저 실행
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 이미 완성된 정적 JS 파일을 실행할 뿐이다. 빌드 시 import.meta.env.VITE_API_URL 같은 코드는 실제 값으로 치환되어 번들에 박힌다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// 코드
const apiUrl = import.meta.env.VITE_API_URL

// 빌드 결과물 &amp;mdash; 값이 이미 확정됨
const apiUrl = &quot;https://api.example.com&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배포 후 서버에서 .env만 바꿔서는 효과가 없다.&lt;/b&gt; 재빌드가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 config 반영 시점 배포 후 수정&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;백엔드&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;런타임 (실행 시)&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;프론트 CSR&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;빌드타임&lt;/td&gt;
&lt;td&gt;재빌드 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프론트에서 배포 후 값을 바꿔야 한다면 &amp;mdash; 런타임 config&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재빌드 없이 값을 바꿔야 하는 경우, 앱 시작 시 외부 파일을 fetch해서 읽는 구조를 별도로 만든다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 앱 시작 전 실행
async function initConfig() {
  const res = await fetch('/config.json')
  window.APP_CONFIG = await res.json()
}
await initConfig()

// 사용 &amp;mdash; 런타임 우선, 없으면 빌드타임 fallback
const apiUrl =
  window.APP_CONFIG?.API_URL ??
  import.meta.env.VITE_API_URL
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/config.json을 서버에서 직접 수정하면 재빌드 없이 반영된다. 다만 async 초기화 처리가 필요하고 구조가 복잡해지므로, 하나의 빌드로 여러 환경에 배포하거나 배포 없이 값을 자주 바꿔야 하는 경우에만 쓴다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  혼합형 &amp;mdash; 실무에서 가장 흔한 형태&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기준은 하나다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;이 값이 노출됐을 때 문제가 생기는가?&quot;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;괜찮으면 &amp;rarr; git에서 관리&lt;/li&gt;
&lt;li&gt;안 되면 &amp;rarr; 서버 파일 / 환경변수 / Secret Manager&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# git에 올라가는 것 &amp;mdash; 공개 가능한 값
server:
  port: 8080
feature:
  cache:
    enabled: true
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# 서버에만 있는 것 &amp;mdash; 민감한 값
# /opt/config/secrets.yml
database:
  password: super_secret
api:
  jwt-secret: xyz789
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;# 실행 시 두 파일을 합쳐서 읽음
java -jar app.jar \
  --spring.config.location=classpath:/application.yml,/opt/config/secrets.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경변수로 주입하는 방식도 흔하다. Docker, Kubernetes 환경에서 특히 많이 쓴다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;export DB_PASSWORD=&quot;super_secret&quot;
python app.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 환경이라면 파일이나 환경변수 대신 Secret Manager API를 직접 호출하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값의 종류 관리 방식&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;포트, 기능 플래그, 기본 옵션&lt;/td&gt;
&lt;td&gt;git&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DB 비밀번호, API 시크릿&lt;/td&gt;
&lt;td&gt;서버 파일 또는 환경변수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JWT 시크릿, 인증 키&lt;/td&gt;
&lt;td&gt;환경변수 또는 Secret Manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;프론트 API URL&lt;/td&gt;
&lt;td&gt;build-time env (공개 가능한 값만)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;프론트 시크릿&lt;/td&gt;
&lt;td&gt;절대 프론트에 포함하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚠️ 헷갈리기 쉬운 포인트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;서버에서 config 수정했는데 배포 후 왜 원복됐지?&quot;&lt;/b&gt; 방식 2(코드와 함께 배포)라면 당연하다. 서버 직접 수정이 아니라 git &amp;rarr; CI/CD 흐름으로 변경해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;stg용으로 빌드했는데 prod에 배포하면?&quot;&lt;/b&gt; 프론트라면 stg 값이 번들에 박힌 채 prod에 올라간다. 빌드 환경과 배포 환경은 반드시 일치시켜야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;프론트 env에 시크릿 키 넣어도 되나?&quot;&lt;/b&gt; 절대 안 된다. 번들에 포함되어 브라우저에서 노출된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목 내용&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;언어 차이인가&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;아니다. Java/Python/Node 모두 두 방식 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;외부 Config 분리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;서버가 상태 관리. 배포해도 config 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;코드와 함께 배포&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Git이 상태 관리. 배포 시 덮어쓰기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;프론트 CSR&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;빌드타임에 값 확정. 배포 후 env 수정은 효과 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;혼합형&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;공개 설정은 git, 민감 정보는 외부 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>DevOps</category>
      <category>.env</category>
      <category>.yml</category>
      <category>application.yaml</category>
      <category>BAMBOO</category>
      <category>build</category>
      <category>CICD</category>
      <category>Config</category>
      <category>DevOps</category>
      <category>배포</category>
      <category>환경설정</category>
      <author>포도쿵야</author>
      <guid isPermaLink="true">https://podokungya.tistory.com/12</guid>
      <comments>https://podokungya.tistory.com/12#entry12comment</comments>
      <pubDate>Thu, 16 Apr 2026 15:02:46 +0900</pubDate>
    </item>
    <item>
      <title>Compound Engineering &amp;mdash; 알고 보면 이미 하고 있는 AI 개발 방법론</title>
      <link>https://podokungya.tistory.com/11</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법론은 강력할수록 전제 조건이 있다. 직접 도입하고 롤백하면서 그 조건을 확인했다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  먼저 &amp;mdash; 개발 방법론 지형 파악&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트 시대에 들어서면서 방법론 이야기가 많아졌다. 새로운 이름들이 쏟아지지만, 각각이 다루는 문제가 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 두 축으로 나눌 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI 이전부터 있던 방법론&lt;/b&gt; &amp;mdash; 코드 품질과 요구사항 관리를 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법론 핵심 질문 시작점&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;TDD&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;테스트를 먼저 써야 코드가 안전하다&lt;/td&gt;
&lt;td&gt;실패하는 테스트 코드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;BDD&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;사용자 행동 기준으로 기능을 정의하자&lt;/td&gt;
&lt;td&gt;자연어 시나리오 (Given-When-Then)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;SDD&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;코드 작성 전에 명세를 먼저 정의하자&lt;/td&gt;
&lt;td&gt;요구사항 명세서, 스펙 문서&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI 에이전트 시대에 등장한 개념&lt;/b&gt; &amp;mdash; AI를 어떻게 활용할 것인가를 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Vibe Coding&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;명확한 명세 없이 프롬프트로 즉흥적으로 개발. 프로토타입에는 빠르지만 프로덕션에서는 예측 불가능성 문제 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Compound Engineering&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;매 사이클의 학습을 CLAUDE.md에 축적해 AI가 점점 더 잘 작동하게 만드는 방법론&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것들은 서로 경쟁하는 관계가 아니다. 다루는 레이어가 다르다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TDD/BDD/SDD는 &lt;b&gt;&quot;무엇을, 어떤 기준으로 만들 것인가&quot;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Compound Engineering은 &lt;b&gt;&quot;AI와 함께 어떻게 축적하며 만들어갈 것인가&quot;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SDD와 Compound Engineering은 특히 자주 혼동된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;SDD&lt;/b&gt; &amp;rarr; 스펙을 먼저 정의해서 AI가 제대로 만들게 한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Compound Engineering&lt;/b&gt; &amp;rarr; 만들고 나서 학습을 기록해 다음에 더 잘 만들게 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘은 함께 쓸 수 있고, 실제로 잘 어울린다. SDD로 스펙을 먼저 정의하고, 그 과정에서 생긴 패턴을 Compound로 축적하는 방식이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Compound Engineering이란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Every.to의 Kieran Klaassen이 AI 에이전트 기반 개발을 하면서 정립한 방법론으로, 핵심 철학은 하나다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;각 기능 개발이 다음 기능 개발을 더 쉽게 만들어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 코드베이스는 기능이 늘어날수록 복잡성이 쌓이면서 느려진다. Compound Engineering은 이것을 뒤집는다. 기능 하나를 완성할 때마다 그 과정에서 발견한 패턴, 실수, 결정을 문서화해서 AI가 다음 세션에 활용하게 한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;전통적 개발:   기능1 ── 기능2 ── 기능3 ── 기능4  (선형, 점점 느려짐)

Compound:      기능1 ─&amp;rarr; 기능2 ──&amp;rarr; 기능3 ───&amp;rarr; 기능4  (점진적 가속)
                    학습 축적  학습 축적   학습 축적
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이클 구조는 네 단계다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Plan &amp;rarr; Work &amp;rarr; Review &amp;rarr; Compound &amp;rarr; Repeat
                             &amp;uarr;
              이번 사이클의 학습을 문서화
              &amp;rarr; 다음 사이클의 AI 컨텍스트로 축적
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 세 단계(Plan, Work, Review)는 어떤 개발 방법론에서도 익숙하다. &lt;b&gt;Compound 단계가 이 방법론의 핵심&lt;/b&gt;이다. 이 단계를 건너뛰면 AI 지원을 받는 일반적인 개발과 다름없다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Compound를 &quot;따른다&quot;는 것의 실체&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도로 &quot;compound를 따르겠다&quot;고 선언하는 파일이 있는 게 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨텍스트 파일 자체가 compound의 저장소다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;기능 하나 완료
    &amp;darr;
이번 사이클의 패턴, 실수, 결정을 컨텍스트 파일에 써넣는다
    &amp;darr;
다음 세션에서 AI가 그 파일을 읽고 시작한다
    &amp;darr;
이전 학습이 자동으로 반영된다
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 1사이클에서 페이지네이션 누락 버그가 났다면 이렇게 기록한다.&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;# CLAUDE.md

## 패턴 및 주의사항

### 페이지네이션
목록 API 응답에는 반드시 meta.total, meta.page를 포함할 것.
1사이클 QA 과정에서 누락으로 지적됨.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 세션에서 AI가 이 내용을 읽고 시작하기 때문에 같은 실수를 하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Compound Engineering을 따른다는 건 결국 하나의 습관이다.&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능 하나를 완료할 때마다 컨텍스트 파일을 업데이트한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일의 이름은 도구마다 다를 뿐, 역할은 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 도구 컨텍스트 파일&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;CLAUDE.md&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;.cursorrules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot&lt;/td&gt;
&lt;td&gt;.github/copilot-instructions.md&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windsurf&lt;/td&gt;
&lt;td&gt;.windsurfrules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex&lt;/td&gt;
&lt;td&gt;AGENTS.md&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  자동으로 되는 게 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 이 방법론을 접했을 때 이렇게 이해했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;버그가 수정되면 자동으로 기록되고, 다음 사이클에 AI가 자동으로 반영해주겠구나.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;틀렸다. &lt;b&gt;compound 단계는 수동 문서화 작업이다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;발견한 패턴을 컨텍스트 파일에 &lt;b&gt;직접 써야&lt;/b&gt; 한다&lt;/li&gt;
&lt;li&gt;어떤 결정을 왜 했는지 &lt;b&gt;직접 기록해야&lt;/b&gt; 한다&lt;/li&gt;
&lt;li&gt;AI가 읽을 수 있는 형태로 &lt;b&gt;직접 정리해야&lt;/b&gt; 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 플러그인의 /workflows:compound 커맨드가 이 과정을 도와주긴 하지만, 무엇을 기록할지 판단하는 건 여전히 개발자의 몫이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  알고 보니 이미 하고 있었다??&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법론 이름을 보고 뭔가 거창한 걸 기대했는데, 실제로 파고들면 이런 생각이 든다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;어? 이거 내가 원래 하던 거 아닌가?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맞다. CLAUDE.md에 &quot;이 프로젝트에서 에러는 이렇게 처리한다&quot;, &quot;API 응답 포맷은 이 구조로 통일한다&quot;, &quot;DB는 UUID 기본키를 쓴다&quot; 같은 내용을 적어두고 AI가 참조하게 하는 것, 그게 이미 Compound Engineering이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compound Engineering이 새로운 기술이 아니라는 분석도 있다. 잘하는 개발자들이 이미 직관적으로 해오던 것을 &lt;b&gt;명시적인 사이클로 만든 것&lt;/b&gt;이 이 방법론의 실체에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그렇다면 이 방법론의 실제 가치는 뭔가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;기능 완료 &amp;rarr; CLAUDE.md 업데이트 &amp;rarr; 다음 기능&quot;이라는 흐름을 &lt;b&gt;빠뜨리지 않고 매번 하도록 구조화&lt;/b&gt;하는 것이다. 개발하다 보면 바빠서 기록을 미루게 되는데, 사이클의 마지막 단계로 명문화해두면 습관처럼 따르게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인의 /workflows:compound 커맨드도 결국 &quot;이번에 뭘 배웠는지 기록하는 걸 빠뜨리지 말라&quot;는 리마인더이자 자동화 도구다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉 이미 CLAUDE.md를 관리하고 있다면, 당신은 이미 Compound Engineering을 하고 있는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  플러그인과 명령어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인 없이도 방법론 자체는 적용 가능하다. 컨텍스트 파일을 직접 관리하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 플러그인(&lt;a href=&quot;https://github.com/EveryInc/compound-engineering-plugin&quot;&gt;EveryInc/compound-engineering-plugin&lt;/a&gt;)은 사이클 전체를 커맨드로 자동화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;설치 (Claude Code 기준)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;claude /plugin marketplace add https://github.com/EveryInc/every-marketplace
claude /plugin install compound-engineering
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 커맨드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;/workflows:brainstorm  # 아이디어 &amp;rarr; 요구사항 정리
/workflows:plan        # 구현 계획서 생성
/workflows:work        # 코드 구현
/workflows:review      # 전문 에이전트 병렬 코드 리뷰
/workflows:compound    # 이번 사이클 학습을 컨텍스트 파일에 반영
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인은 Claude Code 외 Cursor, Copilot, Codex 등 다른 도구 형식으로도 변환을 지원한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  도입하면서 마주한 한계&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;compound 효과가 작게 느껴지는 프로젝트가 있다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;compound의 핵심은 &lt;b&gt;패턴과 학습의 누적&lt;/b&gt;이다. 에러 처리 방식, API 응답 포맷, 공통 유틸리티 같은 것들은 기능이 독립적이더라도 누적될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 각 기능이 서로 다른 도메인을 다루고 공통 패턴이 적은 프로젝트에서는 축적되는 학습의 양이 제한적이다. 인증 &amp;rarr; 권한 &amp;rarr; 프로필 &amp;rarr; 대시보드처럼 이전 기능이 다음 기능의 기반이 되는 구조에서 compound 효과가 가장 크게 나타난다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로젝트 초기에 설계됐어야 한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 진행 중인 프로젝트에 중간에 얹으면, 기존 히스토리 없이 시작하는 것과 다르지 않다. 축적이 쌓일 기반 자체가 없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;compound와 작업 추적을 혼동하게 된다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;compound 단계를 운영하다 보면 요구사항 상태를 추가하고 관리하고 싶어진다. 그런데 Compound Engineering이 다루는 건 &lt;b&gt;작업 추적이 아니라 AI를 위한 학습의 누적&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 추적은 Jira나 WBS가 이미 하고 있는 일이고, compound는 그 위에 &lt;b&gt;AI 컨텍스트 관리라는 별도의 레이어&lt;/b&gt;를 더하는 것이다. 이 둘을 혼동하면 compound 단계가 단순한 체크리스트 관리로 전락한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  어떤 프로젝트에 적합한가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 효과가 큰 프로젝트 효과가 제한적인 프로젝트&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 94px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;기능 간 관계&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;순차적 의존 (A &amp;rarr; B &amp;rarr; C 기반)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;독립적인 기능들의 집합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;공통 패턴&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;반복되는 패턴이 많음&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;기능마다 다른 구조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;프로젝트 시점&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;초기 설계 단계&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;이미 진행 중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;운영 기간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;장기 반복 개선 제품&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;단기 구축 후 유지보수 위주&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;Compound Engineering이 특히 빛나는 프로젝트 유형&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* SaaS 플랫폼 &amp;mdash; 기능이 기능 위에 쌓이는 구조&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e-커머스 플랫폼을 예로 들면, 상품 스키마를 구현하면서 DB 컨벤션(UUID 기본키, created_at/updated_at 포함, 가격은 정수 cents로 저장)을 기록한다. 장바구니는 상품 패턴 위에 쌓이고, 결제는 장바구니 패턴 위에 쌓이고, 주문 내역은 결제 패턴 위에 쌓인다. 각 기능이 이전 기능을 더 빠르게 만드는 구조다.&lt;span data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre style=&quot;color: #14181f;&quot;&gt;&lt;code&gt;상품 스키마 구현 &amp;rarr; DB 컨벤션 기록
    &amp;darr;
장바구니 &amp;rarr; 상품 패턴 자동 적용
    &amp;darr;
결제 &amp;rarr; 장바구니 패턴 자동 적용
    &amp;darr;
주문 내역 &amp;rarr; 결제 패턴 자동 적용&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법론을 도입하기 전에 물어볼 한 가지 질문이 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;이번 사이클이 완료됐을 때 다음 사이클이 더 빨라지는가?&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 질문에 &quot;그렇다&quot;고 답할 수 있다면 도입하라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않다면, compound 레이어 자체가 지금 이 프로젝트에서 필요하지 않은 것이다. 작업 추적은 Jira가, 일정 관리는 WBS가 이미 담당하고 있다. Compound Engineering은 그것들을 대체하는 도구가 아니라, AI 에이전트를 쓸 때 학습을 축적하는 별도의 레이어다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고 링크&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공식 플러그인: &lt;a href=&quot;https://github.com/EveryInc/compound-engineering-plugin&quot;&gt;EveryInc/compound-engineering-plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;방법론 원문 가이드: &lt;a href=&quot;https://every.to/guides/compound-engineering&quot;&gt;every.to/guides/compound-engineering&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>AI</category>
      <category>Ai</category>
      <category>claude</category>
      <category>Compound Engineering</category>
      <category>개발방법론</category>
      <author>포도쿵야</author>
      <guid isPermaLink="true">https://podokungya.tistory.com/11</guid>
      <comments>https://podokungya.tistory.com/11#entry11comment</comments>
      <pubDate>Thu, 9 Apr 2026 15:21:44 +0900</pubDate>
    </item>
  </channel>
</rss>