<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>✨ 일단 기록 ✨</title>
    <link>https://jaejade.tistory.com/</link>
    <description>풀스택 개발도 하고 싶은 백엔드 개발자</description>
    <language>ko</language>
    <pubDate>Fri, 3 Jul 2026 12:38:22 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>jaee</managingEditor>
    <image>
      <title>✨ 일단 기록 ✨</title>
      <url>https://tistory1.daumcdn.net/tistory/4003624/attach/8bdccb0866864a75a1367b57b9758eea</url>
      <link>https://jaejade.tistory.com</link>
    </image>
    <item>
      <title>개인 프로젝트 서버 비용 22만원 나온뒤 외양간 고치기</title>
      <link>https://jaejade.tistory.com/282</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아래는 개인 프로젝트 인프라 구성도이며, 서비스 2개가 배포된 관계로 ECS에 task 2개, RDS도 2개로 구성돼 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1231&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zrkKn/dJMcahdNVCc/1FQ3P3WKBEP1Z2O5I1BtdK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zrkKn/dJMcahdNVCc/1FQ3P3WKBEP1Z2O5I1BtdK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zrkKn/dJMcahdNVCc/1FQ3P3WKBEP1Z2O5I1BtdK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzrkKn%2FdJMcahdNVCc%2F1FQ3P3WKBEP1Z2O5I1BtdK%2Fimg.jpg&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;723&quot; height=&quot;471&quot; data-origin-width=&quot;1231&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;AWS 프리티어 크레딧 소진 후 5월 서버 비용으로 22만 원을 냈다(미친 환율).&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;비용 절감 해야지 마음먹고 귀찮아서 미뤄왔는데, 그 결과 이번 달도 14만 원 이상 태워야 한다는 나태지옥의 형벌을 받게 됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2224&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/88xcZ/dJMcahSq9Ql/E4LBmh152xhKQFN4Kmdtk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/88xcZ/dJMcahSq9Ql/E4LBmh152xhKQFN4Kmdtk0/img.png&quot; data-alt=&quot;이마짚...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/88xcZ/dJMcahSq9Ql/E4LBmh152xhKQFN4Kmdtk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F88xcZ%2FdJMcahSq9Ql%2FE4LBmh152xhKQFN4Kmdtk0%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;2224&quot; height=&quot;408&quot; data-origin-width=&quot;2224&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이마짚...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;구조는 유지하면서 인프라 비용을 줄이기 위해 했던 작업들을 기록하겠다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;[요약]&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. 오토스케일링으로 특정 시간에만 ECS task를 띄운다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2. NAT 게이트웨이 역할을 하는 EC2 인스턴스를 생성한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;3. 새로운 프리티어 계정을 생성하고 인프라를 이전한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;4. 안 쓰는 리소스는 제거한다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;1. 오토스케일링으로 특정 시간에만 ECS task를 띄운다.&lt;/b&gt;&lt;/span&gt;&lt;/span&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;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;귀찮음: ⭐️&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;효과: ⭐️⭐️⭐️&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;구글 애널리틱스를 통해 트래픽 발생하는 시간대를 확인해 보니 평일 오전~오후에만 task를 띄워도 되겠다는 판단을 했다. 오토스케일링 설정 전/후 ECS 비용을 비교해 보니 50% 절감됐다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;1) ECS &amp;gt; Clusters &amp;gt; 클러스터 선택 &amp;gt; 서비스 선택 &amp;gt; &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;Service auto scaling에서&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt; Set the number of tasks 클릭&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;맨 처음에는 tasks 최대/최소 개수를 설정해야 한다. 최소 개수는 0, 최대 개수는 1로 설정하고 저장한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2866&quot; data-origin-height=&quot;1146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AwEme/dJMcageSaJI/y38LVd8NrTTw1kuqcZsydK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AwEme/dJMcageSaJI/y38LVd8NrTTw1kuqcZsydK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AwEme/dJMcageSaJI/y38LVd8NrTTw1kuqcZsydK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAwEme%2FdJMcageSaJI%2Fy38LVd8NrTTw1kuqcZsydK%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;828&quot; height=&quot;331&quot; data-origin-width=&quot;2866&quot; data-origin-height=&quot;1146&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;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;2) Scheduled actions 생성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;task를 띄우고 내려야 하니 총 2개의 scheduled action이 필요하다. 특정 시간대에 동작해야 하므로 cron으로 설정하고, 선택한 타임존에 맞게 작성하자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc7Wv7/dJMcaaFMDRl/Jk2aUWJaCkQZ8QlZw8GRiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc7Wv7/dJMcaaFMDRl/Jk2aUWJaCkQZ8QlZw8GRiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc7Wv7/dJMcaaFMDRl/Jk2aUWJaCkQZ8QlZw8GRiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc7Wv7%2FdJMcaaFMDRl%2FJk2aUWJaCkQZ8QlZw8GRiK%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;813&quot; height=&quot;345&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1222&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;2404&quot; data-origin-height=&quot;1004&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K8OwY/dJMcajo14gi/EFYfv0wKgC0LkQfibxbEZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K8OwY/dJMcajo14gi/EFYfv0wKgC0LkQfibxbEZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K8OwY/dJMcajo14gi/EFYfv0wKgC0LkQfibxbEZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK8OwY%2FdJMcajo14gi%2FEFYfv0wKgC0LkQfibxbEZ1%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;822&quot; height=&quot;343&quot; data-origin-width=&quot;2404&quot; data-origin-height=&quot;1004&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;2. NAT&amp;nbsp;게이트웨이 역할을 하는 EC2 인스턴스를 생성한다.&lt;/b&gt;&lt;/span&gt;&lt;/span&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;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;귀찮음: ⭐️⭐️&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;효과: ⭐️⭐️⭐️&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;장애가 발생해도 나 혼자만 알 것 같은 작귀(^_&amp;lt;)～☆ 프로젝트보다 내 통장이 소중하므로 NAT 게이트웨이 EC2 인스턴스를 생성해 비용을 줄였다. 실무에서 이렇게 사용하는 경우는 거의 없을텐데, &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;높은 대역폭을 감당할 수 있고 장애 발생 시 자동 복구 된다는 점에서 AWS에서 관리해 주는 NAT Gateways(VPC 페이지에서 NAT 게이트웨이를 생성 가능)를 사용하는 게 안정적이기 때문이다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;개인 프로젝트에서 AWS 완전 관리 NAT 게이트웨이를 사용 안 해봐서 정확한 비용 비교는 못했지만 gemini가 대략적으로 계산을 해줬다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;1004&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zrKZM/dJMcabknPu8/p7jjDnXi5LAiBUve5PplR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zrKZM/dJMcabknPu8/p7jjDnXi5LAiBUve5PplR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zrKZM/dJMcabknPu8/p7jjDnXi5LAiBUve5PplR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzrKZM%2FdJMcabknPu8%2Fp7jjDnXi5LAiBUve5PplR0%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;544&quot; height=&quot;380&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;1004&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;1) EC2 인스턴스 생성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;NAT 게이트웨이는 Private subnet의 트래픽을 받아서 외부 인터넷으로 던져주고 다시 받아와야 하는 역할을 하기 때문에 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;Public subnet에 위치시키고, 공인 IP(Public IP)를 갖도록 한다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKvrrB/dJMcadI8aIv/Trt5A1x4hxsWKHwIcV4KU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKvrrB/dJMcadI8aIv/Trt5A1x4hxsWKHwIcV4KU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKvrrB/dJMcadI8aIv/Trt5A1x4hxsWKHwIcV4KU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKvrrB%2FdJMcadI8aIv%2FTrt5A1x4hxsWKHwIcV4KU1%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;783&quot; height=&quot;294&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;706&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;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;2) EC2 인스턴스의 Source/Destination Check 비활성화&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;EC2 인스턴스는 패킷을 주고받을 때 보안상의 목적으로 출발지/목적지 IP가 자신의 IP와 동일한지 체크한다. NAT 게이트웨이는 포워딩 역할을 해줘야 하므로 해당 설정을 비활성화 해야한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2368&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drr1jR/dJMcah5YCjT/8AzG9gIJ4ORkNDmldnZUL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drr1jR/dJMcah5YCjT/8AzG9gIJ4ORkNDmldnZUL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drr1jR/dJMcah5YCjT/8AzG9gIJ4ORkNDmldnZUL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdrr1jR%2FdJMcah5YCjT%2F8AzG9gIJ4ORkNDmldnZUL1%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;873&quot; height=&quot;276&quot; data-origin-width=&quot;2368&quot; data-origin-height=&quot;750&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;1348&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckZuQq/dJMcai4LcEa/1HhelGQgOpG3b02Ye2CBC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckZuQq/dJMcai4LcEa/1HhelGQgOpG3b02Ye2CBC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckZuQq/dJMcai4LcEa/1HhelGQgOpG3b02Ye2CBC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckZuQq%2FdJMcai4LcEa%2F1HhelGQgOpG3b02Ye2CBC0%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;582&quot; height=&quot;325&quot; data-origin-width=&quot;1348&quot; data-origin-height=&quot;752&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;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;3) 인스턴스 내부에서 트래픽 통과되도록 설정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;마지막으로 운영체제 레벨의 설정을 하면 된다. 리눅스는 기본적으로 보안을 위해 IP 포워딩이 비활성화되어 있는데, &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;라우터 역할을 하게끔 IP 포워딩을 활성화한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 커널에서 IP 포워딩 활성화 (기본값: 0)
sudo sysctl -w net.ipv4.ip_forward=1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그리고 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;패킷의 출발지 주소를 EC2 인스턴스의 공인 IP(Public IP)로 변조&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;만약 출발지 주소를 NAT 게이트웨이의 공인 IP(Public IP)로 변조하지 않으면 어떻게 될까? 출발지 주소는 Pirvate Subnet의 사설 IP(Private IP)로 지정되어 버리기 때문에, 외부에서는 어디서 패킷이 출발했는지 알 수 없어 응답을 돌려주지 못한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 보통 외부로 나가는 네트워크 인터페이스인 eth0 지정
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;3. 새로운 프리티어 계정을 생성하고 인프라를 이전한다.&lt;/b&gt;&lt;/span&gt;&lt;/span&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;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;귀찮음: ⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;효과: &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;프리티어 계정을 만들면 기본 크레딧 100달러 + 5개 체험(?)을 통해 받을 수 있는 크레딧 100달러 = 총 200달러의 크레딧을 받을 수 있다. 이 정도면 약 1.5개월은 인프라를 무료로 사용할 수 있으므로 꼭 해야 하는 작업이었다. 실제 서비스라면 데이터 마이그레이션과 서비스 중단을 최소화할 수 있는 방법을 고민해야 한다. 내 경우에는 거의 다 테스트 데이터이므로 데이터 마이그레이션은 패스했고, 도메인 네임서버 변경 작업은 task가 내려간 시간대에 진행했다. 작업을 하면서 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이전에 인프라 구축할 때 겪어보지 못했던 이슈가 있었다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;이슈 1) &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;무지성 복붙의 폐해&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;task 기반으로 배포를 하는데 계속 실패했다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt; 좀 더 자세히 말하면 ECS task 정의할 때 컨테이너 환경변수에 Secrets Manager에 저장한 값을 사용했는데, 새 계정의 Secrets Manger&amp;nbsp;ARN이 아니라 예전 계정의 Secrets Manger&amp;nbsp;ARN을 잘못 기입한 것이다. 로그에 이유가 찍혀서 망정이지 아니었으면 삽질할 뻔했다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;이슈 2) 'ACM 인증서 - Route53 - 가비아' 관계에 대한 이해 부족&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;ACM 인증서 발급 상태가 Pending validation에서 멈췄다. 원인은 가비아 네임 서버에 등록했던 기존 계정의 Route53 NS 주소를 지우지 않고 새 계정의 Route53 NS 주소를 추가했기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;[올바른 작업 순서]&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. Route53 host zone 생성&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2. 가비아에 접속한 뒤, 네임 서버에 새 계정의 NS 주소 모두 입력 및 기존 계정의 Route53 NS 주소는 모두 제거&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;3. ACM에서 SSL/TLS 인증서 발급 (인증서 발급 상태: 검증 대기)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;4. Route53에서 CNAME 타입 레코드 생성&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;5. 발급된 인증서를 ALB 443 리스너에 연결&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;6. Route53에서 A 타입 레코드 생성&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;[추가 설명]&lt;/span&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;span style=&quot;color: #333333;&quot;&gt;2단계: 가비아에 접속한 뒤, 네임 서버에 새 계정의 NS 주소 모두 입력 및 기존 계정의 Route53 NS 주소는 모두 제거&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;도메인 주소&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;에 접근하면 새 계정의 Route53의 네임 서버에 접속된다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;4단계:&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;Route53에서 CNAME 타입 레코드 생성&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;CNAME 타입 레코드 생성 시 3단계에서 발급했던 인증서의&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;CNAME name/value를 사용한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;ACM이 인증서 검증 시, 도메인 주소에 연결된 네임 서버가 있는 host zone에 인증서의 &lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;CNAME name/value와 일치하는 CNAME 레코드의 존재여부 확인&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;일치하는 &lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;CNAME 레코드가 있다면 검증 완료&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&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;&lt;span style=&quot;background-color: #fcfcfc;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;4. 안 쓰는 리소스는 제거한다.&lt;/b&gt;&lt;/span&gt;&lt;/span&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;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;귀찮음: ⭐️&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;효과:&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;⭐️&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;프로젝트용 VPC를 만들어 사용하는 중이기 때문에 default VPC는 삭제했다. 효과는 미미하겠지만 몇 달러라도 아끼기 위하여...&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;&lt;b&gt;귀찮음을 이기면 돈을 아낄 수 있습니다 여러분. &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;&lt;b&gt;이 글을 보는 분들은 경제학적으로 합리적인 삶을 사시길.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bprx6c/dJMcaaThaVq/4XrMU5zTvy3KrLXKeQcK9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bprx6c/dJMcaaThaVq/4XrMU5zTvy3KrLXKeQcK9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bprx6c/dJMcaaThaVq/4XrMU5zTvy3KrLXKeQcK9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbprx6c%2FdJMcaaThaVq%2F4XrMU5zTvy3KrLXKeQcK9k%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;529&quot; height=&quot;346&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;742&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>인프라/AWS</category>
      <category>AWS</category>
      <category>서버비용</category>
      <category>절약</category>
      <author>jaee</author>
      <guid isPermaLink="true">https://jaejade.tistory.com/282</guid>
      <comments>https://jaejade.tistory.com/282#entry282comment</comments>
      <pubDate>Wed, 17 Jun 2026 11:35:08 +0900</pubDate>
    </item>
    <item>
      <title>[wide learning map] 프론트 작업 환경 설정 (Docker, Next.js)</title>
      <link>https://jaejade.tistory.com/275</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;틀린 내용이 있을 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;발견하시면 말씀 부탁드립니다!  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Next.js 앱 설치&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;next app 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774160536495&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npx create-next-app@latest [앱이름] --yes
$ cd [앱이름]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ce8r97/dJMcaiP94HD/xnB9cSq7q0nsRrIPIfIjHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ce8r97/dJMcaiP94HD/xnB9cSq7q0nsRrIPIfIjHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ce8r97/dJMcaiP94HD/xnB9cSq7q0nsRrIPIfIjHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fce8r97%2FdJMcaiP94HD%2FxnB9cSq7q0nsRrIPIfIjHK%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;435&quot; height=&quot;233&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;520&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;size18&quot;&gt;&lt;b&gt;패키지 설치&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774160888072&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ cd frontend
$ npm install next@latest react@latest react-dom@latest

# reactflow: 노드기반 다이어그램 라이브러리
# lucide-react: 아이콘 라이브러리
# axios: 비동기 통신 라이브러리
$ npm install reactflow lucide-react axios&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;size18&quot;&gt;&lt;b&gt;app 폴더 생성&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;Next.js는 파일 시스템 라우팅을 따른다 = 파일 구조에 따라 앱 페이지 라우팅이 된다는 의미&lt;/li&gt;
&lt;li&gt;app 폴더를 생성하고, app 폴더에 파일을 만들면 파일들에 맞춰 라우팅 된다.&lt;/li&gt;
&lt;li&gt;공식 문서에 나와있는 대로 app 폴더 생성 후 하위에 layout.tsx, page.tsx를 만든다.&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;646&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpvrAS/dJMcacoVip7/Qu0dXTiowWRDmezx3wMSZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpvrAS/dJMcacoVip7/Qu0dXTiowWRDmezx3wMSZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpvrAS/dJMcacoVip7/Qu0dXTiowWRDmezx3wMSZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpvrAS%2FdJMcacoVip7%2FQu0dXTiowWRDmezx3wMSZk%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;418&quot; height=&quot;159&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 55px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 38px;&quot;&gt;layout.tsx&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 38px;&quot;&gt;- root layout으로 최상단 레이아웃.&amp;nbsp;&lt;br /&gt;- &amp;lt;html&amp;gt;, &amp;lt;body&amp;gt; 태그가 필수로 있어야함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;page.tsx&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. Docker compose 설정&lt;/b&gt;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Dockerfile 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774161822413&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker init&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1510&quot; data-origin-height=&quot;1064&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjrwI1/dJMcabp2l0x/wXKwNxVrIkDnUTerNM3s20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjrwI1/dJMcabp2l0x/wXKwNxVrIkDnUTerNM3s20/img.png&quot; data-alt=&quot;도커 공식 문서에 나와있는 가이드대로 설정한다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjrwI1/dJMcabp2l0x/wXKwNxVrIkDnUTerNM3s20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjrwI1%2FdJMcabp2l0x%2FwXKwNxVrIkDnUTerNM3s20%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;702&quot; height=&quot;495&quot; data-origin-width=&quot;1510&quot; data-origin-height=&quot;1064&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;도커 공식 문서에 나와있는 가이드대로 설정한다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;frontend 폴더에 Docker관련 파일들이 생성된 것을 확인할 수 있다.&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;636&quot; data-origin-height=&quot;536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NNDMy/dJMcaaq62bu/2PxORNV9hpgYR7KkOQYdWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NNDMy/dJMcaaq62bu/2PxORNV9hpgYR7KkOQYdWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NNDMy/dJMcaaq62bu/2PxORNV9hpgYR7KkOQYdWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNNDMy%2FdJMcaaq62bu%2F2PxORNV9hpgYR7KkOQYdWk%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;353&quot; height=&quot;297&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;536&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;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. standalone 모드 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Dockerfile&lt;span&gt;&amp;nbsp;수정&lt;/span&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/vercel/next.js/blob/canary/examples/with-docker/Dockerfile&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1774163822707&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# ============================================
# Stage 1: Dependencies Installation Stage
# ============================================

# IMPORTANT: Node.js Version Maintenance
# This Dockerfile uses Node.js 24.13.0-slim, which was the latest LTS version at the time of writing.
# To ensure security and compatibility, regularly update the NODE_VERSION ARG to the latest LTS version.
ARG NODE_VERSION=24.13.0-slim

FROM node:${NODE_VERSION} AS dependencies

# Set working directory
WORKDIR /app

# Copy package-related files first to leverage Docker's caching mechanism
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./

# Install project dependencies with frozen lockfile for reproducible builds
RUN --mount=type=cache,target=/root/.npm \
    --mount=type=cache,target=/usr/local/share/.cache/yarn \
    --mount=type=cache,target=/root/.local/share/pnpm/store \
  if [ -f package-lock.json ]; then \
    npm ci --no-audit --no-fund; \
  elif [ -f yarn.lock ]; then \
    corepack enable yarn &amp;amp;&amp;amp; yarn install --frozen-lockfile --production=false; \
  elif [ -f pnpm-lock.yaml ]; then \
    corepack enable pnpm &amp;amp;&amp;amp; pnpm install --frozen-lockfile; \
  else \
    echo &quot;No lockfile found.&quot; &amp;amp;&amp;amp; exit 1; \
  fi

# ============================================
# Stage 2: Build Next.js application in standalone mode
# ============================================

FROM node:${NODE_VERSION} AS builder

# Set working directory
WORKDIR /app

# Copy project dependencies from dependencies stage
COPY --from=dependencies /app/node_modules ./node_modules

# Copy application source code
COPY . .

ENV NODE_ENV=production

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED=1

# Build Next.js application
# If you want to speed up Docker rebuilds, you can cache the build artifacts
# by adding: --mount=type=cache,target=/app/.next/cache
# This caches the .next/cache directory across builds, but it also prevents
# .next/cache/fetch-cache from being included in the final image, meaning
# cached fetch responses from the build won't be available at runtime.
RUN if [ -f package-lock.json ]; then \
    npm run build; \
  elif [ -f yarn.lock ]; then \
    corepack enable yarn &amp;amp;&amp;amp; yarn build; \
  elif [ -f pnpm-lock.yaml ]; then \
    corepack enable pnpm &amp;amp;&amp;amp; pnpm build; \
  else \
    echo &quot;No lockfile found.&quot; &amp;amp;&amp;amp; exit 1; \
  fi

# ============================================
# Stage 3: Run Next.js application
# ============================================

FROM node:${NODE_VERSION} AS runner

# Set working directory
WORKDIR /app

# Set production environment variables
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME=&quot;0.0.0.0&quot;

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the run time.
# ENV NEXT_TELEMETRY_DISABLED=1

# Copy production assets
COPY --from=builder --chown=node:node /app/public ./public

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown node:node .next

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=node:node /app/.next/standalone ./
COPY --from=builder --chown=node:node /app/.next/static ./.next/static

# If you want to persist the fetch cache generated during the build so that
# cached responses are available immediately on startup, uncomment this line:
# COPY --from=builder --chown=node:node /app/.next/cache ./.next/cache

# Switch to non-root user for security best practices
USER node

# Expose port 3000 to allow HTTP traffic
EXPOSE 3000

# Start Next.js standalone server
CMD [&quot;node&quot;, &quot;server.js&quot;]&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;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;next.config.tsx 파일 수정&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774164047801&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import type { NextConfig } from &quot;next&quot;;

const nextConfig: NextConfig = {
  output: &quot;standalone&quot;, // 추가
  reactCompiler: true,
};

export default nextConfig;&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. docker-compose.yml 파일 수정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;frontend 컨테이너 설정 추가&lt;/b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;/span&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-compose.yml 파일은 backend, frontend 앱과 동일한 경로에 있다&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;604&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm4MH1/dJMcajhesqZ/LqKrAOPtFnp09Ts7bM1970/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm4MH1/dJMcajhesqZ/LqKrAOPtFnp09Ts7bM1970/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm4MH1/dJMcajhesqZ/LqKrAOPtFnp09Ts7bM1970/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm4MH1%2FdJMcajhesqZ%2FLqKrAOPtFnp09Ts7bM1970%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;415&quot; height=&quot;154&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1774162205014&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3.8'
services:
  # ...
  # frontend 컨테이너 설정 추가
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    env_file:
      - .env
    container_name: frontend
    ports:
      - 3000:3000
    volumes:
      - ./frontend:/frontend
    environment:
      NODE_ENV: production&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. next.js 컨테이너 실행 및 확인&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1774164270442&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker-compose up --build&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/prZ6P/dJMcad2nv2z/brkNkBsVA6OWsqRyDBwGl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/prZ6P/dJMcad2nv2z/brkNkBsVA6OWsqRyDBwGl0/img.png&quot; data-alt=&quot;컨테이너가 정상 동작하면 page.tsx의 내용이 화면에 뜨는걸 볼 수 있다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/prZ6P/dJMcad2nv2z/brkNkBsVA6OWsqRyDBwGl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FprZ6P%2FdJMcad2nv2z%2FbrkNkBsVA6OWsqRyDBwGl0%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;521&quot; height=&quot;160&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;컨테이너가 정상 동작하면 page.tsx의 내용이 화면에 뜨는걸 볼 수 있다&lt;/figcaption&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;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;참고자료&lt;/span&gt;&lt;/i&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #9d9d9d;&quot;&gt;&lt;i&gt;&lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://docs.docker.com/guides/reactjs/containerize/#generate-a-dockerfile&quot;&gt;https://docs.docker.com/guides/reactjs/containerize/#generate-a-dockerfile&lt;/a&gt;&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #9d9d9d;&quot;&gt;&lt;i&gt;&lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://nextjs.org/docs/app/getting-started/installation&quot;&gt;https://nextjs.org/docs/app/getting-started/installation&lt;/a&gt;&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #9d9d9d;&quot;&gt;&lt;i&gt;&lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://github.com/vercel/next.js/tree/canary/examples/with-docker&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/vercel/next.js/tree/canary/examples/with-docker&lt;/a&gt;&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트</category>
      <author>jaee</author>
      <guid isPermaLink="true">https://jaejade.tistory.com/275</guid>
      <comments>https://jaejade.tistory.com/275#entry275comment</comments>
      <pubDate>Sun, 22 Mar 2026 16:33:00 +0900</pubDate>
    </item>
    <item>
      <title>[wide learning map] 백엔드 작업 환경 설정 (Docker, FastAPI, PostgreSQL)</title>
      <link>https://jaejade.tistory.com/272</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;틀린 내용이 있을 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;발견하시면 말씀 부탁드립니다!  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;1. 라이브러리 설치&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1773121257192&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# fastapi 설치
$ pip install &quot;fastapi[standard]&quot;

# fastapi 개발 서버 실행
$ fastapi dev main.py&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqDC3Q/dJMcahwRgsa/JkdC6GTdOGr6LlDWvYnlkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqDC3Q/dJMcahwRgsa/JkdC6GTdOGr6LlDWvYnlkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqDC3Q/dJMcahwRgsa/JkdC6GTdOGr6LlDWvYnlkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqDC3Q%2FdJMcahwRgsa%2FJkdC6GTdOGr6LlDWvYnlkK%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;577&quot; height=&quot;269&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;468&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;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;2. 백엔드 파일 구조 설계&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;마이그레이션 환경 초기화&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;alembic.ini 파일은 백엔드 루트 경로, alembic 폴더는 app/ 하위에 자동 생성됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1773196266629&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# alembic 설치
$ pip install alembic

# 마이그레이션 환경 초기화 
$ alembic init app/alembic&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;파일 구조 설계&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Django와 달리 Fastapi는 작업자가 처음부터 끝까지 직접 파일 구조를 짜야한다 = 유연한데 수고로움&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Fastapi 개발자가 만든 공식 Fastapi 템플릿인 &lt;a href=&quot;https://github.com/fastapi/full-stack-fastapi-template&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Full stack Fastapi&lt;/a&gt; 템플릿 참고&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1773195650426&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;wide_learning_map # 프로젝트 루트
├── frontend # 프론트 작업 루트
└── backend # 백엔드 작업 루트
    ├── alembic.ini # Alembic의 환경 설정 파일 (DB 마이그레이션 설정 파일)
    ├── app
    │&amp;nbsp;&amp;nbsp; ├── __init__.py
    │   ├── alembic # 마이그레이션 히스토리 저장소
    │   │&amp;nbsp;&amp;nbsp; ├── env.py
    │   │&amp;nbsp;&amp;nbsp; ├── README
    │   │&amp;nbsp;&amp;nbsp; ├── script.py.mako
    │   │&amp;nbsp;&amp;nbsp; └── versions
    │&amp;nbsp;&amp;nbsp; ├── api 
    │&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── __init__.py
    │&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── main.py # 여러 개로 쪼개진 routes/들을 하나로 묶어서 메인 앱에 전달하는 허브 역할
    │&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; └── routes # 실제 API 엔드포인트가 위치
    │&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp;     └── __init__.py
    │&amp;nbsp;&amp;nbsp; ├── core # 프로젝트의 핵심 설정
    │&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; └── __init__.py
    │&amp;nbsp;&amp;nbsp; ├── crud.py # 순수 DB 작업 실행 (create, select 등)
    │&amp;nbsp;&amp;nbsp; ├── initial_data.py
    │&amp;nbsp;&amp;nbsp; ├── main.py # 전체 FastAPI 애플리케이션의 엔트리 포인트. 앱 객체를 생성하고 라우터를 연결.
    │&amp;nbsp;&amp;nbsp; ├── models.py # SQLModel을 사용해 실제 DB 테이블 구조를 정의
    │   ├── services.py # 비즈니스 로직 전담
    │&amp;nbsp;&amp;nbsp; └── utils.py # 반복적으로 사용되는 유틸함수
    ├── Dockerfile
    ├── pyproject.toml # 프로젝트의 의존성(라이브러리 버전)과 빌드 설정 관리
    └── scripts
        └── prestart.sh&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;3.&lt;b&gt;&amp;nbsp;Docker compose 설정&lt;/b&gt;&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;백엔드 작업부터 진행할거라 db와 backend 컨테이너 정보만 작성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;민감 정보는 .env 파일에 작성하여 docker-compose.yml에는 미노출되게 처리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1773283242695&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3.8'
services:
  db:
    image: postgres:15
    container_name: postgres
    volumes:
      - postgres_data_wlm:/var/lib/postgresql/data
    env_file: .env
    ports:
      - &quot;5433:5432&quot;
    networks:
      - mynetwork
    healthcheck: # DB가 쿼리를 받을 준비가 됐는지 체크
      test: [&quot;CMD-SHELL&quot;, &quot;pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB&quot;]
      interval: 5s
      timeout: 5s
      retries: 5

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.dev
    container_name: backend
    ports:
      - &quot;80:80&quot; # 호스트:컨테이너
    volumes:
      - ./backend:/code
    networks:
      - mynetwork
    restart: always # 컨테이너 종료 시 자동 재시작


volumes:
  postgres_data_wlm: # 볼륨 정의

networks:
  mynetwork: # 네트워크 정의&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;4. DB 마이그레이션&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Django와 마찬가지로 Fastapi도 마이그레이션 스크립트를 생성한 뒤 해당 스크립트 기반으로 마이그레이션을 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;app/alembic/env.py 파일 수정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773209392903&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. app/alembic/env.py 수정
# 상단에 2줄 추가
from app.models import SQLModel  # 우리가 만든 모델의 베이스
import app.models  # 모든 모델 클래스를 불러오기 위해 필요

# target_metadata를 설정해야 Alembic이 모델 변경을 감지
target_metadata = SQLModel.metadata&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;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;alembic.ini 파일 수정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773210132713&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sqlalchemy.url = [DB drive]://[DB유저명]:[DB비밀번호]@[DB호스트:포트번호]/[DB명]&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;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;마이그레이션 스크립트 파일 생성&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;app/alembic/versions 경로에 마이그레이션 파일만 생성한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1773209203648&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ alembic revision --autogenerate -m &quot;Initial migration&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSzW6I/dJMcacPQRNc/uGRWOTMRxER1NY6gF56bi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSzW6I/dJMcacPQRNc/uGRWOTMRxER1NY6gF56bi1/img.png&quot; data-alt=&quot;마이그레이션 완료되면 app/alembic/versions에 마이그레이션 파일 생성됨&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSzW6I/dJMcacPQRNc/uGRWOTMRxER1NY6gF56bi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSzW6I%2FdJMcacPQRNc%2FuGRWOTMRxER1NY6gF56bi1%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;426&quot; height=&quot;253&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;마이그레이션 완료되면 app/alembic/versions에 마이그레이션 파일 생성됨&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;마이그레이션 실행&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;실제 DB 테이블을 생성한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1773210039287&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ alembic upgrade head&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wjtN7/dJMcaadruRZ/ku9SUlU2jk5KvQgY1KYkDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wjtN7/dJMcaadruRZ/ku9SUlU2jk5KvQgY1KYkDk/img.png&quot; data-alt=&quot;DB에 테이블 생성 완료&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wjtN7/dJMcaadruRZ/ku9SUlU2jk5KvQgY1KYkDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwjtN7%2FdJMcaadruRZ%2Fku9SUlU2jk5KvQgY1KYkDk%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;440&quot; height=&quot;184&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;342&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;DB에 테이블 생성 완료&lt;/figcaption&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;참고자료&lt;/span&gt;&lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #9d9d9d;&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #9d9d9d;&quot;&gt;&lt;i&gt;&lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://github.com/fastapi/full-stack-fastapi-template&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/fastapi/full-stack-fastapi-template&lt;/a&gt;&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #9d9d9d;&quot;&gt;&lt;i&gt;&lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://wikidocs.net/318196&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wikidocs.net/318196&lt;/a&gt;&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로젝트</category>
      <author>jaee</author>
      <guid isPermaLink="true">https://jaejade.tistory.com/272</guid>
      <comments>https://jaejade.tistory.com/272#entry272comment</comments>
      <pubDate>Tue, 10 Mar 2026 15:06:02 +0900</pubDate>
    </item>
    <item>
      <title>[개인] 로또 번호 추천</title>
      <link>https://jaejade.tistory.com/269</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;a href=&quot;https://jam.me.kr&quot;&gt;&amp;gt;&amp;gt; https://jam.me.kr &amp;lt;&amp;lt;&lt;/a&gt;&lt;/h2&gt;
&lt;figure id=&quot;og_1773031929153&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Lucky Lotto&quot; data-og-description=&quot;  오늘의 행운 번호와 성공 확률을 확인해보세요&quot; data-og-host=&quot;jam.me.kr&quot; data-og-source-url=&quot;https://jam.me.kr&quot; data-og-url=&quot;https://jam.me.kr&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://jam.me.kr&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jam.me.kr&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Lucky Lotto&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  오늘의 행운 번호와 성공 확률을 확인해보세요&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jam.me.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 목표는 1. 인프라를 처음부터 끝까지 구축해보는 것 2. 프론트엔드 개발까지 해보는 것 총 2개였다. (자세한 내용은 포트폴리오에...) 이걸 가능하게 해준 제미나이에게 모든 영광을 &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;840&quot; data-origin-height=&quot;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ce1fA1/dJMcag5LDpf/1MHHYNiAAJlkN0vpk5okW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ce1fA1/dJMcag5LDpf/1MHHYNiAAJlkN0vpk5okW0/img.png&quot; data-alt=&quot;못생겼어도 내가 만든 내새끼 &amp;amp;hearts;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ce1fA1/dJMcag5LDpf/1MHHYNiAAJlkN0vpk5okW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fce1fA1%2FdJMcag5LDpf%2F1MHHYNiAAJlkN0vpk5okW0%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;408&quot; height=&quot;307&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;632&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;못생겼어도 내가 만든 내새끼 &amp;hearts;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>프로젝트</category>
      <author>jaee</author>
      <guid isPermaLink="true">https://jaejade.tistory.com/269</guid>
      <comments>https://jaejade.tistory.com/269#entry269comment</comments>
      <pubDate>Mon, 9 Mar 2026 13:58:17 +0900</pubDate>
    </item>
    <item>
      <title>[python] 더맵게</title>
      <link>https://jaejade.tistory.com/261</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;문제&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;링크: &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42626&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/42626&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1768280248817&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42626&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bOEnTD/dJMb9fZoyG9/9lZyu7WzrIhVk0TwVEO9gK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/tDYTC/dJMb88FX7om/XXvf3wSudAJmPOx05xRfrk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42626&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42626&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bOEnTD/dJMb9fZoyG9/9lZyu7WzrIhVk0TwVEO9gK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/tDYTC/dJMb88FX7om/XXvf3wSudAJmPOx05xRfrk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;모든 음식의 스코빌 지수를 K 이상으로 만들기 위해 섞어야 하는 최소 횟수를 return 하도록 solution 함수를 작성&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;python&quot; style=&quot;background-color: #e9ecf3; color: #263747;&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;섞은 음식의 스코빌 지수 = 가장 맵지 않은 음식의 스코빌 지수 + (두 번째로 맵지 않은 음식의 스코빌 지수 * 2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;풀이&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 안매운 메뉴, 두번째로 안매운 메뉴를 뽑아 계산해야됨 = heap 관련 문제
&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://wikidocs.net/105044&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;python heapq 참고&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1768277219915&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import heapq

def solution(scoville, K):
    answer = 0

    # 가장 안매운 메뉴가 K보다 크다면 0 리턴
    if min(scoville) &amp;gt; K:
        return 0

    # scoville을 heap 구조로 변경
    heapq.heapify(scoville)
    smallest = heapq.heappop(scoville)

    while smallest &amp;lt; K:
        if not scoville:
            return -1

        answer += 1

        next = heapq.heappop(scoville) * 2
        heapq.heappush(scoville, smallest + next)
        smallest = heapq.heappop(scoville)

    return answer&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘, 자료구조/프로그래머스</category>
      <author>jaee</author>
      <guid isPermaLink="true">https://jaejade.tistory.com/261</guid>
      <comments>https://jaejade.tistory.com/261#entry261comment</comments>
      <pubDate>Tue, 13 Jan 2026 13:11:12 +0900</pubDate>
    </item>
    <item>
      <title>[Python] 숫자 변환하기</title>
      <link>https://jaejade.tistory.com/260</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;링크: &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/154538&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/154538&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1768280319581&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/154538&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/91GlJ/dJMb81fLRrO/JImunZ23YRu0b7iqhAXZs0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/zW1Al/dJMb9g44osr/Th0x5Sj7XFq4UnZF71kyg1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/154538&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/154538&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/91GlJ/dJMb81fLRrO/JImunZ23YRu0b7iqhAXZs0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/zW1Al/dJMb9g44osr/Th0x5Sj7XFq4UnZF71kyg1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;자연수&amp;nbsp;&lt;/span&gt;x&lt;span style=&quot;text-align: left;&quot;&gt;,&amp;nbsp;&lt;/span&gt;y&lt;span style=&quot;text-align: left;&quot;&gt;,&amp;nbsp;&lt;/span&gt;n&lt;span style=&quot;text-align: left;&quot;&gt;이 매개변수로 주어질 때,&amp;nbsp;&lt;/span&gt;x&lt;span style=&quot;text-align: left;&quot;&gt;를&amp;nbsp;&lt;/span&gt;y&lt;span style=&quot;text-align: left;&quot;&gt;로 변환하기 위해 필요한 최소 연산 횟수를 return하도록 solution 함수 작성 &lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(x&lt;span style=&quot;text-align: left;&quot;&gt;를&amp;nbsp;&lt;/span&gt;y&lt;span style=&quot;text-align: left;&quot;&gt;로 만들 수 없다면 -1 return).&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768278118543&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 사용 가능한 연산
x + n
x * 2
x * 3

# 제한 사항
1 &amp;le; x &amp;le; y &amp;le; 1,000,000
1 &amp;le; n &amp;lt; y&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;풀이&lt;/b&gt;&lt;/h4&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;최소 연산횟수 = 최단 거리 탐색 = bfs(너비 우선 탐색) 알고리즘 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767934602904&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import deque

def solution(x, y, n):
    if x == y:
        return 0
    
    q = deque() # bfs이므로 큐 사용
    q.append((x, 0)) # 현재값, 현재횟수
    visited = set([x]) # 재탐색 방지용
    
    while q:
        cur, cnt = q.popleft()
        
        for nxt in (cur+n, cur*2, cur*3):
            if nxt == y:
                return cnt + 1
            
            if nxt &amp;lt; y and nxt not in visited:
                visited.add(nxt)
                # 탐색된 순서대로 큐에 저장
                q.append((nxt, cnt + 1))

    return -1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘, 자료구조/프로그래머스</category>
      <author>jaee</author>
      <guid isPermaLink="true">https://jaejade.tistory.com/260</guid>
      <comments>https://jaejade.tistory.com/260#entry260comment</comments>
      <pubDate>Fri, 9 Jan 2026 14:02:46 +0900</pubDate>
    </item>
    <item>
      <title>[Python] 뒤에 있는 큰 수 찾기</title>
      <link>https://jaejade.tistory.com/257</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;링크: &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/154539&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/154539&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1768280387431&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/154539&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b3RDrw/dJMb87NPxjH/LK3qHB1g3BTmGC72PGfJvK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bSleCK/dJMb86OU3Pf/kfZbWxbAF09NJufsyrYEmK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/154539&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/154539&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b3RDrw/dJMb87NPxjH/LK3qHB1g3BTmGC72PGfJvK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bSleCK/dJMb86OU3Pf/kfZbWxbAF09NJufsyrYEmK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;정수 배열&amp;nbsp;&lt;/span&gt;numbers&lt;span style=&quot;text-align: left;&quot;&gt;가 매개변수로 주어질 때,&amp;nbsp;&lt;/span&gt;모든 원소에 대한 &lt;u&gt;뒷 큰수&lt;/u&gt;들을 차례로 담은 배열을 return 하도록 solution 함수 작성&lt;/span&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;/ul&gt;
&lt;pre id=&quot;code_1768278476214&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 제한 사항
4 &amp;le; numbers의 길이 &amp;le; 1,000,000
1 &amp;le; numbers[i] &amp;le; 1,000,000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;풀이&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;처음 작성한 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;시간 초과로 실패: for 문 안에 for 문이 있음. 시간복잡도: O(n^2)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767585995630&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def solution(numbers):
    # 기본값들을 -1 로 채워넣음
    answer = [-1] * len(numbers)

    for i, number in enumerate(numbers):
        for k in range(i, len(numbers)):
            # 큰 수가 나오면 인덱스의 값에 해당값 할당
           	if number &amp;lt; numbers[k]:
                answer[i] = numbers[k]
                break
    
    return answer&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;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;개선한 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;stack 사용하여 for문 안에 for문 사용 안하도록 개선. 시간 복잡도: O(n)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767587749609&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def solution(numbers):
    answer = [-1] * len(numbers)
    index_stack = []

    for i, number in enumerate(numbers):
    	# 현재 숫자보다 작은 인덱스값을 모두 확인하기 위해 while문 사용
        # if 문을 사용하면 바로 직전의 인덱스값만 확인할 수 있는 문제가 있음
        while index_stack and numbers[index_stack[-1]] &amp;lt; number:
            index = index_stack.pop()
            answer[index] = number
            
        # 자기 자신과 비교를 피하기 위해 while문 밑에서 처리
        index_stack.append(i)

    return answer&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘, 자료구조/프로그래머스</category>
      <author>jaee</author>
      <guid isPermaLink="true">https://jaejade.tistory.com/257</guid>
      <comments>https://jaejade.tistory.com/257#entry257comment</comments>
      <pubDate>Mon, 5 Jan 2026 13:40:12 +0900</pubDate>
    </item>
    <item>
      <title>[Python] 방문 길이</title>
      <link>https://jaejade.tistory.com/255</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크: &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/49994&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/49994&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1768280201900&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/49994&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/TGRLZ/dJMb86OU3M6/QpP2CIRubB7xUQ8mfzzAhK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/GLsP9/dJMb9eTISMe/HaBWOkz7GkH4ZbEBctoPHK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/49994&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/49994&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/TGRLZ/dJMb86OU3M6/QpP2CIRubB7xUQ8mfzzAhK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/GLsP9/dJMb9eTISMe/HaBWOkz7GkH4ZbEBctoPHK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;게임 캐릭터를 4가지 명령어를 통해 움직일 때, &lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;게임 캐릭터가 지나간 길 중&amp;nbsp;&lt;/span&gt;캐릭터가 처음 걸어본 길의 길이&lt;span style=&quot;text-align: left;&quot;&gt;를 구하는 함수 작성.&lt;/span&gt;&lt;/span&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;U: 위쪽으로 한칸 이동&lt;/li&gt;
&lt;li&gt;D: 아래쪽으로 한칸 이동&lt;/li&gt;
&lt;li&gt;R: 오른쪽으로 한칸 이동&lt;/li&gt;
&lt;li&gt;L: 왼쪽으로 한칸 이동&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;캐릭터는 좌표평면의 (0,0) 위치에서 시작하며, 좌표평면의 경계는&lt;span&gt;&amp;nbsp;&lt;/span&gt;(-5, 5), 왼쪽 아래(-5, -5), 오른쪽 위(5, 5), 오른쪽 아래(5, -5)로 이루어져 있다. 좌표 평면의 경계를 넘어가는 명령어는 무시한다&lt;/b&gt;&lt;span style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768280003026&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 제한 사항
dirs는 string형으로 주어지며, 'U', 'D', 'R', 'L' 이외에 문자는 주어지지 않음
dirs의 길이는 500 이하의 자연수&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;풀이&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;처음에 작성한 코드&lt;/p&gt;
&lt;pre id=&quot;code_1767330740485&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def solution(dirs):
    curr_point = (0,0)
    routes = set()
    
    for d in dirs:
    	# 현재 좌표를 시작점으로 두고
        start_point = curr_point
        
        # 조건에 해당하면 x, y 좌표 이동
        if d == 'U' and curr_point[1] &amp;lt; 5:
            curr_point = (curr_point[0], curr_point[1] + 1)
        elif d == 'D' and curr_point[1] &amp;gt; -5:
            curr_point = (curr_point[0], curr_point[1] - 1)
        elif d == 'R' and curr_point[0] &amp;lt; 5:
            curr_point = (curr_point[0] + 1, curr_point[1])
        elif d == 'L' and curr_point[0] &amp;gt; -5:
            curr_point = (curr_point[0] - 1, curr_point[1])
        else:
            continue
            
        routes.add(tuple(sorted((start_point, curr_point))))
        
    return len(routes)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;가독성 개선한 코드&lt;/h4&gt;
&lt;pre id=&quot;code_1767331288733&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def solution(dirs):
    # 규칙 선언
    move = {
        'U': (0, 1),
        'D': (0, -1),
        'L': (-1, 0),
        'R': (1, 0)
    }

    x, y = 0, 0
    routes = set()

    for d in dirs:
        dx, dy = move[d] # move에서 규칙에 맞는 튜플 요소 추출
        nx, ny = x + dx, y + dy # 현재 좌표와 튜플 요소 합산하여 다음 좌표 구하기

        if not (-5 &amp;lt;= nx &amp;lt;= 5 and -5 &amp;lt;= ny &amp;lt;= 5):
            continue

        curr = (x, y) # 현재좌표
        next = (nx, ny) # 다음좌표

        routes.add(tuple(sorted((curr, next))))

        x, y = nx, ny

    return len(routes)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘, 자료구조/프로그래머스</category>
      <author>jaee</author>
      <guid isPermaLink="true">https://jaejade.tistory.com/255</guid>
      <comments>https://jaejade.tistory.com/255#entry255comment</comments>
      <pubDate>Fri, 2 Jan 2026 14:15:08 +0900</pubDate>
    </item>
    <item>
      <title>[✅ 상시 업데이트] 사용 안하면 까먹는 명령어들</title>
      <link>https://jaejade.tistory.com/247</link>
      <description>&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;Python 가상환경 생성 &amp;amp; 실행 &amp;amp; 종료&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1773369859028&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 경로는 편한대로. 내 경우엔 /Project/backend

# 가상환경 생성
$ cd /Project/backend
$ python3 -m venv [가상환경 이름]

# 가상환경 실행
$ source [가상환경 이름]/bin/activate

# 가상환경 종료
$ deactivate&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;Git&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1773369859030&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 1. 기존 git origin 저장소에서 local로 복사하는 경우
$ git clone [깃헙 레포지토리 주소]
# -&amp;gt; 폴더 &amp;amp; .git 생성, origin remote 등록, 최신 코드 다운로드 한큐에 됨


# 2. 로컬 프로젝트를 GitHub에 처음 연결할 때
# 1) git 폴더 설정
$ git init
# 2) origin remote 등록
$ git remote add origin [깃허브 레포지토리 주소]&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;폴더 구조 확인&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1773369859030&quot; class=&quot;elixir&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 현재 경로 기준 폴더 구조 
$ tree .

# 2depth까지 확인
$ tree -L 2

# 특정 폴더 제외
$ tree -I &quot;[제외할 폴더명]&quot;

# 여러개 폴더 제외 
$ tree -I &quot;[제외할 폴더명1]|[제외할 폴더명2]&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;Tmux&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1773369859031&quot; class=&quot;elixir&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 여러개 pane에 동시 입력 on
$ setw synchronize-panes on
# 동시 입력 off
$ setw synchronize-panes off

# 화면 분할 (윈도우 하나에 여러개 pane 만들기)
$ ctrl + v: 가로 분할
$ ctrl + h: 세로 분할

# 새 윈도우 띄우기
$ ctrl + a, c

# 다음 윈도우로 이동
$ ctrl + a, n

# 특정 윈도우로 이동
$ ctrl + a, 윈도우 창 번호&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;(Mac OS) 포트번호 점유하고 있는 프로그램 확인&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1774156817990&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ lsof -i :[포트번호]&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;쉘 파일 실시간 읽기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1774251731328&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ tail -f [파일명]&lt;/code&gt;&lt;/pre&gt;</description>
      <category>기타</category>
      <author>jaee</author>
      <guid isPermaLink="true">https://jaejade.tistory.com/247</guid>
      <comments>https://jaejade.tistory.com/247#entry247comment</comments>
      <pubDate>Sat, 11 Oct 2025 23:46:42 +0900</pubDate>
    </item>
    <item>
      <title>Error: pg_config executable not found</title>
      <link>https://jaejade.tistory.com/243</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;r_1825530_6oF8Q.jpg&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5nVHF/btsONwbQm5Z/1bzcVXsx7MlyV9wCv3Cpd1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5nVHF/btsONwbQm5Z/1bzcVXsx7MlyV9wCv3Cpd1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5nVHF/btsONwbQm5Z/1bzcVXsx7MlyV9wCv3Cpd1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5nVHF%2FbtsONwbQm5Z%2F1bzcVXsx7MlyV9wCv3Cpd1%2Fimg.jpg&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;332&quot; height=&quot;332&quot; data-filename=&quot;r_1825530_6oF8Q.jpg&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;400&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;기본적인 내용이지만 나와같은 에러를 마주한 분들을 위해 기록.&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;장고 서버 실행시 에러 발생. pip list로 확인해보니 psycopg2 모듈이 없었음.&lt;/p&gt;
&lt;pre id=&quot;code_1750683494841&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ModuleNotFoundError: No module named 'psycopg2'&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;[참고] psycopg2란?&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;PostgreSQL을 Python에서 사용하기 위한 어댑터.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;이 모듈은 PostgreSQL DB 연결을 설정하고, SQL 쿼리를 실행하며, DB작업을 수행하는데 필요한 기능을 제공&lt;/span&gt;&lt;br /&gt;&lt;a href=&quot;https://pypi.org/project/psycopg2/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pypi.org/project/psycopg2/&lt;/a&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pip install psycopg2 로 psycopg2 모듈 설치 시도했는데 다른 에러 발생. &lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;which pg_config 로 pg_config의 위치를 확인해보니&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;pg_config not found로 pg_config가 없는걸 확인&lt;/p&gt;
&lt;pre id=&quot;code_1750683532983&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Error: pg_config executable not found&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;[참고] pg_config란?&lt;br /&gt;설치된 PostgreSQL 버전에 대한 여러 정보들을 보여주는 도구&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;(나는 맥을 쓰기 때문에) brew install postgresql 로 postgresql을 설치하고 다시 pg_config 경로 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 pip install psycopg2로 모듈 설치 및 장고 서버 실행.&lt;/p&gt;</description>
      <category>언어, 프레임워크/Python &amp;amp; Django</category>
      <author>jaee</author>
      <guid isPermaLink="true">https://jaejade.tistory.com/243</guid>
      <comments>https://jaejade.tistory.com/243#entry243comment</comments>
      <pubDate>Tue, 6 May 2025 22:38:16 +0900</pubDate>
    </item>
  </channel>
</rss>