- config/services.php fallback 기본값 변경 - AiConfig DEFAULT_MODELS 상수 + getActiveGemini() fallback 변경 - NotionService fallback 변경 - AI 설정 관리 UI placeholder/기본값 변경 - Google Cloud AI 가이드 서비스 현황 모델명 변경 - 환경변수 관리 아카데미 예시 변경
1010 lines
65 KiB
PHP
1010 lines
65 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', '.env 관리 정책')
|
|
|
|
@push('styles')
|
|
<style>
|
|
/* 이미지 기본 스타일 */
|
|
.academy-img-hover {
|
|
transition: box-shadow 0.2s ease;
|
|
}
|
|
.academy-img-hover:hover {
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
.academy-img-wrap {
|
|
overflow: hidden;
|
|
border-radius: 0.75rem;
|
|
}
|
|
|
|
/* hover 프리뷰 오버레이 */
|
|
#hover-preview {
|
|
display: none;
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 45;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
backdrop-filter: blur(3px);
|
|
pointer-events: none;
|
|
}
|
|
#hover-preview.is-active {
|
|
display: flex;
|
|
pointer-events: none;
|
|
}
|
|
#hover-preview img {
|
|
max-height: 80vh;
|
|
max-width: 85vw;
|
|
border-radius: 0.75rem;
|
|
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.5);
|
|
opacity: 0;
|
|
transform: scale(0.3);
|
|
transition: opacity 0.25s ease, transform 0.25s ease;
|
|
}
|
|
#hover-preview.is-active img {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
#hover-preview .hover-caption {
|
|
position: absolute;
|
|
bottom: 2rem;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
color: rgba(255,255,255,0.85);
|
|
font-size: 0.8rem;
|
|
background: rgba(0,0,0,0.5);
|
|
padding: 0.4rem 1rem;
|
|
border-radius: 2rem;
|
|
white-space: nowrap;
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease 0.15s;
|
|
}
|
|
#hover-preview.is-active .hover-caption {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* 클릭 라이트박스 */
|
|
#lightbox {
|
|
display: none;
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 50;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(0, 0, 0, 0.9);
|
|
backdrop-filter: blur(4px);
|
|
}
|
|
#lightbox.is-open {
|
|
display: flex;
|
|
}
|
|
#lightbox img {
|
|
max-height: 90vh;
|
|
max-width: 90vw;
|
|
border-radius: 0.5rem;
|
|
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
|
|
}
|
|
</style>
|
|
@endpush
|
|
|
|
@section('content')
|
|
<div class="max-w-6xl mx-auto">
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 히어로 배너 --}}
|
|
{{-- ============================================================ --}}
|
|
<div class="rounded-2xl overflow-hidden mb-8 shadow-lg" style="background: linear-gradient(135deg, #0d2d1f 0%, #0f172a 100%);">
|
|
<div class="flex items-center" style="flex-wrap: wrap;">
|
|
<div style="flex: 1 1 300px; padding: 2rem 2.5rem;">
|
|
<div class="flex items-center gap-2 text-sm mb-2" style="color: #34d399;">
|
|
<span>아카데미</span>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
|
|
<span style="color: #ffffff;">.env 관리 정책</span>
|
|
</div>
|
|
<h1 class="text-3xl font-bold mb-2" style="color: #ffffff;">.env 관리 정책</h1>
|
|
<p class="text-sm" style="color: #cbd5e1;">SAM 프로젝트의 환경 변수 관리 — 열쇠 고리처럼 서비스를 여는 비밀 설정 파일</p>
|
|
</div>
|
|
<div class="shrink-0" style="width: 240px; padding: 1.5rem;">
|
|
<div class="overflow-hidden rounded-xl">
|
|
<img src="{{ asset('images/academy/env-management/1.svg') }}" alt=".env 열쇠 고리 비유"
|
|
class="w-full rounded-xl cursor-pointer academy-img-hover"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex gap-8">
|
|
{{-- ============================================================ --}}
|
|
{{-- 좌측 고정 목차 (TOC) --}}
|
|
{{-- ============================================================ --}}
|
|
<nav class="hidden lg:block shrink-0" style="width: 220px;">
|
|
<div class="sticky top-24">
|
|
<div class="bg-emerald-50 border border-emerald-200 rounded-xl p-5">
|
|
<h2 class="font-semibold text-emerald-800 mb-3 flex items-center gap-2 text-sm">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /></svg>
|
|
목차
|
|
</h2>
|
|
<div class="space-y-0.5 text-xs">
|
|
<a href="#env-what" class="block text-emerald-700 hover:text-emerald-900 py-1 font-medium">1. .env란 무엇인가?</a>
|
|
<a href="#env-analogy" class="block text-emerald-600 hover:text-emerald-800 py-0.5 pl-3">열쇠 고리 비유</a>
|
|
<a href="#env-example" class="block text-emerald-600 hover:text-emerald-800 py-0.5 pl-3">.env.example vs .env</a>
|
|
|
|
<a href="#env-structure" class="block text-emerald-700 hover:text-emerald-900 py-1 font-medium mt-2">2. SAM의 .env 구조</a>
|
|
<a href="#env-mng" class="block text-emerald-600 hover:text-emerald-800 py-0.5 pl-3">MNG 카테고리</a>
|
|
<a href="#env-api" class="block text-emerald-600 hover:text-emerald-800 py-0.5 pl-3">API 카테고리</a>
|
|
|
|
<a href="#env-docker" class="block text-emerald-700 hover:text-emerald-900 py-1 font-medium mt-2">3. Docker Override</a>
|
|
<a href="#env-priority" class="block text-emerald-600 hover:text-emerald-800 py-0.5 pl-3">우선순위</a>
|
|
<a href="#env-override-example" class="block text-emerald-600 hover:text-emerald-800 py-0.5 pl-3">실제 예시</a>
|
|
|
|
<a href="#env-sync" class="block text-emerald-700 hover:text-emerald-900 py-1 font-medium mt-2">4. 동기화 필수 변수</a>
|
|
<a href="#env-sync-shared" class="block text-emerald-600 hover:text-emerald-800 py-0.5 pl-3">공유 API 키</a>
|
|
<a href="#env-sync-path" class="block text-emerald-600 hover:text-emerald-800 py-0.5 pl-3">경로가 다른 변수</a>
|
|
|
|
<a href="#env-local-server" class="block text-emerald-700 hover:text-emerald-900 py-1 font-medium mt-2">5. 로컬 vs 서버</a>
|
|
<a href="#env-debug-danger" class="block text-emerald-600 hover:text-emerald-800 py-0.5 pl-3">APP_DEBUG 위험</a>
|
|
|
|
<a href="#env-change" class="block text-emerald-700 hover:text-emerald-900 py-1 font-medium mt-2">6. 변경 후 할 일</a>
|
|
<a href="#env-cache" class="block text-emerald-600 hover:text-emerald-800 py-0.5 pl-3">캐시 클리어</a>
|
|
<a href="#env-onboarding" class="block text-emerald-600 hover:text-emerald-800 py-0.5 pl-3">온보딩 체크리스트</a>
|
|
|
|
<a href="#env-caution" class="block text-emerald-700 hover:text-emerald-900 py-1 font-medium mt-2">7. 주의사항 & 실수</a>
|
|
<a href="#env-forbidden" class="block text-emerald-600 hover:text-emerald-800 py-0.5 pl-3">금지 카드</a>
|
|
<a href="#env-troubleshoot" class="block text-emerald-600 hover:text-emerald-800 py-0.5 pl-3">증상별 해결</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 우측 콘텐츠 --}}
|
|
{{-- ============================================================ --}}
|
|
<div class="flex-1 min-w-0 space-y-10">
|
|
|
|
{{-- 모바일 목차 --}}
|
|
<div class="lg:hidden bg-emerald-50 border border-emerald-200 rounded-xl p-4 mb-6">
|
|
<details>
|
|
<summary class="font-semibold text-emerald-800 text-sm cursor-pointer flex items-center gap-2">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /></svg>
|
|
목차 보기
|
|
</summary>
|
|
<nav class="mt-3 space-y-1 text-sm">
|
|
<a href="#env-what" class="block text-emerald-700 hover:text-emerald-900 py-1">1. .env란 무엇인가?</a>
|
|
<a href="#env-structure" class="block text-emerald-700 hover:text-emerald-900 py-1">2. SAM의 .env 구조</a>
|
|
<a href="#env-docker" class="block text-emerald-700 hover:text-emerald-900 py-1">3. Docker가 .env를 덮어쓴다</a>
|
|
<a href="#env-sync" class="block text-emerald-700 hover:text-emerald-900 py-1">4. 양쪽이 같아야 하는 변수</a>
|
|
<a href="#env-local-server" class="block text-emerald-700 hover:text-emerald-900 py-1">5. 로컬 vs 서버 환경</a>
|
|
<a href="#env-change" class="block text-emerald-700 hover:text-emerald-900 py-1">6. .env 변경 후 해야 할 일</a>
|
|
<a href="#env-caution" class="block text-emerald-700 hover:text-emerald-900 py-1">7. 주의사항 & 자주 하는 실수</a>
|
|
</nav>
|
|
</details>
|
|
</div>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 1. .env란 무엇인가? --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="env-what" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-emerald-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">1</span>
|
|
.env란 무엇인가?
|
|
</h2>
|
|
|
|
<!-- 1-1. 열쇠 고리 비유 -->
|
|
<div id="env-analogy" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-emerald-400 rounded-full"></span>
|
|
열쇠 고리 비유
|
|
</h3>
|
|
<div class="bg-emerald-50 rounded-lg p-5 border border-emerald-100 mb-4">
|
|
<p class="text-sm text-emerald-900 leading-relaxed">
|
|
<strong>.env 파일</strong>은 <strong>열쇠 고리</strong>와 같다.
|
|
데이터베이스, 메일, AI, 푸시 알림 등 여러 서비스를 여는 열쇠(비밀번호, API 키)를 한 곳에 모아둔 파일이다.
|
|
</p>
|
|
</div>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
|
|
<p class="font-semibold text-amber-800 mb-2">왜 설정을 코드에서 분리하는가?</p>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs">
|
|
<div class="bg-white rounded-lg p-3 border border-amber-200">
|
|
<p class="font-bold text-amber-800 mb-1">보안</p>
|
|
<p class="text-amber-900">API 키, 비밀번호를 코드에 직접 적으면 Git에 올라가서 유출된다. .env는 Git에 올리지 않는다.</p>
|
|
</div>
|
|
<div class="bg-white rounded-lg p-3 border border-amber-200">
|
|
<p class="font-bold text-amber-800 mb-1">환경별 차이</p>
|
|
<p class="text-amber-900">로컬에서는 DB가 Docker 컨테이너, 서버에서는 localhost. 코드는 같은데 설정만 다르다.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 1-2. .env.example vs .env -->
|
|
<div id="env-example" class="scroll-mt-20">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-emerald-400 rounded-full"></span>
|
|
.env.example vs .env
|
|
</h3>
|
|
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200 mb-4">
|
|
<p class="font-semibold text-gray-800 mb-2">비유: 양식과 실제 서류</p>
|
|
<p class="text-xs text-gray-700 leading-relaxed mb-3">
|
|
<code>.env.example</code>은 <strong>빈 양식지</strong>다. 어떤 항목을 채워야 하는지 알려주지만 실제 값은 비어 있다.<br>
|
|
<code>.env</code>는 <strong>작성 완료된 서류</strong>다. 실제 비밀번호와 API 키가 들어 있다.
|
|
</p>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs">
|
|
<div class="bg-white rounded-lg p-3 border border-gray-300">
|
|
<p class="font-bold text-gray-800 mb-2">.env.example (양식)</p>
|
|
<code class="block bg-gray-100 rounded p-2 text-[11px] leading-relaxed">
|
|
GEMINI_API_KEY=<br>
|
|
DB_PASSWORD=sampass<br>
|
|
INTERNAL_EXCHANGE_SECRET=
|
|
</code>
|
|
<p class="mt-2 text-gray-600">Git에 포함 (공유용)</p>
|
|
</div>
|
|
<div class="bg-white rounded-lg p-3 border border-emerald-300">
|
|
<p class="font-bold text-emerald-800 mb-2">.env (실제)</p>
|
|
<code class="block bg-gray-100 rounded p-2 text-[11px] leading-relaxed">
|
|
GEMINI_API_KEY=AIzaSy...<br>
|
|
DB_PASSWORD=Pr0d_S3cur3!<br>
|
|
INTERNAL_EXCHANGE_SECRET=abc123...
|
|
</code>
|
|
<p class="mt-2 text-emerald-700 font-semibold">Git에 절대 포함 금지!</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SVG 2 -->
|
|
<div class="overflow-hidden rounded-xl mb-4">
|
|
<img src="{{ asset('images/academy/env-management/2.svg') }}" alt=".env 역할 개념도"
|
|
class="w-full rounded-xl cursor-pointer academy-img-hover"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 2. SAM 프로젝트의 .env 구조 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="env-structure" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-emerald-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">2</span>
|
|
SAM 프로젝트의 .env 구조
|
|
</h2>
|
|
|
|
<div class="bg-emerald-50 rounded-lg p-5 border border-emerald-100 mb-6">
|
|
<p class="text-sm text-emerald-900 leading-relaxed">
|
|
SAM은 <strong>MNG, API, React</strong> 3개 프로젝트로 구성되며, 각 프로젝트가 <strong>독립된 .env 파일</strong>을 보유한다.
|
|
공유 DB를 사용하지만 환경 변수는 각자 관리한다.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- SVG 3 -->
|
|
<div class="overflow-hidden rounded-xl mb-6">
|
|
<img src="{{ asset('images/academy/env-management/3.svg') }}" alt="프로젝트별 .env 구조"
|
|
class="w-full rounded-xl cursor-pointer academy-img-hover"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
|
|
<!-- MNG 카테고리 -->
|
|
<div id="env-mng" class="scroll-mt-20 mb-6">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-emerald-400 rounded-full"></span>
|
|
MNG .env 카테고리
|
|
</h3>
|
|
<div class="grid grid-cols-2 md:grid-cols-3 gap-2 text-xs">
|
|
<div class="bg-gray-50 rounded-lg p-3 border border-gray-200 text-center">
|
|
<p class="font-bold text-gray-800">APP</p>
|
|
<p class="text-gray-600">이름, 환경, URL</p>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-3 border border-gray-200 text-center">
|
|
<p class="font-bold text-gray-800">Database</p>
|
|
<p class="text-gray-600">DB 접속 정보</p>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-3 border border-gray-200 text-center">
|
|
<p class="font-bold text-gray-800">Session</p>
|
|
<p class="text-gray-600">세션 드라이버, 수명</p>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-3 border border-gray-200 text-center">
|
|
<p class="font-bold text-gray-800">Mail</p>
|
|
<p class="text-gray-600">SMTP 서버 설정</p>
|
|
</div>
|
|
<div class="bg-purple-50 rounded-lg p-3 border border-purple-200 text-center">
|
|
<p class="font-bold text-purple-800">SAM API 연동</p>
|
|
<p class="text-purple-600">API_BASE_URL, 내부통신키</p>
|
|
</div>
|
|
<div class="bg-purple-50 rounded-lg p-3 border border-purple-200 text-center">
|
|
<p class="font-bold text-purple-800">Google AI</p>
|
|
<p class="text-purple-600">Gemini, Vertex, GCS</p>
|
|
</div>
|
|
<div class="bg-purple-50 rounded-lg p-3 border border-purple-200 text-center">
|
|
<p class="font-bold text-purple-800">Claude AI</p>
|
|
<p class="text-purple-600">CLAUDE_API_KEY</p>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-3 border border-gray-200 text-center">
|
|
<p class="font-bold text-gray-800">FCM</p>
|
|
<p class="text-gray-600">Firebase 푸시 알림</p>
|
|
</div>
|
|
<div class="bg-blue-50 rounded-lg p-3 border border-blue-200 text-center">
|
|
<p class="font-bold text-blue-800">Notion</p>
|
|
<p class="text-blue-600">MNG 전용</p>
|
|
</div>
|
|
<div class="bg-blue-50 rounded-lg p-3 border border-blue-200 text-center">
|
|
<p class="font-bold text-blue-800">기상청 API</p>
|
|
<p class="text-blue-600">MNG 전용</p>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-gray-500 mt-2"><span class="inline-block w-2 h-2 bg-purple-400 rounded-full mr-1"></span>보라 = 양쪽 공유 · <span class="inline-block w-2 h-2 bg-blue-400 rounded-full mr-1"></span>파랑 = MNG 전용</p>
|
|
</div>
|
|
|
|
<!-- API 카테고리 -->
|
|
<div id="env-api" class="scroll-mt-20">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-emerald-400 rounded-full"></span>
|
|
API .env 추가 카테고리
|
|
</h3>
|
|
<div class="grid grid-cols-2 md:grid-cols-3 gap-2 text-xs">
|
|
<div class="bg-amber-50 rounded-lg p-3 border border-amber-200 text-center">
|
|
<p class="font-bold text-amber-800">Slack 로깅</p>
|
|
<p class="text-amber-600">API 전용</p>
|
|
</div>
|
|
<div class="bg-amber-50 rounded-lg p-3 border border-amber-200 text-center">
|
|
<p class="font-bold text-amber-800">Swagger</p>
|
|
<p class="text-amber-600">API 문서 설정</p>
|
|
</div>
|
|
<div class="bg-amber-50 rounded-lg p-3 border border-amber-200 text-center">
|
|
<p class="font-bold text-amber-800">Sanctum</p>
|
|
<p class="text-amber-600">토큰 만료 설정</p>
|
|
</div>
|
|
<div class="bg-amber-50 rounded-lg p-3 border border-amber-200 text-center">
|
|
<p class="font-bold text-amber-800">Legacy DB</p>
|
|
<p class="text-amber-600">5130 DB 접속</p>
|
|
</div>
|
|
<div class="bg-amber-50 rounded-lg p-3 border border-amber-200 text-center">
|
|
<p class="font-bold text-amber-800">바로빌</p>
|
|
<p class="text-amber-600">세금계산서 SOAP</p>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-gray-500 mt-2"><span class="inline-block w-2 h-2 bg-amber-400 rounded-full mr-1"></span>주황 = API 전용</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 3. Docker가 .env를 덮어쓴다 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="env-docker" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-emerald-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">3</span>
|
|
Docker가 .env를 덮어쓴다
|
|
</h2>
|
|
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-6">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 현장 상관의 즉각 명령</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
군대에서 <strong>기본 명령서</strong>(.env)가 있지만, <strong>현장 상관</strong>(docker-compose)이 "이건 이렇게 해!"라고 하면 그게 우선이다.
|
|
Docker 환경에서는 <code>docker-compose.yml</code>의 <code>environment</code> 설정이 .env보다 강하다.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- 우선순위 -->
|
|
<div id="env-priority" class="scroll-mt-20 mb-6">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-emerald-400 rounded-full"></span>
|
|
우선순위 (높은 순)
|
|
</h3>
|
|
|
|
<!-- SVG 4 -->
|
|
<div class="overflow-hidden rounded-xl mb-4">
|
|
<img src="{{ asset('images/academy/env-management/4.svg') }}" alt="Override 우선순위"
|
|
class="w-full rounded-xl cursor-pointer academy-img-hover"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
|
|
<div class="space-y-2 text-xs">
|
|
<div class="flex items-center gap-3 bg-emerald-50 rounded-lg p-3 border border-emerald-200">
|
|
<span class="shrink-0 w-6 h-6 bg-emerald-500 text-white rounded-full flex items-center justify-center text-xs font-bold">1</span>
|
|
<div>
|
|
<p class="font-bold text-emerald-800">docker-compose.yml <code>environment:</code></p>
|
|
<p class="text-emerald-700">최우선. 컨테이너 시작 시 직접 주입된다.</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-3 bg-gray-50 rounded-lg p-3 border border-gray-200">
|
|
<span class="shrink-0 w-6 h-6 bg-gray-500 text-white rounded-full flex items-center justify-center text-xs font-bold">2</span>
|
|
<div>
|
|
<p class="font-bold text-gray-800">.env 파일</p>
|
|
<p class="text-gray-600">프로젝트 루트의 환경 변수 파일.</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-3 bg-gray-50 rounded-lg p-3 border border-gray-200">
|
|
<span class="shrink-0 w-6 h-6 bg-gray-400 text-white rounded-full flex items-center justify-center text-xs font-bold">3</span>
|
|
<div>
|
|
<p class="font-bold text-gray-700">.env.example</p>
|
|
<p class="text-gray-500">기본값 참고용. 실제 적용되지 않는다.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Override 실제 예시 -->
|
|
<div id="env-override-example" class="scroll-mt-20">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-emerald-400 rounded-full"></span>
|
|
실제 Override 예시
|
|
</h3>
|
|
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200 mb-4">
|
|
<p class="font-semibold text-gray-800 mb-3">API 프로젝트의 DB_HOST</p>
|
|
<div class="space-y-2 text-xs">
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-gray-400 line-through">api/.env: <code>DB_HOST=127.0.0.1</code></span>
|
|
<span class="text-red-500 text-[10px]">무시됨</span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-emerald-700 font-bold">docker-compose: <code>DB_HOST=sam-mysql-1</code></span>
|
|
<span class="bg-emerald-100 text-emerald-800 px-2 py-0.5 rounded text-[10px] font-bold">적용됨</span>
|
|
</div>
|
|
</div>
|
|
<div class="mt-3 bg-amber-50 rounded p-3 border border-amber-200">
|
|
<p class="text-xs text-amber-800">
|
|
<strong>Docker에서 Override하는 5개 변수:</strong>
|
|
<code>DB_HOST</code>, <code>DB_PORT</code>, <code>DB_DATABASE</code>, <code>DB_USERNAME</code>, <code>DB_PASSWORD</code>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
|
|
<p class="font-semibold text-gray-800 mb-3">React 프로젝트</p>
|
|
<div class="text-xs space-y-1">
|
|
<p class="text-gray-700"><code>NEXT_PUBLIC_API_URL=https://api.sam.kr</code></p>
|
|
<p class="text-gray-700"><code>NEXT_PUBLIC_API_KEY=42Jfwc6E...</code></p>
|
|
<p class="text-gray-700"><code>NODE_ENV=development</code></p>
|
|
</div>
|
|
<p class="text-xs text-gray-500 mt-2">React는 .env 파일 없이 docker-compose에서만 설정한다.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 4. 양쪽이 같아야 하는 변수 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="env-sync" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-emerald-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">4</span>
|
|
양쪽이 같아야 하는 변수
|
|
</h2>
|
|
|
|
<div class="bg-purple-50 rounded-lg p-4 border border-purple-200 mb-6">
|
|
<p class="font-semibold text-purple-800 mb-2">비유: 한 쌍의 자물쇠</p>
|
|
<p class="text-xs text-purple-900 leading-relaxed">
|
|
MNG와 API는 서로 HTTP로 통신한다. <strong>INTERNAL_EXCHANGE_SECRET</strong>이 양쪽에서 다르면
|
|
HMAC 인증이 실패하여 통신이 불가능하다. 자물쇠와 열쇠가 맞지 않는 것과 같다.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- SVG 5 -->
|
|
<div class="overflow-hidden rounded-xl mb-6">
|
|
<img src="{{ asset('images/academy/env-management/5.svg') }}" alt="동기화 필수 변수 맵"
|
|
class="w-full rounded-xl cursor-pointer academy-img-hover"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
|
|
<!-- 공유 API 키 -->
|
|
<div id="env-sync-shared" class="scroll-mt-20 mb-6">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-emerald-400 rounded-full"></span>
|
|
공유 API 키 (양쪽 동일 값 필수)
|
|
</h3>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-xs border border-gray-200 rounded-lg overflow-hidden">
|
|
<thead class="bg-emerald-50">
|
|
<tr>
|
|
<th class="text-left p-2 font-semibold text-emerald-800 border-b">환경 변수</th>
|
|
<th class="text-center p-2 font-semibold text-emerald-800 border-b">MNG</th>
|
|
<th class="text-center p-2 font-semibold text-emerald-800 border-b">API</th>
|
|
<th class="text-left p-2 font-semibold text-emerald-800 border-b">설명</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="bg-red-50">
|
|
<td class="p-2 border-b font-mono font-bold text-red-800">INTERNAL_EXCHANGE_SECRET</td>
|
|
<td class="p-2 border-b text-center text-red-600 font-bold">필수</td>
|
|
<td class="p-2 border-b text-center text-red-600 font-bold">필수</td>
|
|
<td class="p-2 border-b text-red-700">서버 간 HMAC 검증 키</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="p-2 border-b font-mono">GEMINI_API_KEY</td>
|
|
<td class="p-2 border-b text-center text-emerald-600">✓</td>
|
|
<td class="p-2 border-b text-center text-emerald-600">✓</td>
|
|
<td class="p-2 border-b text-gray-600">Gemini AI API 키</td>
|
|
</tr>
|
|
<tr class="bg-gray-50">
|
|
<td class="p-2 border-b font-mono">GEMINI_MODEL</td>
|
|
<td class="p-2 border-b text-center text-emerald-600">✓</td>
|
|
<td class="p-2 border-b text-center text-emerald-600">✓</td>
|
|
<td class="p-2 border-b text-gray-600">gemini-2.5-flash</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="p-2 border-b font-mono">VERTEX_AI_PROJECT_ID</td>
|
|
<td class="p-2 border-b text-center text-emerald-600">✓</td>
|
|
<td class="p-2 border-b text-center text-emerald-600">✓</td>
|
|
<td class="p-2 border-b text-gray-600">Vertex AI 프로젝트</td>
|
|
</tr>
|
|
<tr class="bg-gray-50">
|
|
<td class="p-2 border-b font-mono">VERTEX_AI_LOCATION</td>
|
|
<td class="p-2 border-b text-center text-emerald-600">✓</td>
|
|
<td class="p-2 border-b text-center text-emerald-600">✓</td>
|
|
<td class="p-2 border-b text-gray-600">us-central1</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="p-2 border-b font-mono">GOOGLE_STORAGE_BUCKET</td>
|
|
<td class="p-2 border-b text-center text-emerald-600">✓</td>
|
|
<td class="p-2 border-b text-center text-emerald-600">✓</td>
|
|
<td class="p-2 border-b text-gray-600">GCS 버킷 이름</td>
|
|
</tr>
|
|
<tr class="bg-gray-50">
|
|
<td class="p-2 font-mono">DB_HOST / DB_DATABASE / DB_PASSWORD</td>
|
|
<td class="p-2 text-center text-emerald-600">✓</td>
|
|
<td class="p-2 text-center text-emerald-600">✓</td>
|
|
<td class="p-2 text-gray-600">공유 DB 접속 정보</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 경로가 다른 변수 -->
|
|
<div id="env-sync-path" class="scroll-mt-20">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-emerald-400 rounded-full"></span>
|
|
경로가 다른 변수 (같은 파일, 다른 경로)
|
|
</h3>
|
|
|
|
<!-- SVG 6 -->
|
|
<div class="overflow-hidden rounded-xl mb-4">
|
|
<img src="{{ asset('images/academy/env-management/6.svg') }}" alt="경로가 다른 변수"
|
|
class="w-full rounded-xl cursor-pointer academy-img-hover"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-xs border border-gray-200 rounded-lg overflow-hidden">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="text-left p-2 font-semibold text-gray-800 border-b">환경 변수</th>
|
|
<th class="text-left p-2 font-semibold text-gray-800 border-b">MNG 경로</th>
|
|
<th class="text-left p-2 font-semibold text-gray-800 border-b">API 경로</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td class="p-2 border-b font-mono font-semibold">GOOGLE_APPLICATION_CREDENTIALS</td>
|
|
<td class="p-2 border-b font-mono text-[10px]">/var/www/sales/apikey/google_sa.json</td>
|
|
<td class="p-2 border-b font-mono text-[10px]">/var/www/mng/apikey/google_sa.json</td>
|
|
</tr>
|
|
<tr class="bg-gray-50">
|
|
<td class="p-2 font-mono font-semibold">FCM_SA_PATH</td>
|
|
<td class="p-2 font-mono text-[10px]">secrets/firebase-service-account.json</td>
|
|
<td class="p-2 font-mono text-[10px]">secrets/codebridge-x-firebase-sa.json</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="bg-amber-50 rounded-lg p-3 border border-amber-100 mt-3">
|
|
<p class="text-xs text-amber-800">
|
|
동일한 Google 서비스 계정 파일이지만 컨테이너마다 마운트 경로가 다르다.
|
|
값을 복사하면 안 되고 각 프로젝트의 실제 경로를 사용해야 한다.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 5. 로컬 vs 서버 환경 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="env-local-server" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-emerald-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">5</span>
|
|
로컬 vs 서버 환경
|
|
</h2>
|
|
|
|
<!-- SVG 7 -->
|
|
<div class="overflow-hidden rounded-xl mb-6">
|
|
<img src="{{ asset('images/academy/env-management/7.svg') }}" alt="로컬 vs 서버 비교"
|
|
class="w-full rounded-xl cursor-pointer academy-img-hover"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
|
|
<div class="overflow-x-auto mb-6">
|
|
<table class="w-full text-xs border border-gray-200 rounded-lg overflow-hidden">
|
|
<thead class="bg-emerald-50">
|
|
<tr>
|
|
<th class="text-left p-2 font-semibold text-emerald-800 border-b">환경 변수</th>
|
|
<th class="text-left p-2 font-semibold text-emerald-800 border-b">로컬 (Docker)</th>
|
|
<th class="text-left p-2 font-semibold text-emerald-800 border-b">서버 (운영)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td class="p-2 border-b font-mono font-semibold">APP_ENV</td>
|
|
<td class="p-2 border-b"><code>local</code></td>
|
|
<td class="p-2 border-b"><code>production</code></td>
|
|
</tr>
|
|
<tr class="bg-gray-50">
|
|
<td class="p-2 border-b font-mono font-semibold">APP_DEBUG</td>
|
|
<td class="p-2 border-b"><code class="text-emerald-700">true</code></td>
|
|
<td class="p-2 border-b"><code class="text-red-700 font-bold">false</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="p-2 border-b font-mono font-semibold">DB_HOST</td>
|
|
<td class="p-2 border-b"><code>sam-mysql-1</code> (컨테이너명)</td>
|
|
<td class="p-2 border-b"><code>127.0.0.1</code> (localhost)</td>
|
|
</tr>
|
|
<tr class="bg-gray-50">
|
|
<td class="p-2 font-mono font-semibold">DB_PASSWORD</td>
|
|
<td class="p-2"><code>sampass</code> (개발용)</td>
|
|
<td class="p-2"><code>●●●●●●●</code> (강력한 비밀번호)</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- APP_DEBUG 위험 -->
|
|
<div id="env-debug-danger" class="scroll-mt-20">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-emerald-400 rounded-full"></span>
|
|
APP_DEBUG=true의 운영 환경 위험성
|
|
</h3>
|
|
<div class="bg-red-50 rounded-lg p-4 border border-red-200">
|
|
<p class="font-bold text-red-800 mb-2">운영 서버에서 APP_DEBUG=true이면?</p>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 text-xs">
|
|
<div class="bg-white rounded-lg p-3 border border-red-200 text-center">
|
|
<p class="font-bold text-red-800 mb-1">DB 비밀번호 노출</p>
|
|
<p class="text-red-700">에러 발생 시 .env 값이<br>브라우저에 표시된다</p>
|
|
</div>
|
|
<div class="bg-white rounded-lg p-3 border border-red-200 text-center">
|
|
<p class="font-bold text-red-800 mb-1">API 키 유출</p>
|
|
<p class="text-red-700">스택 트레이스에 환경 변수<br>전체가 포함될 수 있다</p>
|
|
</div>
|
|
<div class="bg-white rounded-lg p-3 border border-red-200 text-center">
|
|
<p class="font-bold text-red-800 mb-1">서버 경로 노출</p>
|
|
<p class="text-red-700">파일 구조, 프레임워크 버전 등<br>공격에 필요한 정보가 드러난다</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 6. .env 변경 후 해야 할 일 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="env-change" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-emerald-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">6</span>
|
|
.env 변경 후 해야 할 일
|
|
</h2>
|
|
|
|
<!-- 캐시 메커니즘 설명 -->
|
|
<div id="env-cache" class="scroll-mt-20 mb-6">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-emerald-400 rounded-full"></span>
|
|
왜 config:clear가 필요한가
|
|
</h3>
|
|
<div class="bg-emerald-50 rounded-lg p-5 border border-emerald-100 mb-4">
|
|
<p class="text-sm text-emerald-900 leading-relaxed">
|
|
Laravel은 성능을 위해 <code>.env</code> 값을 <strong>캐시</strong>에 저장해둔다.
|
|
.env를 수정해도 <code>php artisan config:clear</code>를 실행하지 않으면 <strong>이전 값이 계속 사용</strong>된다.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- SVG 8 -->
|
|
<div class="overflow-hidden rounded-xl mb-4">
|
|
<img src="{{ asset('images/academy/env-management/8.svg') }}" alt=".env 변경 → 캐시 클리어 흐름"
|
|
class="w-full rounded-xl cursor-pointer academy-img-hover"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-xs">
|
|
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
|
|
<p class="font-bold text-gray-800 mb-2">로컬 (Docker) 환경</p>
|
|
<div class="space-y-2">
|
|
<div class="bg-white rounded p-2 border border-gray-300">
|
|
<code class="text-[11px]">docker exec sam-mng-1 php artisan config:clear</code>
|
|
</div>
|
|
<div class="bg-white rounded p-2 border border-gray-300">
|
|
<code class="text-[11px]">docker exec sam-api-1 php artisan config:clear</code>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
|
|
<p class="font-bold text-gray-800 mb-2">서버 환경</p>
|
|
<div class="space-y-2">
|
|
<div class="bg-white rounded p-2 border border-gray-300">
|
|
<code class="text-[11px]">cd /home/webservice/mng && php artisan config:clear</code>
|
|
</div>
|
|
<div class="bg-white rounded p-2 border border-gray-300">
|
|
<code class="text-[11px]">cd /home/webservice/api && php artisan config:clear</code>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 온보딩 체크리스트 -->
|
|
<div id="env-onboarding" class="scroll-mt-20">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-emerald-400 rounded-full"></span>
|
|
신규 개발자 온보딩 체크리스트
|
|
</h3>
|
|
|
|
<!-- SVG 9 -->
|
|
<div class="overflow-hidden rounded-xl mb-4">
|
|
<img src="{{ asset('images/academy/env-management/9.svg') }}" alt="온보딩 체크리스트"
|
|
class="w-full rounded-xl cursor-pointer academy-img-hover"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
|
|
<div class="space-y-2 text-xs">
|
|
<div class="flex items-start gap-3 bg-gray-50 rounded-lg p-3 border border-gray-200">
|
|
<span class="shrink-0 w-6 h-6 bg-emerald-500 text-white rounded-full flex items-center justify-center text-xs font-bold">1</span>
|
|
<div>
|
|
<p class="font-bold text-gray-800">.env.example을 .env로 복사</p>
|
|
<code class="text-[11px] text-gray-600">cp .env.example .env</code>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-start gap-3 bg-gray-50 rounded-lg p-3 border border-gray-200">
|
|
<span class="shrink-0 w-6 h-6 bg-emerald-500 text-white rounded-full flex items-center justify-center text-xs font-bold">2</span>
|
|
<div>
|
|
<p class="font-bold text-gray-800">APP_KEY 생성</p>
|
|
<code class="text-[11px] text-gray-600">docker exec sam-mng-1 php artisan key:generate</code>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-start gap-3 bg-gray-50 rounded-lg p-3 border border-gray-200">
|
|
<span class="shrink-0 w-6 h-6 bg-emerald-500 text-white rounded-full flex items-center justify-center text-xs font-bold">3</span>
|
|
<div>
|
|
<p class="font-bold text-gray-800">DB 접속 정보 확인</p>
|
|
<p class="text-gray-600">Docker가 Override하므로 .env 기본값으로 충분하다</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-start gap-3 bg-gray-50 rounded-lg p-3 border border-gray-200">
|
|
<span class="shrink-0 w-6 h-6 bg-emerald-500 text-white rounded-full flex items-center justify-center text-xs font-bold">4</span>
|
|
<div>
|
|
<p class="font-bold text-gray-800">공유 API 키 동기화</p>
|
|
<p class="text-gray-600">GEMINI_API_KEY, INTERNAL_EXCHANGE_SECRET 등을 팀장에게 받아 설정</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-start gap-3 bg-gray-50 rounded-lg p-3 border border-gray-200">
|
|
<span class="shrink-0 w-6 h-6 bg-emerald-500 text-white rounded-full flex items-center justify-center text-xs font-bold">5</span>
|
|
<div>
|
|
<p class="font-bold text-gray-800">캐시 초기화</p>
|
|
<code class="text-[11px] text-gray-600">docker exec sam-mng-1 php artisan config:clear</code>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 7. 주의사항 & 자주 하는 실수 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="env-caution" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-emerald-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">7</span>
|
|
주의사항 & 자주 하는 실수
|
|
</h2>
|
|
|
|
<!-- 금지 카드 6종 -->
|
|
<div id="env-forbidden" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-emerald-400 rounded-full"></span>
|
|
절대 하지 마라 (6가지)
|
|
</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs">
|
|
<div class="bg-red-50 rounded-lg p-3 border border-red-200">
|
|
<p class="font-bold text-red-800 mb-1">.env를 Git에 커밋</p>
|
|
<p class="text-red-700">비밀번호, API 키가 저장소에 영구 저장된다. .gitignore에 .env가 포함되어 있는지 확인한다.</p>
|
|
</div>
|
|
<div class="bg-red-50 rounded-lg p-3 border border-red-200">
|
|
<p class="font-bold text-red-800 mb-1">운영 서버에서 APP_DEBUG=true</p>
|
|
<p class="text-red-700">에러 발생 시 모든 환경 변수가 브라우저에 노출된다.</p>
|
|
</div>
|
|
<div class="bg-red-50 rounded-lg p-3 border border-red-200">
|
|
<p class="font-bold text-red-800 mb-1">EXCHANGE_SECRET 불일치</p>
|
|
<p class="text-red-700">MNG와 API의 INTERNAL_EXCHANGE_SECRET이 다르면 서버 간 통신이 전부 실패한다.</p>
|
|
</div>
|
|
<div class="bg-red-50 rounded-lg p-3 border border-red-200">
|
|
<p class="font-bold text-red-800 mb-1">운영 서버에서 APP_KEY 재생성</p>
|
|
<p class="text-red-700">기존 세션, 암호화된 데이터가 모두 복호화 불가능해진다.</p>
|
|
</div>
|
|
<div class="bg-red-50 rounded-lg p-3 border border-red-200">
|
|
<p class="font-bold text-red-800 mb-1">config:clear 안 하고 "안 되요"</p>
|
|
<p class="text-red-700">.env 수정 후 캐시를 안 지우면 이전 설정이 계속 적용된다.</p>
|
|
</div>
|
|
<div class="bg-red-50 rounded-lg p-3 border border-red-200">
|
|
<p class="font-bold text-red-800 mb-1">운영 키를 로컬에 복사</p>
|
|
<p class="text-red-700">로컬에서 운영 DB에 접속하면 실수로 데이터를 변경할 수 있다.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 증상별 해결책 -->
|
|
<div id="env-troubleshoot" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-emerald-400 rounded-full"></span>
|
|
증상별 해결책
|
|
</h3>
|
|
<div class="space-y-2 text-xs">
|
|
<div class="bg-gray-50 rounded-lg p-3 border border-gray-200">
|
|
<div class="flex items-start gap-3">
|
|
<span class="shrink-0 bg-red-100 text-red-800 px-2 py-0.5 rounded font-bold text-[10px]">증상</span>
|
|
<div>
|
|
<p class="font-bold text-gray-800">MNG → API 연동 실패 (401/403)</p>
|
|
<p class="text-gray-600 mt-1">
|
|
<span class="bg-emerald-100 text-emerald-800 px-1.5 py-0.5 rounded text-[10px] font-bold">해결</span>
|
|
양쪽 <code>INTERNAL_EXCHANGE_SECRET</code> 값 일치 확인 → <code>config:clear</code>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-3 border border-gray-200">
|
|
<div class="flex items-start gap-3">
|
|
<span class="shrink-0 bg-red-100 text-red-800 px-2 py-0.5 rounded font-bold text-[10px]">증상</span>
|
|
<div>
|
|
<p class="font-bold text-gray-800">AI 기능 (Gemini/Claude) 동작 안 함</p>
|
|
<p class="text-gray-600 mt-1">
|
|
<span class="bg-emerald-100 text-emerald-800 px-1.5 py-0.5 rounded text-[10px] font-bold">해결</span>
|
|
해당 프로젝트 .env의 <code>GEMINI_API_KEY</code> 또는 <code>CLAUDE_API_KEY</code> 값 확인 → <code>config:clear</code>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-3 border border-gray-200">
|
|
<div class="flex items-start gap-3">
|
|
<span class="shrink-0 bg-red-100 text-red-800 px-2 py-0.5 rounded font-bold text-[10px]">증상</span>
|
|
<div>
|
|
<p class="font-bold text-gray-800">DB 접속 오류 (Connection refused)</p>
|
|
<p class="text-gray-600 mt-1">
|
|
<span class="bg-emerald-100 text-emerald-800 px-1.5 py-0.5 rounded text-[10px] font-bold">해결</span>
|
|
Docker: <code>DB_HOST=sam-mysql-1</code> / 서버: <code>DB_HOST=127.0.0.1</code> 확인. Docker에서는 compose가 override하므로 .env 값은 무관.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-3 border border-gray-200">
|
|
<div class="flex items-start gap-3">
|
|
<span class="shrink-0 bg-red-100 text-red-800 px-2 py-0.5 rounded font-bold text-[10px]">증상</span>
|
|
<div>
|
|
<p class="font-bold text-gray-800">Google 서비스 계정 파일 오류</p>
|
|
<p class="text-gray-600 mt-1">
|
|
<span class="bg-emerald-100 text-emerald-800 px-1.5 py-0.5 rounded text-[10px] font-bold">해결</span>
|
|
<code>GOOGLE_APPLICATION_CREDENTIALS</code> 경로가 해당 컨테이너의 마운트 경로와 일치하는지 확인.
|
|
MNG와 API의 경로가 다르다.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-3 border border-gray-200">
|
|
<div class="flex items-start gap-3">
|
|
<span class="shrink-0 bg-red-100 text-red-800 px-2 py-0.5 rounded font-bold text-[10px]">증상</span>
|
|
<div>
|
|
<p class="font-bold text-gray-800">.env 수정했는데 설정이 안 바뀜</p>
|
|
<p class="text-gray-600 mt-1">
|
|
<span class="bg-emerald-100 text-emerald-800 px-1.5 py-0.5 rounded text-[10px] font-bold">해결</span>
|
|
<code>php artisan config:clear</code> 실행. Docker의 override 변수인지도 확인 (compose가 .env보다 우선).
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 신규 변수 추가 절차 -->
|
|
<div class="bg-emerald-50 rounded-lg p-4 border border-emerald-200">
|
|
<p class="font-semibold text-emerald-800 mb-2">신규 환경 변수 추가 시 절차</p>
|
|
<ol class="text-xs text-emerald-900 space-y-1 list-decimal list-inside">
|
|
<li><code>.env.example</code>에 키=기본값 추가 (다른 개발자를 위한 문서 역할)</li>
|
|
<li>자신의 <code>.env</code>에 실제 값 설정</li>
|
|
<li><code>config/services.php</code> 등에서 <code>env('NEW_KEY')</code>로 참조</li>
|
|
<li>팀원에게 새 변수 추가 사실 공유</li>
|
|
<li>양쪽(MNG/API) 필요 시 동기화 필수 항목인지 확인</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 용어 사전 --}}
|
|
{{-- ============================================================ --}}
|
|
@include('components.academy-glossary', ['domain' => 'env-management'])
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- hover 프리뷰 오버레이 -->
|
|
<div id="hover-preview">
|
|
<img id="hover-preview-img" src="" alt="">
|
|
<span class="hover-caption" id="hover-preview-caption"></span>
|
|
</div>
|
|
|
|
<!-- 클릭 라이트박스 -->
|
|
<div id="lightbox" onclick="closeLightbox()">
|
|
<button onclick="closeLightbox()" style="position:absolute; top:1rem; right:1rem; background:none; border:none; cursor:pointer; color:rgba(255,255,255,0.8); padding:0.5rem;">
|
|
<svg style="width:2rem; height:2rem;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
|
|
</button>
|
|
<img id="lightbox-img" onclick="event.stopPropagation()">
|
|
</div>
|
|
<script>
|
|
(function() {
|
|
var preview = document.getElementById('hover-preview');
|
|
var previewImg = document.getElementById('hover-preview-img');
|
|
var previewCaption = document.getElementById('hover-preview-caption');
|
|
var hoverTimer = null;
|
|
var isPreviewActive = false;
|
|
var HOVER_DELAY = 350;
|
|
|
|
document.querySelectorAll('.academy-img-hover').forEach(function(img) {
|
|
img.addEventListener('mouseenter', function() {
|
|
var el = this;
|
|
hoverTimer = setTimeout(function() {
|
|
showPreview(el);
|
|
}, HOVER_DELAY);
|
|
});
|
|
|
|
img.addEventListener('mouseleave', function() {
|
|
clearTimeout(hoverTimer);
|
|
if (isPreviewActive) {
|
|
hidePreview();
|
|
}
|
|
});
|
|
});
|
|
|
|
function showPreview(el) {
|
|
previewImg.src = el.src;
|
|
previewImg.alt = el.alt || '';
|
|
var caption = el.alt || '';
|
|
var nextP = el.parentElement && el.parentElement.querySelector('p');
|
|
if (nextP) caption = nextP.textContent;
|
|
previewCaption.textContent = caption;
|
|
|
|
preview.classList.add('is-active');
|
|
isPreviewActive = true;
|
|
}
|
|
|
|
function hidePreview() {
|
|
preview.classList.remove('is-active');
|
|
isPreviewActive = false;
|
|
}
|
|
|
|
window.openLightbox = function(el) {
|
|
var lb = document.getElementById('lightbox');
|
|
var img = document.getElementById('lightbox-img');
|
|
img.src = el.src;
|
|
img.alt = el.alt;
|
|
lb.classList.add('is-open');
|
|
document.body.style.overflow = 'hidden';
|
|
hidePreview();
|
|
};
|
|
|
|
window.closeLightbox = function() {
|
|
var lb = document.getElementById('lightbox');
|
|
lb.classList.remove('is-open');
|
|
document.body.style.overflow = '';
|
|
};
|
|
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
if (isPreviewActive) hidePreview();
|
|
closeLightbox();
|
|
}
|
|
});
|
|
})();
|
|
</script>
|
|
@endsection
|