Files
sam-manage/resources/views/academy/docker-environment.blade.php
김보곤 7a7904efc1 fix: [academy] Docker 환경이해 페이지에 로컬/서버 환경 차이 반영
- 서버는 Docker 없이 네이티브로 운영됨을 명시하는 핵심 안내 박스 추가
- 섹션 2 제목을 "SAM 로컬 Docker 아키텍처"로 변경
- 섹션 5-3에 서버 네이티브 구조도 추가
- 섹션 6에 로컬(Docker) vs 서버(네이티브) 명령어 비교표 추가
- 목차(TOC) 업데이트
2026-02-23 14:10:27 +09:00

1246 lines
84 KiB
PHP

@extends('layouts.app')
@section('title', 'Docker 환경이해')
@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, #1a2a3a 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: #64b5f6;">
<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;">Docker 환경</span>
</div>
<h1 class="text-3xl font-bold mb-2" style="color: #ffffff;">Docker 환경이해</h1>
<p class="text-sm" style="color: #cbd5e1;">SAM 프로젝트의 로컬 Docker 환경과 서버 네이티브 환경 비개발자도 이해할 있는 가이드</p>
</div>
<div class="shrink-0" style="width: 240px; padding: 1.5rem;">
<div class="overflow-hidden rounded-xl">
<img src="{{ asset('images/academy/docker-environment/1.svg') }}" alt="Docker 컨테이너 비유 일러스트"
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-blue-50 border border-blue-200 rounded-xl p-5">
<h2 class="font-semibold text-blue-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="#docker-why" class="block text-blue-700 hover:text-blue-900 py-1 font-medium">1. Docker란?</a>
<a href="#docker-analogy" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">컨테이너 비유</a>
<a href="#docker-vs-vm" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">컨테이너 vs VM</a>
<a href="#architecture" class="block text-blue-700 hover:text-blue-900 py-1 font-medium mt-2">2. SAM 로컬 Docker 아키텍처</a>
<a href="#arch-services" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">서비스 구성</a>
<a href="#arch-flow" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">요청 흐름</a>
<a href="#services" class="block text-blue-700 hover:text-blue-900 py-1 font-medium mt-2">3. 서비스별 상세</a>
<a href="#svc-mng-api" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">MNG / API</a>
<a href="#svc-react-mysql" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">React / MySQL</a>
<a href="#svc-nginx" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">Nginx 라우팅</a>
<a href="#volumes" class="block text-blue-700 hover:text-blue-900 py-1 font-medium mt-2">4. 볼륨과 데이터</a>
<a href="#vol-bind" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">바인드 마운트</a>
<a href="#vol-docker" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">Docker 볼륨</a>
<a href="#env" class="block text-blue-700 hover:text-blue-900 py-1 font-medium mt-2">5. 환경 변수 (.env)</a>
<a href="#env-role" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">.env의 역할</a>
<a href="#env-priority" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">우선순위</a>
<a href="#env-local-server" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">로컬 vs 서버</a>
<a href="#commands" class="block text-blue-700 hover:text-blue-900 py-1 font-medium mt-2">6. 실전 명령어</a>
<a href="#cmd-compare" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">로컬 vs 서버 비교</a>
<a href="#cmd-basic" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">자주 쓰는 명령어</a>
<a href="#cmd-caution" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">주의사항</a>
<a href="#cmd-trouble" class="block text-blue-600 hover:text-blue-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-blue-50 border border-blue-200 rounded-xl p-4 mb-6">
<details>
<summary class="font-semibold text-blue-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="#docker-why" class="block text-blue-700 hover:text-blue-900 py-1">1. Docker란? Docker를 쓰는가</a>
<a href="#architecture" class="block text-blue-700 hover:text-blue-900 py-1">2. SAM 로컬 Docker 아키텍처</a>
<a href="#services" class="block text-blue-700 hover:text-blue-900 py-1">3. 서비스별 상세</a>
<a href="#volumes" class="block text-blue-700 hover:text-blue-900 py-1">4. 볼륨과 데이터 관리</a>
<a href="#env" class="block text-blue-700 hover:text-blue-900 py-1">5. 환경 변수 (.env) 관리</a>
<a href="#commands" class="block text-blue-700 hover:text-blue-900 py-1">6. 실전 명령어 & 트러블슈팅</a>
</nav>
</details>
</div>
{{-- ============================================================ --}}
{{-- 핵심 안내: 로컬 vs 서버 환경 차이 --}}
{{-- ============================================================ --}}
<div class="bg-indigo-50 border-2 border-indigo-300 rounded-xl p-5 mb-2">
<div class="flex items-start gap-3">
<span class="w-8 h-8 bg-indigo-500 text-white rounded-lg flex items-center justify-center text-sm font-bold shrink-0">!</span>
<div>
<h3 class="font-bold text-indigo-900 mb-2">핵심: SAM의 로컬과 서버 환경은 다르다</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs">
<div class="bg-blue-100 rounded-lg p-3 border border-blue-300">
<p class="font-bold text-blue-900 mb-1">로컬 개발 환경 ( PC / WSL)</p>
<p class="text-blue-800"><strong>Docker 사용</strong> PHP, MySQL, Nginx가 모두 컨테이너 안에서 실행된다.</p>
<p class="text-blue-700 mt-1"><code class="bg-blue-200 px-1 rounded">docker exec sam-api-1 php artisan migrate</code></p>
</div>
<div class="bg-green-100 rounded-lg p-3 border border-green-300">
<p class="font-bold text-green-900 mb-1">운영 서버 (114.203.209.83)</p>
<p class="text-green-800"><strong>Docker 없음 (네이티브)</strong> PHP, MySQL, Nginx가 서버에 직접 설치되어 있다.</p>
<p class="text-green-700 mt-1"><code class="bg-green-200 px-1 rounded">cd /home/webservice/api && php artisan migrate</code></p>
</div>
</div>
<p class="text-xs text-indigo-700 mt-2">
<strong>서버에서 Docker를 사용하지 않는 이유:</strong> 서버 스펙(2코어, 3.8GB RAM)에서 Docker는 무거워서 성능 저하가 발생한다.
따라서 서버에는 PHP, Nginx, MySQL을 직접 설치하여 운영한다.
</p>
</div>
</div>
</div>
{{-- ============================================================ --}}
{{-- 1. Docker란? Docker를 쓰는가 --}}
{{-- ============================================================ --}}
<section id="docker-why" 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-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">1</span>
Docker란? Docker를 쓰는가
</h2>
<!-- 1-1. 컨테이너 비유 -->
<div id="docker-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-blue-400 rounded-full"></span>
컨테이너 비유
</h3>
<div class="bg-blue-50 rounded-lg p-5 border border-blue-100 mb-4">
<p class="text-sm text-blue-900 leading-relaxed">
<strong>Docker</strong> 애플리케이션을 <strong>컨테이너</strong>라는 격리된 환경에 넣어 실행하는 도구다.
어떤 컴퓨터에서든 동일한 환경으로 프로그램을 돌릴 있게 해준다.
</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>
<p class="text-xs text-amber-900 leading-relaxed">
이사할 가구를 <strong>컨테이너에 통째로 넣어서 옮기면</strong>, 어디로 가든 꺼내서 바로 사용할 있다.
Docker도 마찬가지다. 프로그램과 실행에 필요한 모든 (PHP, MySQL, 설정 파일 ) 컨테이너에 넣어두면,
<strong> PC든 서버든 똑같이 동작한다.</strong>
</p>
</div>
<div class="text-sm text-gray-700 space-y-3">
<p class="font-semibold text-gray-800">Docker 없이 개발할 때의 문제점:</p>
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 text-xs">
<div class="bg-red-50 rounded-lg p-3 border border-red-200 text-center">
<p class="font-bold text-red-800 mb-1">"내 PC에선 되는데?"</p>
<p class="text-red-700">PHP 버전이 달라서<br>서버에서 오류 발생</p>
</div>
<div class="bg-red-50 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">A 프로젝트는 PHP 7.3<br>B 프로젝트는 PHP 8.4 필요</p>
</div>
<div class="bg-red-50 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">PHP, MySQL, Nginx...<br>하나하나 설치하다 하루 종일</p>
</div>
</div>
<div class="bg-green-50 rounded-lg p-4 border border-green-200 mt-3">
<p class="font-semibold text-green-800 mb-1">Docker를 쓰면:</p>
<p class="text-xs text-green-700"><code class="bg-green-100 px-1 rounded">docker compose up</code> 줄이면 모든 서비스가 동일한 환경으로 시작된다.</p>
</div>
</div>
</div>
<!-- 1-2. 컨테이너 vs VM -->
<div id="docker-vs-vm" 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-blue-400 rounded-full"></span>
컨테이너 vs 가상머신(VM)
</h3>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/docker-environment/2.svg') }}" alt="컨테이너 vs VM 비교"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 280px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">컨테이너 vs 가상머신 구조 비교</p>
<div class="overflow-x-auto">
<table class="w-full text-sm border-collapse">
<thead>
<tr class="border-b-2 border-gray-200 bg-gray-50">
<th class="text-left py-2 px-3 font-medium text-gray-600">비교 항목</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">가상머신 (VM)</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">Docker 컨테이너</th>
</tr>
</thead>
<tbody class="text-xs text-gray-700">
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">비유</td>
<td class="py-2 px-3">건물 통째로 빌리기</td>
<td class="py-2 px-3">칸막이 사무실 임대</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">OS</td>
<td class="py-2 px-3">각각 별도 OS 설치</td>
<td class="py-2 px-3">호스트 OS 커널 공유</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">크기</td>
<td class="py-2 px-3"> GB</td>
<td class="py-2 px-3">수십~수백 MB</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">시작 시간</td>
<td class="py-2 px-3"> </td>
<td class="py-2 px-3"> </td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">자원 사용</td>
<td class="py-2 px-3">무거움 (RAM 많이 차지)</td>
<td class="py-2 px-3">가벼움 (오버헤드 적음)</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 2. SAM Docker 아키텍처 --}}
{{-- ============================================================ --}}
<section id="architecture" 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-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">2</span>
SAM 로컬 Docker 아키텍처 개발 환경 구조
</h2>
<!-- 2-1. 서비스 구성 -->
<div id="arch-services" 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-blue-400 rounded-full"></span>
9 서비스 구성
</h3>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/docker-environment/3.svg') }}" alt="SAM 전체 아키텍처 다이어그램"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 320px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">SAM Docker 전체 아키텍처 samnet 브리지 네트워크</p>
<div class="bg-blue-50 rounded-lg p-4 border border-blue-100 mb-4">
<p class="text-sm text-blue-900 leading-relaxed">
<strong>로컬 개발 환경</strong>에서 SAM은 <strong>9 서비스</strong> 하나의 <strong>samnet 브리지 네트워크</strong>에서 통신한다.
<code class="bg-blue-100 px-1 rounded">docker-compose.yml</code> 파일로 전체 구성을 관리한다.
</p>
<p class="text-xs text-blue-700 mt-1">
(운영 서버에서는 Docker를 사용하지 않고, PHP/Nginx/MySQL이 직접 설치되어 있다.)
</p>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm border-collapse">
<thead>
<tr class="border-b-2 border-gray-200 bg-gray-50">
<th class="text-left py-2 px-3 font-medium text-gray-600">서비스</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">컨테이너명</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">기술</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">역할</th>
</tr>
</thead>
<tbody class="text-xs text-gray-700">
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">nginx</td>
<td class="py-2 px-3"><code class="bg-gray-100 px-1 rounded">sam-nginx-1</code></td>
<td class="py-2 px-3">Nginx</td>
<td class="py-2 px-3">리버스 프록시, SSL, 도메인 라우팅</td>
</tr>
<tr class="border-b border-gray-100 bg-blue-50">
<td class="py-2 px-3 font-medium">api</td>
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">sam-api-1</code></td>
<td class="py-2 px-3">PHP 8.4 + Laravel</td>
<td class="py-2 px-3">REST API, DB 마이그레이션</td>
</tr>
<tr class="border-b border-gray-100 bg-blue-50">
<td class="py-2 px-3 font-medium">mng</td>
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">sam-mng-1</code></td>
<td class="py-2 px-3">PHP 8.4 + Laravel</td>
<td class="py-2 px-3">관리자 (HTMX + Blade)</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">react</td>
<td class="py-2 px-3"><code class="bg-gray-100 px-1 rounded">sam-react-1</code></td>
<td class="py-2 px-3">Node.js + Next.js 15</td>
<td class="py-2 px-3">고객용 프론트엔드 (React)</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">sales</td>
<td class="py-2 px-3"><code class="bg-gray-100 px-1 rounded">sam-sales-1</code></td>
<td class="py-2 px-3">PHP + Laravel</td>
<td class="py-2 px-3">영업 관리</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">admin</td>
<td class="py-2 px-3"><code class="bg-gray-100 px-1 rounded">sam-admin-1</code></td>
<td class="py-2 px-3">PHP + Laravel</td>
<td class="py-2 px-3">시스템 관리자 페이지</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">php73</td>
<td class="py-2 px-3"><code class="bg-gray-100 px-1 rounded">sam-php73-1</code></td>
<td class="py-2 px-3">PHP 7.3</td>
<td class="py-2 px-3">레거시(5130) 호환</td>
</tr>
<tr class="border-b border-gray-100 bg-orange-50">
<td class="py-2 px-3 font-medium">mysql</td>
<td class="py-2 px-3"><code class="bg-orange-100 px-1 rounded">sam-mysql-1</code></td>
<td class="py-2 px-3">MySQL 8.0</td>
<td class="py-2 px-3">데이터베이스 (samdb + chandj)</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">phpmyadmin</td>
<td class="py-2 px-3"><code class="bg-gray-100 px-1 rounded">sam-phpmyadmin-1</code></td>
<td class="py-2 px-3">phpMyAdmin</td>
<td class="py-2 px-3">DB 관리 UI</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 2-2. 요청 흐름 -->
<div id="arch-flow" 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-blue-400 rounded-full"></span>
요청의 여정 브라우저에서 DB까지
</h3>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/docker-environment/4.svg') }}" alt="요청 흐름 5단계"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 280px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">요청 흐름 5단계 브라우저 Nginx PHP-FPM Laravel MySQL</p>
<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>
<p class="text-xs text-amber-900 leading-relaxed">
손님(브라우저) 식당에 들어오면 안내 데스크(Nginx) 좌석으로 안내
서빙 직원(PHP-FPM) 주문을 받아 주방(Laravel)에서 요리
재료 창고(MySQL)에서 재료를 꺼내 요리를 완성한다.
</p>
</div>
<div class="space-y-2">
<div class="flex items-center gap-3 text-xs">
<span class="w-6 h-6 bg-blue-500 text-white rounded-full flex items-center justify-center font-bold shrink-0">1</span>
<span class="text-gray-700"><strong>브라우저</strong>에서 <code class="bg-gray-100 px-1 rounded">mng.sam.kr</code> 접속</span>
</div>
<div class="flex items-center gap-3 text-xs">
<span class="w-6 h-6 bg-blue-500 text-white rounded-full flex items-center justify-center font-bold shrink-0">2</span>
<span class="text-gray-700"><strong>Nginx</strong> 도메인을 보고 MNG 컨테이너로 전달</span>
</div>
<div class="flex items-center gap-3 text-xs">
<span class="w-6 h-6 bg-blue-500 text-white rounded-full flex items-center justify-center font-bold shrink-0">3</span>
<span class="text-gray-700"><strong>PHP-FPM</strong> PHP 코드를 실행</span>
</div>
<div class="flex items-center gap-3 text-xs">
<span class="w-6 h-6 bg-blue-500 text-white rounded-full flex items-center justify-center font-bold shrink-0">4</span>
<span class="text-gray-700"><strong>Laravel</strong> 비즈니스 로직 처리 + DB 조회</span>
</div>
<div class="flex items-center gap-3 text-xs">
<span class="w-6 h-6 bg-blue-500 text-white rounded-full flex items-center justify-center font-bold shrink-0">5</span>
<span class="text-gray-700"><strong>MySQL</strong>에서 데이터를 가져와 HTML로 응답 반환</span>
</div>
</div>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 3. 서비스별 상세 --}}
{{-- ============================================================ --}}
<section id="services" 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-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">3</span>
서비스별 상세 컨테이너의 역할
</h2>
<!-- 3-1. MNG / API -->
<div id="svc-mng-api" 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-blue-400 rounded-full"></span>
MNG / API PHP + Laravel 컨테이너
</h3>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/docker-environment/5.svg') }}" alt="Supervisor 내부 프로세스 구조"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 280px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">컨테이너 내부 프로세스 구조 Supervisor가 관리</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div class="bg-blue-50 rounded-lg p-4 border border-blue-200">
<p class="font-semibold text-blue-800 text-sm mb-2">MNG 컨테이너</p>
<ul class="text-xs text-blue-900 space-y-1">
<li>PHP 8.4 + Laravel 11</li>
<li>Supervisor가 관리하는 프로세스:</li>
<li class="pl-3">- Nginx (내부 웹서버)</li>
<li class="pl-3">- PHP-FPM (코드 실행)</li>
<li class="pl-3">- Queue Worker x2 (백그라운드 작업)</li>
</ul>
</div>
<div class="bg-blue-50 rounded-lg p-4 border border-blue-200">
<p class="font-semibold text-blue-800 text-sm mb-2">API 컨테이너</p>
<ul class="text-xs text-blue-900 space-y-1">
<li>PHP 8.4 + Laravel 11</li>
<li>Supervisor가 관리하는 프로세스:</li>
<li class="pl-3">- Nginx (내부 웹서버)</li>
<li class="pl-3">- PHP-FPM (코드 실행)</li>
<li class="pl-3">- Queue Worker (백그라운드 작업)</li>
<li class="pl-3">- Scheduler (크론 작업)</li>
</ul>
</div>
</div>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100">
<p class="font-semibold text-amber-800 mb-2">비유: 식당 주방</p>
<p class="text-xs text-amber-900 leading-relaxed">
하나의 컨테이너 안에 <strong>주방장(Supervisor)</strong> 있고, 아래 요리사(PHP-FPM), 서빙 직원(Nginx), 설거지 담당(Queue Worker) 각자 일을 한다.
주방장이 누가 쓰러지면 바로 직원을 투입한다(프로세스 자동 재시작).
</p>
</div>
</div>
<!-- 3-2. React / MySQL -->
<div id="svc-react-mysql" 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-blue-400 rounded-full"></span>
React / MySQL
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="bg-purple-50 rounded-lg p-4 border border-purple-200">
<p class="font-semibold text-purple-800 text-sm mb-2">React (Next.js) 컨테이너</p>
<ul class="text-xs text-purple-900 space-y-1">
<li>Node.js + Next.js 15</li>
<li>고객용 프론트엔드 </li>
<li>서버 사이드 렌더링(SSR)</li>
<li>포트 3000에서 실행</li>
</ul>
<div class="bg-red-50 rounded p-2 mt-2 border border-red-200">
<p class="text-xs text-red-700 font-medium">서버에서 빌드 금지! 로컬에서만 빌드</p>
</div>
</div>
<div class="bg-orange-50 rounded-lg p-4 border border-orange-200">
<p class="font-semibold text-orange-800 text-sm mb-2">MySQL 컨테이너</p>
<ul class="text-xs text-orange-900 space-y-1">
<li>MySQL 8.0</li>
<li>데이터베이스 2 운영:</li>
<li class="pl-3">- <code class="bg-orange-100 px-1 rounded">samdb</code>: SAM 메인 DB</li>
<li class="pl-3">- <code class="bg-orange-100 px-1 rounded">chandj</code>: 레거시 DB</li>
<li>포트 3306으로 접근</li>
<li>데이터는 <code class="bg-orange-100 px-1 rounded">db_data</code> 볼륨에 영구 저장</li>
</ul>
</div>
</div>
</div>
<!-- 3-3. Nginx 라우팅 -->
<div id="svc-nginx" 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-blue-400 rounded-full"></span>
Nginx 도메인별 라우팅
</h3>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/docker-environment/6.svg') }}" alt="Nginx 도메인 라우팅 맵"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 280px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">Nginx 도메인 서비스 라우팅 </p>
<div class="bg-blue-50 rounded-lg p-4 border border-blue-100 mb-4">
<p class="text-sm text-blue-900 leading-relaxed">
외부 Nginx가 <strong>도메인 이름</strong> 보고 어떤 컨테이너로 보낼지 결정한다.
모든 요청은 80/443 포트로 들어오고, Nginx가 내부 서비스로 분배한다.
</p>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm border-collapse">
<thead>
<tr class="border-b-2 border-gray-200 bg-gray-50">
<th class="text-left py-2 px-3 font-medium text-gray-600">도메인</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">서비스</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">내부 포트</th>
</tr>
</thead>
<tbody class="text-xs text-gray-700">
<tr class="border-b border-gray-100">
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">mng.sam.kr</code></td>
<td class="py-2 px-3">mng (Laravel)</td>
<td class="py-2 px-3">:8080</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">api.sam.kr</code></td>
<td class="py-2 px-3">api (Laravel)</td>
<td class="py-2 px-3">:8080</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">app.sam.kr</code></td>
<td class="py-2 px-3">react (Next.js)</td>
<td class="py-2 px-3">:3000</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">admin.sam.kr</code></td>
<td class="py-2 px-3">admin (Laravel)</td>
<td class="py-2 px-3">:8080</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 4. 볼륨과 데이터 관리 --}}
{{-- ============================================================ --}}
<section id="volumes" 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-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">4</span>
볼륨과 데이터 관리
</h2>
<!-- 4-1. 바인드 마운트 -->
<div id="vol-bind" 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-blue-400 rounded-full"></span>
바인드 마운트 소스 코드 실시간 동기화
</h3>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/docker-environment/7.svg') }}" alt="볼륨 마운트 구조"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 280px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">바인드 마운트 vs Docker 볼륨</p>
<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>
<p class="text-xs text-amber-900 leading-relaxed">
바인드 마운트는 <strong> PC 폴더를 컨테이너와 공유</strong>하는 것이다.
PC에서 파일을 수정하면 컨테이너 안에서도 즉시 반영된다.
마치 클라우드 공유 폴더처럼 양쪽이 항상 같은 파일을 본다.
</p>
</div>
<div class="bg-gray-900 rounded-lg p-4 overflow-x-auto mb-4">
<pre class="text-xs font-mono leading-relaxed"><span class="text-gray-400"># docker-compose.yml 예시</span>
<span class="text-green-400">volumes:</span>
<span class="text-yellow-300">- ../mng:/var/www/mng</span> <span class="text-gray-400"># 바인드 마운트: 소스코드</span>
<span class="text-cyan-300">- mng_vendor:/var/www/mng/vendor</span> <span class="text-gray-400"># Docker 볼륨: 의존성</span></pre>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs">
<div class="bg-green-50 rounded-lg p-3 border border-green-200">
<p class="font-bold text-green-800 mb-1">바인드 마운트</p>
<p class="text-green-700">호스트 경로 컨테이너 경로 연결</p>
<p class="text-green-600 mt-1">코드 수정 즉시 반영</p>
</div>
<div class="bg-blue-50 rounded-lg p-3 border border-blue-200">
<p class="font-bold text-blue-800 mb-1">Docker 볼륨</p>
<p class="text-blue-700">Docker가 자체 관리하는 저장소</p>
<p class="text-blue-600 mt-1">vendor, node_modules 성능 향상</p>
</div>
</div>
</div>
<!-- 4-2. Docker 볼륨 -->
<div id="vol-docker" 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-blue-400 rounded-full"></span>
Docker 볼륨 의존성과 데이터 격리
</h3>
<div class="bg-blue-50 rounded-lg p-4 border border-blue-100 mb-4">
<p class="text-sm text-blue-900 leading-relaxed">
<code class="bg-blue-100 px-1 rounded">vendor</code> <code class="bg-blue-100 px-1 rounded">node_modules</code> Docker 볼륨으로 격리한다.
이렇게 하면 호스트(WSL) 파일 시스템 성능 문제를 피하고, 컨테이너 내부에서 빠르게 읽을 있다.
</p>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm border-collapse">
<thead>
<tr class="border-b-2 border-gray-200 bg-gray-50">
<th class="text-left py-2 px-3 font-medium text-gray-600">볼륨명</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">용도</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">특징</th>
</tr>
</thead>
<tbody class="text-xs text-gray-700">
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium"><code class="bg-gray-100 px-1 rounded">db_data</code></td>
<td class="py-2 px-3">MySQL 데이터</td>
<td class="py-2 px-3">컨테이너 삭제해도 데이터 유지</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium"><code class="bg-gray-100 px-1 rounded">api_vendor</code></td>
<td class="py-2 px-3">API Composer 패키지</td>
<td class="py-2 px-3">컨테이너 내부 성능 최적화</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium"><code class="bg-gray-100 px-1 rounded">mng_vendor</code></td>
<td class="py-2 px-3">MNG Composer 패키지</td>
<td class="py-2 px-3">컨테이너 내부 성능 최적화</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium"><code class="bg-gray-100 px-1 rounded">*_node_modules</code></td>
<td class="py-2 px-3">Node.js 패키지</td>
<td class="py-2 px-3">호스트와 분리하여 속도 향상</td>
</tr>
</tbody>
</table>
</div>
<div class="bg-red-50 rounded-lg p-4 border border-red-200 mt-4">
<p class="font-semibold text-red-800 text-sm mb-1">주의: Docker 볼륨은 호스트에서 직접 접근 불가</p>
<p class="text-xs text-red-700">
<code class="bg-red-100 px-1 rounded">vendor</code> 폴더는 Docker 볼륨 안에 있으므로,
호스트(WSL)에서 직접 수정할 없다. 반드시 <code class="bg-red-100 px-1 rounded">docker exec</code> 컨테이너에 들어가서 작업해야 한다.
</p>
</div>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 5. 환경 변수 (.env) 관리 --}}
{{-- ============================================================ --}}
<section id="env" 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-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">5</span>
환경 변수 (.env) 관리
</h2>
<!-- 5-1. .env의 역할 -->
<div id="env-role" 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-blue-400 rounded-full"></span>
.env 파일의 역할과 구조
</h3>
<div class="bg-blue-50 rounded-lg p-5 border border-blue-100 mb-4">
<p class="text-sm text-blue-900 leading-relaxed">
<strong>.env</strong> 파일은 애플리케이션의 <strong>환경 설정</strong> 저장하는 파일이다.
DB 접속 정보, API , 이름 <strong>환경마다 달라지는 </strong> 코드 밖에서 관리한다.
</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>
<p class="text-xs text-amber-900 leading-relaxed">
프랜차이즈 매장마다 <strong>전화번호, 주소, 영업시간</strong> 다르다.
매장 설정 카드(.env) 적어두면, 같은 매뉴얼(코드)로도 매장마다 다르게 운영할 있다.
코드를 바꾸지 않고도 DB 주소, 비밀번호 등을 변경 가능하다.
</p>
</div>
<div class="bg-gray-900 rounded-lg p-4 overflow-x-auto mb-4">
<pre class="text-xs font-mono leading-relaxed"><span class="text-gray-400"># .env 파일 예시</span>
<span class="text-green-400">APP_NAME</span>=<span class="text-yellow-300">SAM</span>
<span class="text-green-400">APP_ENV</span>=<span class="text-yellow-300">local</span>
<span class="text-green-400">APP_DEBUG</span>=<span class="text-yellow-300">true</span>
<span class="text-gray-400"># 데이터베이스 설정</span>
<span class="text-green-400">DB_HOST</span>=<span class="text-yellow-300">sam-mysql-1</span>
<span class="text-green-400">DB_PORT</span>=<span class="text-yellow-300">3306</span>
<span class="text-green-400">DB_DATABASE</span>=<span class="text-yellow-300">samdb</span>
<span class="text-green-400">DB_USERNAME</span>=<span class="text-yellow-300">samuser</span>
<span class="text-green-400">DB_PASSWORD</span>=<span class="text-yellow-300">sampass</span>
<span class="text-gray-400"># 메일 설정</span>
<span class="text-green-400">MAIL_MAILER</span>=<span class="text-yellow-300">smtp</span>
<span class="text-green-400">MAIL_HOST</span>=<span class="text-yellow-300">smtp.gmail.com</span></pre>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs">
<div class="bg-green-50 rounded-lg p-3 border border-green-200">
<p class="font-bold text-green-800 mb-1">.env 파일에 넣는 </p>
<ul class="text-green-700 space-y-0.5">
<li>- DB 접속 정보 (호스트, 비밀번호)</li>
<li>- API , 시크릿 </li>
<li>- 환경 (local / production)</li>
<li>- 메일, 캐시, 설정</li>
</ul>
</div>
<div class="bg-red-50 rounded-lg p-3 border border-red-200">
<p class="font-bold text-red-800 mb-1">.env 파일 주의사항</p>
<ul class="text-red-700 space-y-0.5">
<li>- Git에 커밋하면 (.gitignore)</li>
<li>- 비밀번호가 있으므로 외부 공유 금지</li>
<li>- 서버와 로컬 값이 다름</li>
<li>- 수정 캐시 클리어 필요</li>
</ul>
</div>
</div>
</div>
<!-- 5-2. 우선순위 -->
<div id="env-priority" 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-blue-400 rounded-full"></span>
Docker environment vs .env 우선순위
</h3>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/docker-environment/8.svg') }}" alt=".env 로드 흐름"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 280px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">.env 로드 흐름과 우선순위</p>
<div class="bg-blue-50 rounded-lg p-4 border border-blue-100 mb-4">
<p class="text-sm text-blue-900 leading-relaxed">
Docker Compose의 <code class="bg-blue-100 px-1 rounded">environment</code> 설정은
프로젝트 <code class="bg-blue-100 px-1 rounded">.env</code> 파일보다 <strong>우선</strong>한다.
, Docker에서 설정한 DB_HOST가 .env의 DB_HOST를 덮어쓴다.
</p>
</div>
<div class="space-y-2 mb-4">
<div class="flex items-center gap-3 text-xs">
<span class="w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center font-bold shrink-0">1</span>
<span class="text-gray-700"><strong>최우선:</strong> docker-compose.yml <code class="bg-gray-100 px-1 rounded">environment</code> 섹션</span>
</div>
<div class="flex items-center gap-3 text-xs">
<span class="w-6 h-6 bg-orange-500 text-white rounded-full flex items-center justify-center font-bold shrink-0">2</span>
<span class="text-gray-700"><strong>중간:</strong> 프로젝트 <code class="bg-gray-100 px-1 rounded">.env</code> 파일</span>
</div>
<div class="flex items-center gap-3 text-xs">
<span class="w-6 h-6 bg-gray-400 text-white rounded-full flex items-center justify-center font-bold shrink-0">3</span>
<span class="text-gray-700"><strong>기본값:</strong> <code class="bg-gray-100 px-1 rounded">.env.example</code> (참조용, 실제 로드 )</span>
</div>
</div>
<div class="bg-gray-900 rounded-lg p-4 overflow-x-auto">
<pre class="text-xs font-mono leading-relaxed"><span class="text-gray-400"># docker-compose.yml에서 설정한 환경 변수 (최우선)</span>
<span class="text-green-400">environment:</span>
- <span class="text-yellow-300">DB_HOST=sam-mysql-1</span> <span class="text-gray-400"># ← 이 값이 .env의 DB_HOST를 덮어씀</span>
- <span class="text-yellow-300">DB_PORT=3306</span>
- <span class="text-yellow-300">DB_DATABASE=samdb</span>
<span class="text-gray-400"># 프로젝트 .env 파일의 DB_HOST=127.0.0.1 은 무시됨</span></pre>
</div>
</div>
<!-- 5-3. 로컬 vs 서버 -->
<div id="env-local-server" 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-blue-400 rounded-full"></span>
로컬 vs 서버 .env 차이점
</h3>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/docker-environment/9.svg') }}" alt="로컬 vs 서버 환경 비교"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 280px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">로컬(Docker) vs 서버(직접 설치) 환경 비교</p>
<div class="overflow-x-auto">
<table class="w-full text-sm border-collapse">
<thead>
<tr class="border-b-2 border-gray-200 bg-gray-50">
<th class="text-left py-2 px-3 font-medium text-gray-600">설정 항목</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">로컬 (Docker)</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">서버 (운영)</th>
</tr>
</thead>
<tbody class="text-xs text-gray-700">
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">APP_ENV</td>
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">local</code></td>
<td class="py-2 px-3"><code class="bg-green-100 px-1 rounded">production</code></td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">APP_DEBUG</td>
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">true</code></td>
<td class="py-2 px-3"><code class="bg-green-100 px-1 rounded">false</code></td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">DB_HOST</td>
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">sam-mysql-1</code> (컨테이너명)</td>
<td class="py-2 px-3"><code class="bg-green-100 px-1 rounded">127.0.0.1</code> (로컬호스트)</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">DB_PASSWORD</td>
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">sampass</code> (개발용)</td>
<td class="py-2 px-3"><code class="bg-green-100 px-1 rounded">****</code> (운영용 복잡 비밀번호)</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">APP_URL</td>
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">https://mng.sam.kr</code></td>
<td class="py-2 px-3"><code class="bg-green-100 px-1 rounded">https://mng.sam.kr</code></td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">실행 방법</td>
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">docker exec</code></td>
<td class="py-2 px-3"><code class="bg-green-100 px-1 rounded">직접 명령 실행</code></td>
</tr>
</tbody>
</table>
</div>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mt-4">
<p class="font-semibold text-amber-800 mb-1">핵심 차이: DB_HOST</p>
<p class="text-xs text-amber-900">
로컬에서는 Docker 네트워크 안에서 컨테이너 이름(<code class="bg-amber-100 px-1 rounded">sam-mysql-1</code>)으로 DB에 접근하고,
서버에서는 MySQL이 같은 서버에 직접 설치되어 있으므로 <code class="bg-amber-100 px-1 rounded">127.0.0.1</code> 접근한다.
</p>
</div>
<div class="bg-indigo-50 rounded-lg p-4 border border-indigo-200 mt-4">
<p class="font-semibold text-indigo-800 mb-1">서버 환경 구조 (Docker 없음)</p>
<p class="text-xs text-indigo-900 mb-2">
운영 서버에는 Docker가 설치되어 있지 않다. 서버 스펙이 제한적이라(2코어/3.8GB RAM) Docker의 오버헤드를 감당하기 어렵기 때문이다.
</p>
<div class="bg-gray-900 rounded p-3">
<pre class="text-xs font-mono leading-relaxed"><span class="text-gray-400"># 운영 서버 구조 (네이티브 설치)</span>
<span class="text-green-400">운영 서버 (114.203.209.83)</span>
<span class="text-yellow-300">├── Nginx</span> <span class="text-gray-400"> 웹서버 (직접 설치)</span>
<span class="text-yellow-300">├── PHP-FPM</span> <span class="text-gray-400"> PHP 처리 (직접 설치)</span>
<span class="text-yellow-300">├── MySQL</span> <span class="text-gray-400"> DB (직접 설치)</span>
<span class="text-cyan-300">├── /home/webservice/mng</span> <span class="text-gray-400"> MNG </span>
<span class="text-cyan-300">├── /home/webservice/api</span> <span class="text-gray-400"> API </span>
<span class="text-cyan-300">└── /home/webservice/react</span> <span class="text-gray-400"> React </span></pre>
</div>
</div>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 6. 실전 명령어 & 트러블슈팅 --}}
{{-- ============================================================ --}}
<section id="commands" 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-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">6</span>
실전 명령어 & 트러블슈팅
</h2>
<!-- 6-0. 로컬 vs 서버 명령어 비교 -->
<div id="cmd-compare" 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-blue-400 rounded-full"></span>
로컬(Docker) vs 서버(네이티브) 명령어 비교
</h3>
<div class="bg-indigo-50 rounded-lg p-4 border border-indigo-100 mb-4">
<p class="text-sm text-indigo-900 leading-relaxed">
로컬에서는 <code class="bg-indigo-100 px-1 rounded">docker exec</code> 통해 컨테이너 안에서 명령을 실행하고,
서버에서는 Docker 없이 <strong>직접 명령을 실행</strong>한다.
</p>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm border-collapse">
<thead>
<tr class="border-b-2 border-gray-200 bg-gray-50">
<th class="text-left py-2 px-3 font-medium text-gray-600">작업</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">로컬 (Docker)</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">서버 (네이티브)</th>
</tr>
</thead>
<tbody class="text-xs text-gray-700">
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">artisan 실행</td>
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">docker exec sam-api-1 php artisan ...</code></td>
<td class="py-2 px-3"><code class="bg-green-100 px-1 rounded">cd /home/webservice/api && php artisan ...</code></td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">composer</td>
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">docker exec sam-api-1 composer install</code></td>
<td class="py-2 px-3"><code class="bg-green-100 px-1 rounded">cd /home/webservice/api && composer install</code></td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">마이그레이션</td>
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">docker exec sam-api-1 php artisan migrate</code></td>
<td class="py-2 px-3"><code class="bg-green-100 px-1 rounded">cd /home/webservice/api && php artisan migrate</code></td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">캐시 클리어</td>
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">docker exec sam-mng-1 php artisan config:clear</code></td>
<td class="py-2 px-3"><code class="bg-green-100 px-1 rounded">cd /home/webservice/mng && php artisan config:clear</code></td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-medium">로그 확인</td>
<td class="py-2 px-3"><code class="bg-blue-100 px-1 rounded">docker logs sam-api-1 --tail 50</code></td>
<td class="py-2 px-3"><code class="bg-green-100 px-1 rounded">tail -50 /home/webservice/api/storage/logs/laravel.log</code></td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 6-1. 자주 쓰는 명령어 -->
<div id="cmd-basic" 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-blue-400 rounded-full"></span>
자주 쓰는 로컬 Docker 명령어
</h3>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/docker-environment/10.svg') }}" alt="명령어 치트시트"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 280px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">Docker 명령어 치트시트</p>
<div class="space-y-3">
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
<p class="font-semibold text-green-800 text-sm mb-3">안전한 명령어 (자유롭게 사용)</p>
<div class="space-y-2">
<div class="bg-gray-900 rounded p-3">
<pre class="text-xs font-mono"><span class="text-gray-400"># 컨테이너 상태 확인</span>
<span class="text-green-400">docker ps</span>
<span class="text-gray-400"># 컨테이너 로그 확인</span>
<span class="text-green-400">docker logs sam-api-1 --tail 50</span>
<span class="text-gray-400"># 컨테이너 안에서 명령 실행</span>
<span class="text-green-400">docker exec sam-api-1 php artisan --version</span>
<span class="text-green-400">docker exec sam-mng-1 php artisan route:list</span>
<span class="text-gray-400"># 캐시 클리어 (설정 변경 후 필수)</span>
<span class="text-green-400">docker exec sam-api-1 php artisan config:clear</span>
<span class="text-green-400">docker exec sam-api-1 php artisan cache:clear</span>
<span class="text-green-400">docker exec sam-mng-1 php artisan config:clear</span></pre>
</div>
</div>
</div>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-200">
<p class="font-semibold text-amber-800 text-sm mb-3">주의가 필요한 명령어</p>
<div class="bg-gray-900 rounded p-3">
<pre class="text-xs font-mono"><span class="text-gray-400"># 모든 서비스 시작</span>
<span class="text-yellow-300">docker compose up -d</span>
<span class="text-gray-400"># 특정 서비스 재시작</span>
<span class="text-yellow-300">docker compose restart api</span>
<span class="text-gray-400"># 의존성 설치 (composer.json 변경 시)</span>
<span class="text-yellow-300">docker exec sam-api-1 composer install</span>
<span class="text-gray-400"># DB 마이그레이션 (반드시 API에서만!)</span>
<span class="text-yellow-300">docker exec sam-api-1 php artisan migrate</span></pre>
</div>
</div>
</div>
</div>
<!-- 6-2. 주의사항 -->
<div id="cmd-caution" 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-blue-400 rounded-full"></span>
SAM 프로젝트 주의사항
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div class="bg-red-50 rounded-lg p-4 border-2 border-red-200">
<div class="flex items-center gap-2 mb-2">
<span class="text-red-500 font-bold text-lg">&#10005;</span>
<p class="font-semibold text-red-800 text-sm">마이그레이션은 API에서만</p>
</div>
<p class="text-xs text-red-700">MNG에서 마이그레이션을 실행하면 된다. DB 스키마 관리는 API 프로젝트가 담당한다.</p>
<div class="bg-red-100 rounded p-2 mt-2">
<p class="text-xs text-red-800"><code>docker exec sam-mng-1 php artisan migrate</code> 금지!</p>
</div>
</div>
<div class="bg-red-50 rounded-lg p-4 border-2 border-red-200">
<div class="flex items-center gap-2 mb-2">
<span class="text-red-500 font-bold text-lg">&#10005;</span>
<p class="font-semibold text-red-800 text-sm">메뉴 시더 실행 금지</p>
</div>
<p class="text-xs text-red-700">메뉴 시더를 실행하면 부서별 권한 설정이 초기화된다. 메뉴 변경은 tinker로 개별 처리한다.</p>
<div class="bg-red-100 rounded p-2 mt-2">
<p class="text-xs text-red-800"><code>php artisan db:seed --class=MngMenuSeeder</code> 금지!</p>
</div>
</div>
<div class="bg-red-50 rounded-lg p-4 border-2 border-red-200">
<div class="flex items-center gap-2 mb-2">
<span class="text-red-500 font-bold text-lg">&#10005;</span>
<p class="font-semibold text-red-800 text-sm">서버에서 npm run build 금지</p>
</div>
<p class="text-xs text-red-700">서버 메모리 부족으로 빌드 실패. React 빌드는 반드시 로컬에서 수행한다.</p>
<div class="bg-red-100 rounded p-2 mt-2">
<p class="text-xs text-red-800">서버(2코어/3.8GB) 빌드 OOM 위험</p>
</div>
</div>
<div class="bg-green-50 rounded-lg p-4 border-2 border-green-200">
<div class="flex items-center gap-2 mb-2">
<span class="text-green-500 font-bold text-lg">&#10003;</span>
<p class="font-semibold text-green-800 text-sm">올바른 작업 흐름</p>
</div>
<p class="text-xs text-green-700">로컬에서 코드 수정 git commit git push 서버에서 git pull composer install config:clear</p>
<div class="bg-green-100 rounded p-2 mt-2">
<p class="text-xs text-green-800">코드 변경은 항상 로컬에서! 서버는 배포만!</p>
<p class="text-xs text-green-700 mt-1">서버에서는 Docker 없이 직접 명령 실행 (<code class="bg-green-200 px-1 rounded">php artisan</code>, <code class="bg-green-200 px-1 rounded">composer</code>)</p>
</div>
</div>
</div>
</div>
<!-- 6-3. 트러블슈팅 -->
<div id="cmd-trouble" 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-blue-400 rounded-full"></span>
문제 발생 대처법
</h3>
<div class="space-y-4">
<div class="bg-gray-50 rounded-lg p-4 border">
<p class="font-semibold text-gray-800 text-sm mb-2">"컨테이너가 안 뜬다"</p>
<div class="bg-gray-900 rounded p-3 mb-2">
<pre class="text-xs font-mono"><span class="text-gray-400"># 1. 상태 확인</span>
<span class="text-green-400">docker ps -a</span> <span class="text-gray-400"># 종료된 컨테이너도 표시</span>
<span class="text-gray-400"># 2. 에러 로그 확인</span>
<span class="text-green-400">docker logs sam-api-1 --tail 100</span>
<span class="text-gray-400"># 3. 서비스 재시작</span>
<span class="text-yellow-300">cd /home/aweso/sam/docker && docker compose restart api</span></pre>
</div>
</div>
<div class="bg-gray-50 rounded-lg p-4 border">
<p class="font-semibold text-gray-800 text-sm mb-2">"설정을 변경했는데 반영이 안 된다"</p>
<div class="bg-gray-900 rounded p-3 mb-2">
<pre class="text-xs font-mono"><span class="text-gray-400"># .env 변경 후 반드시 캐시 클리어</span>
<span class="text-green-400">docker exec sam-api-1 php artisan config:clear</span>
<span class="text-green-400">docker exec sam-api-1 php artisan cache:clear</span>
<span class="text-green-400">docker exec sam-api-1 php artisan view:clear</span></pre>
</div>
<p class="text-xs text-gray-600">Laravel은 설정을 캐시하므로, .env 변경 캐시를 비워야 값이 적용된다.</p>
</div>
<div class="bg-gray-50 rounded-lg p-4 border">
<p class="font-semibold text-gray-800 text-sm mb-2">"Class not found 에러"</p>
<div class="bg-gray-900 rounded p-3 mb-2">
<pre class="text-xs font-mono"><span class="text-gray-400"># Composer 오토로드 재생성</span>
<span class="text-green-400">docker exec sam-api-1 composer dump-autoload</span>
<span class="text-gray-400"># 또는 패키지 전체 재설치</span>
<span class="text-yellow-300">docker exec sam-api-1 composer install</span></pre>
</div>
</div>
<div class="bg-gray-50 rounded-lg p-4 border">
<p class="font-semibold text-gray-800 text-sm mb-2">"Permission denied 에러"</p>
<div class="bg-gray-900 rounded p-3 mb-2">
<pre class="text-xs font-mono"><span class="text-gray-400"># storage/logs 권한 확인</span>
<span class="text-green-400">docker exec sam-api-1 ls -la storage/logs/</span>
<span class="text-gray-400"># 권한 수정 (컨테이너 내부)</span>
<span class="text-yellow-300">docker exec sam-api-1 chmod -R 775 storage/</span>
<span class="text-yellow-300">docker exec sam-api-1 chown -R www-data:www-data storage/</span></pre>
</div>
</div>
</div>
</div>
</div>
</section>
</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>
@include('components.academy-glossary', ['domain' => 'docker-environment'])
@endsection