AI서버 구축 가이드 1부: 23개 컨테이너 통합 Docker Compose 아키텍처 해설

시리즈글 1 / 5 · 완전판
업데이트2026년 5월
컨테이너23개 서비스 완전 해설
난이도초급 ~ 고급
통합 Docker Compose 컨테이너 완전 해설 Portainer 환경변수 가이드 서비스 연동 구조 볼륨 설계
이 글은 AI서버 구축의 모든 서비스를 한 파일로 관리하는 통합 Docker Compose를 완전하게 해설합니다. 각 컨테이너가 왜 필요한지, 언제 제거해도 되는지, 어떻게 서로 연결되는지를 카드 형태로 먼저 파악하고, 이후 super-annotated Compose 파일과 환경변수·볼륨·연동 구조 완전 해설까지 — 이 글 하나로 전체 AI 서버 아키텍처를 완전히 이해합니다.
SECTION01

사전 준비 — Ubuntu · NVIDIA 드라이버 · Docker

AI서버 구축에서 Compose 파일을 실행하기 전에 반드시 완료해야 하는 3가지 선행 작업입니다. 이미 구성된 서버라면 아래 검증 명령어로 상태만 확인하세요.

✅ 선행 작업 체크리스트

단계작업검증 명령어정상 결과
1Ubuntu 24.04 LTS Server 설치 + 기본 설정lsb_release -aUbuntu 24.04 출력
2NVIDIA 드라이버 설치 (nouveau 차단 후)nvidia-smiGPU 정보 + 드라이버 버전 출력
3Docker CE + Compose v2 설치docker compose versionDocker Compose v2.x 출력
4NVIDIA Container Toolkit 설치 + Docker 재시작docker run --rm --gpus all nvidia/cuda:12.6.0-base-ubuntu22.04 nvidia-smi컨테이너 내부에서 GPU 출력
5daemon.json 설정 (UFW 우회 차단, nvidia runtime 기본)cat /etc/docker/daemon.jsoniptables:false, default-runtime:nvidia 확인

🔢 CUDA ↔ 드라이버 최소 요구사항 (GPU별 요약)

GPU최소 드라이버권장 드라이버권장 CUDAPyTorch
RTX 3060/3070/3080/3090 (Ampere)450.80+560.xCUDA 12.4~12.62.5.x / 2.6.x
RTX 4060/4070/4080/4090 (Ada)525.60+560.xCUDA 12.4~12.62.5.x / 2.6.x
RTX 5080/5090 (Blackwell)570.x+570.x+CUDA 12.82.6.x+
💡
Docker 환경에서 호스트 CUDA Toolkit 설치는 선택사항

Ollama, ComfyUI, vLLM 등은 CUDA 런타임을 Docker 이미지 안에 포함합니다. 호스트에는 NVIDIA 드라이버 + NVIDIA Container Toolkit만 있으면 됩니다. 호스트에서 직접 Python AI 스크립트를 실행할 때만 CUDA Toolkit 전체가 필요합니다.

SECTION02

전체 컨테이너 한눈에 보기 — 역할·필요성·제외 조건

아래 카드는 이 Compose 파일에 포함된 모든 서비스입니다. 왜 써야 하는지, 언제 빼도 되는지를 먼저 파악하고 자신의 환경에 맞게 선택하세요.

필수 — 빠지면 AI 서버 핵심 기능 불가
권장 — 없어도 동작하지만 크게 편리함
선택 — 필요한 경우에만 활성화
🧠 CORE — LLM 추론 핵심 스택
🦙
Ollama ai-ollama
GPU에서 LLM을 실행하는 핵심 추론 엔진. AI 서버의 심장부
필수 GPU 0
왜 필요한가
Open WebUI의 채팅 응답, n8n AI 노드, Dify AI 앱, RAG 임베딩 생성 모두 Ollama가 처리합니다. 이 서비스 없이는 로컬 LLM 추론이 완전히 불가능합니다.
언제 제외해도 되는가
OpenAI GPT / Anthropic Claude 등 외부 API만 사용하고 로컬 LLM이 전혀 필요 없을 때. 단, RAG 임베딩도 Ollama 모델을 쓰므로 RAG 기능도 일부 제한됩니다.
연동 서비스 (Ollama를 호출하는 서비스)
open-webui n8n dify pipelines
포트 / 내부 URL
11434 — 외부: 내부망만 허용 권장
내부 URL: http://ai-ollama:11434
💬
Open WebUI ai-webui
ChatGPT를 대체하는 올인원 AI 인터페이스. 채팅·RAG·이미지·음성 통합
필수 CPU
왜 필요한가
사용자가 AI와 상호작용하는 메인 인터페이스. Ollama·OpenAI·Claude를 한 UI에서 선택, RAG Knowledge Base 관리, 이미지 생성, 음성 입출력, 팀원 권한 관리까지 모두 담당합니다.
언제 제외해도 되는가
API 전용 서버로 운영하고 UI가 전혀 필요 없을 때. 또는 Dify로 대체할 경우. 그러나 대부분의 경우 핵심 서비스입니다.
연동 서비스 (Open WebUI가 사용하는)
ollama qdrant postgres searxng whisper kokoro comfyui pipelines
포트 / 내부 URL
3000 → 컨테이너 내부 8080
브라우저: http://서버IP:3000
🔀
Open WebUI Pipelines ai-pipelines
모든 채팅 메시지를 LLM 전후로 가로채 Python으로 처리하는 미들웨어
권장 CPU
왜 필요한가
채팅 메시지를 LLM에 전달하기 전·후에 Python 코드로 가공합니다. 한국어 자동 번역, PII(개인정보) 마스킹, 욕설 필터, 자동 분류 파이프라인, 외부 API 연동 등에 사용됩니다.
언제 제외해도 되는가
채팅 메시지를 전처리·후처리할 필요가 없을 때. 기본 채팅만 사용한다면 빼도 됩니다.
연동
open-webui ← 메시지 전달
포트
9099 (내부망만 사용)
🔍
SearXNG ai-searxng
프라이버시 보호 메타 검색 엔진. AI가 실시간 인터넷 정보를 참조하게 함
권장 CPU
왜 필요한가
Open WebUI의 웹 검색 기능 백엔드. “최근 뉴스 알려줘”처럼 AI가 인터넷을 검색해야 할 때 이 서비스가 Google, Bing 등을 통합 검색해 결과를 반환합니다. Google API 키 없이 무료로 사용 가능합니다.
언제 제외해도 되는가
AI가 최신 인터넷 정보를 검색할 필요가 없거나, Brave/Google API를 대신 사용할 경우 제거 가능합니다.
연동
open-webui ← 검색 결과 제공
포트
8080 (내부망만, 외부 노출 불필요)
🗄️ DATABASE — 데이터 레이어
🎯
Qdrant ai-qdrant
AI 문서를 벡터로 저장하는 RAG 전용 데이터베이스. Knowledge Base의 실제 저장소
필수 CPU+RAM
왜 필요한가
RAG(문서 기반 AI 검색)의 핵심 저장소입니다. PDF·Word·웹페이지를 임베딩해 저장하고, 질문과 의미적으로 유사한 내용을 밀리초 안에 검색합니다. Open WebUI가 “벡터 DB”로 Qdrant를 지정하면 모든 Knowledge Base가 여기 저장됩니다.
언제 제외해도 되는가
RAG(문서 검색) 기능을 전혀 사용하지 않을 때. ChromaDB로 대체 가능하나, 성능·필터링 면에서 Qdrant가 우수합니다.
연동
open-webui dify n8n
포트
6333 REST · 6334 gRPC
API Key로 접근 제어 필수
🐘
PostgreSQL ai-postgres
n8n·Open WebUI·Dify의 메타데이터를 저장하는 관계형 DB. 한 인스턴스로 여러 DB 통합 관리
필수 CPU
왜 필요한가
n8n의 워크플로우·실행 기록, Open WebUI의 사용자·대화·설정, Dify의 앱 구성을 저장합니다. 하나의 PostgreSQL 컨테이너로 여러 서비스의 DB를 동시에 관리해 리소스를 절약합니다. pgvector 확장으로 벡터 검색도 지원합니다.
언제 제외해도 되는가
n8n·Dify를 사용하지 않고 Open WebUI를 SQLite(기본)로만 운영할 때. 단, 다중 사용자·팀 운영에는 PostgreSQL이 필수입니다.
연동 (이 DB를 사용하는 서비스)
open-webui n8n dify
포트
5432 — 외부 노출 금지. 내부망만 사용
🔴
Redis ai-redis
인메모리 캐시 및 메시지 큐. Dify의 백그라운드 작업 처리에 필수
권장 CPU
왜 필요한가
Dify의 Celery 태스크 큐(비동기 임베딩·파이프라인 처리)와 세션 캐시에 사용됩니다. n8n 실행 결과 캐싱에도 활용할 수 있습니다. 매우 낮은 메모리(~50MB)로 동작합니다.
언제 제외해도 되는가
Dify를 사용하지 않을 경우 제거 가능합니다. n8n과 Open WebUI는 Redis 없이도 동작합니다.
연동
dify
포트
6379 — 내부망만. 비밀번호 필수
🛠️ MANAGEMENT — 서버 관리 도구
🪄
Portainer CE ai-portainer
Docker를 터미널 없이 브라우저에서 관리하는 GUI 대시보드. 컨테이너·볼륨·로그를 클릭으로 제어
권장 CPU
왜 필요한가
터미널에 익숙하지 않은 팀원도 컨테이너 상태 확인, 로그 조회, 재시작, 볼륨 관리를 브라우저에서 할 수 있습니다. docker ps / docker logs 명령어를 시각적 대시보드로 대체합니다. 무료(CE) 버전으로 충분합니다.
언제 제외해도 되는가
터미널에 익숙하고 CLI로만 관리할 경우 제거 가능합니다. 혼자 쓰는 서버라면 선택입니다.
제공 기능
컨테이너 시작/중지/재시작, 실시간 로그, 볼륨·네트워크·이미지 관리, Compose 스택 시각화, 리소스 사용량 모니터링
포트
9000 HTTP · 9443 HTTPS
브라우저: http://서버IP:9000
🌐
Nginx Proxy Manager ai-nginx-pm
Nginx 설정 파일 없이 GUI로 리버스 프록시·SSL 인증서를 관리하는 도구
권장 CPU
왜 필요한가
여러 AI 서비스를 각각 다른 도메인(ai.yoursite.com, n8n.yoursite.com)으로 안전하게 외부 공개합니다. Let’s Encrypt SSL 자동 발급·갱신을 UI에서 몇 클릭으로 처리합니다. nginx.conf 파일을 직접 편집할 필요가 없습니다.
언제 제외해도 되는가
Cloudflare Tunnel을 사용하면 NPM 없이도 HTTPS 외부 공개가 가능합니다. 내부망 전용이거나 Caddy로 대체할 경우 제거 가능합니다.
포트 (외부 노출)
80 HTTP · 443 HTTPS · 81 관리UI
연동
모든 서비스의 외부 접근 진입점. 내부 서비스 포트를 도메인으로 라우팅합니다.
🔭
Watchtower ai-watchtower
Docker 이미지를 자동으로 최신 버전으로 업데이트하는 데몬
권장 CPU
왜 필요한가
AI 도구들은 빠르게 업데이트됩니다. Watchtower가 설정된 주기(예: 매일 새벽 3시)에 자동으로 새 이미지를 풀하고 컨테이너를 재시작합니다. docker compose pull && docker compose up -d를 매번 수동으로 실행할 필요가 없습니다.
언제 제외해도 되는가
버전을 직접 고정 관리하거나, 프로덕션 환경에서 자동 업데이트가 위험할 때. 중요 서비스만 자동 업데이트 제외하는 설정도 가능합니다.
제공 기능
이미지 자동 업데이트 · Slack/이메일 알림 · 특정 컨테이너 제외 설정 · cron 스케줄
포트
없음 (데몬 전용 — UI 없음)
📡
Uptime Kuma ai-uptime
모든 AI 서비스의 가동 상태를 실시간 모니터링하고 다운 시 즉시 알림 발송
권장 CPU
왜 필요한가
각 서비스의 URL을 주기적으로 ping해 다운타임을 즉시 감지합니다. Ollama가 죽었거나, Open WebUI가 응답하지 않을 때 Slack·텔레그램·이메일로 즉시 알림을 보냅니다. 서비스 상태 페이지도 생성할 수 있습니다.
언제 제외해도 되는가
Grafana Alerting으로 대체하거나, 모니터링이 불필요한 개인 서버에서는 선택입니다.
포트
3001 — 브라우저 대시보드
연동
모든 서비스 URL을 외부에서 감시. Slack·Telegram webhook 연동
🤖 AUTOMATION — 자동화 & AI 앱
n8n ai-n8n
500개+ 앱을 연결하는 노코드 자동화 플랫폼. AI와 외부 서비스를 자동으로 연결
권장 CPU
왜 필요한가
이메일이 오면 AI가 요약해 슬랙으로 전달, 새 파일이 추가되면 자동 RAG 인덱싱, 매일 오전 트렌드 분석 리포트 생성 등 AI 서버를 자동화 엔진으로 만드는 핵심 도구입니다.
언제 제외해도 되는가
자동화가 전혀 필요 없고 수동으로만 AI를 사용할 때. Make(Integromat)나 Zapier로 대체 가능하지만 이 경우 외부로 데이터가 전송됩니다.
연동
ollama qdrant postgres + 외부 서비스 500+
포트
5678 — Nginx로 HTTPS 공개 권장
🏗️
Dify ai-dify
RAG·에이전트·워크플로우를 노코드로 구성하고 즉시 API로 배포하는 AI 앱 빌더
선택 CPU
왜 필요한가
Open WebUI보다 더 복잡한 AI 앱(다단계 RAG, 조건부 로직, 멀티에이전트)을 코드 없이 만들고 REST API로 즉시 배포할 수 있습니다. 사내 특화 챗봇이나 고객 지원 봇에 최적입니다.
언제 제외해도 되는가
Open WebUI로 충분하거나, 복잡한 AI 앱을 코드로 직접 개발할 경우. Redis와 PostgreSQL에 의존하므로 둘 다 활성화해야 합니다.
연동
ollama qdrant postgres redis
포트
3002 (API: 5001)
🎨 IMAGING — AI 이미지 생성 (GPU 집중)
🖼️
ComfyUI ai-comfyui
노드 그래프 방식의 이미지 생성 엔진. FLUX.1·SDXL 지원. API 자동화에 강점
선택 GPU
왜 필요한가
DALL-E로 이미지 한 장당 $0.04~$0.12 지불하는 대신 무제한 무료 생성. Open WebUI와 연결하면 채팅에서 이미지 생성 요청이 가능합니다. FLUX.1 최신 모델 지원이 타 도구보다 빠릅니다.
언제 제외해도 되는가
이미지 생성이 필요 없거나, VRAM이 부족해 LLM과 동시 실행이 어려울 때. Ollama와 GPU를 공유하므로 VRAM이 넉넉해야 합니다.
연동
open-webui ← 이미지 생성 요청
포트
8188 — REST API 포함
🎭
Stable Diffusion A1111 ai-a1111
탭 기반 직관적 UI의 이미지 생성 도구. 방대한 Extension 생태계가 강점
선택 GPU
왜 필요한가
초보자도 쉽게 사용하는 탭 방식 UI, ADetailer·ControlNet·LoRA 등 수백 개 Extension, 빠른 이미지 생성 실험에 적합합니다. ComfyUI와 병행해 용도를 분리하는 것을 권장합니다.
언제 제외해도 되는가
ComfyUI만 사용하거나 이미지 생성 자체가 불필요할 때. ComfyUI와 A1111은 GPU를 공유하므로 동시에 두 개를 쓰는 것은 VRAM 관리에 주의가 필요합니다.
연동
open-webui ← 이미지 API 제공
포트
7860 — REST API 포함
🎙️ VOICE — 음성 AI
🎤
Faster-Whisper ai-whisper
OpenAI Whisper의 4배 빠른 로컬 음성 인식(STT). 99개 언어, 한국어 특화
선택 GPU
왜 필요한가
Open WebUI 채팅창 마이크 버튼, 회의 녹음 자동 전사, 음성 AI 어시스턴트의 STT 백엔드. OpenAI Whisper API($0.006/분) 없이 무제한 무료 사용. OpenAI API 형식 호환이라 기존 코드 수정 없이 교체 가능합니다.
언제 제외해도 되는가
음성 입력 기능이 불필요하거나 타이핑으로만 AI와 대화할 경우. GPU 사용량을 줄이고 싶을 때도 제거 가능합니다.
연동
open-webui ← STT 백엔드
포트
8000 — OpenAI 호환 API
🔊
Kokoro TTS ai-kokoro
82M 파라미터 초경량 고품질 TTS. ElevenLabs 수준의 자연스러운 음성을 CPU로도 합성
선택 CPU
왜 필요한가
AI 답변을 자연스러운 음성으로 읽어주는 TTS 백엔드. ElevenLabs($22/월) 없이 무제한 무료. Open WebUI에서 답변 TTS 버튼이 이 서비스를 호출합니다. OpenAI TTS API 호환 포맷이라 교체가 쉽습니다.
언제 제외해도 되는가
AI 응답을 텍스트로만 보고 음성 재생이 필요 없을 때. CPU 부하를 줄이고 싶을 때 제거 가능합니다.
연동
open-webui ← TTS 백엔드
포트
8880 — OpenAI TTS 호환 API
📊 MONITORING — 관측 가능성
📈
Prometheus ai-prometheus
서버·GPU·컨테이너의 모든 메트릭을 수집하는 시계열 DB
권장CPU
역할 & 언제 제외하나
GPU 온도, VRAM 사용량, CPU·메모리·디스크·컨테이너 상태 등 수백 가지 메트릭을 30초 간격으로 수집합니다. Grafana의 데이터 소스로 사용됩니다. 그라파나 없이 운영하거나 단순 서버라면 제거 가능합니다.
포트
9090
연동
grafananvidia-exporternode-exporter
📊
Grafana ai-grafana
Prometheus 메트릭을 아름다운 대시보드로 시각화. GPU 모니터링 핵심
권장CPU
역할 & 언제 제외하나
GPU 온도·VRAM·전력·각 컨테이너 CPU·메모리를 실시간 그래프로 표시합니다. 임계값 초과 시 Slack 알림도 가능합니다. 단순 알림만 필요하다면 Uptime Kuma로 대체하고 제거 가능합니다.
포트
3003
연동
prometheus← 데이터 소스
🎮
NVIDIA Exporter ai-nvidia-exp
GPU 메트릭 수집기. Prometheus에 GPU 데이터 제공
제외 조건
GPU 없는 서버 또는 Prometheus 미사용 시
💻
Node Exporter ai-node-exp
CPU·RAM·디스크·네트워크 시스템 메트릭 수집
제외 조건
Prometheus 미사용 시
🐳
cAdvisor ai-cadvisor
컨테이너별 CPU·메모리·네트워크 I/O 메트릭
제외 조건
Portainer로 대체하거나 Prometheus 미사용 시
SECTION03

서비스 연동 구조도 — 누가 누구와 통신하는가

아래 구조도는 각 서비스가 어떤 방향으로 데이터를 주고받는지 보여줍니다. 화살표 방향은 요청의 방향(A → B: A가 B를 호출)입니다.

🔀 채팅 흐름 (사용자 질문 → 답변 과정)

브라우저 open-webui :3000 ollama :11434 LLM 모델
open-webui qdrant :6333 ← RAG 검색 (문서 관련 질문)
open-webui searxng :8080 ← 웹 검색 필요 시
open-webui pipelines :9099 ← 메시지 필터 적용 시

🎙️ 음성 흐름 (마이크 → 음성 답변 과정)

마이크 입력 open-webui whisper :8000 텍스트 변환
LLM 응답 텍스트 open-webui kokoro :8880 음성 재생

🗄️ 데이터 저장 흐름

open-webui postgres (openwebui DB) ← 사용자·대화·설정 저장
n8n postgres (n8n DB) ← 워크플로우·실행기록 저장
dify postgres (dify DB) + redis ← 앱구성 + 태스크 큐
open-webui / dify qdrant ← Knowledge Base 벡터 저장

📊 모니터링 흐름

prometheus ←수집 nvidia-exporter :9835 + GPU 메트릭
prometheus ←수집 node-exporter :9100 + 시스템 메트릭
prometheus ←수집 cadvisor :8080 + 컨테이너 메트릭
grafana :3003 ←쿼리 prometheus :9090 → 대시보드 시각화

🌐 네트워크 레이어 분리

네트워크목적포함 서비스외부 접근
ai-internal-net서비스 간 내부 통신 전용모든 서비스❌ 차단
ai-external-net외부 공개가 필요한 서비스open-webui, n8n, nginx-pm✅ Nginx 통해서만
host network시스템 메트릭 수집 정확도node-exporter만❌ 내부만
SECTION04

통합 Docker Compose — 초주석 완전판

모든 서비스를 하나의 파일로 관리합니다. YAML 주석이 각 서비스·설정·포트의 이유를 완전하게 설명하므로 파일 자체가 학습 문서입니다. Docker Compose Profiles로 필요한 서비스만 선택 실행합니다.

📁 ~/ai-server/compose/docker-compose.yml
yaml — 통합 AI 서버 Docker Compose (초주석 완전판)
##═══════════════════════════════════════════════════════════════════════════
##  AI 서버 통합 Docker Compose — 완전 주석판                               
##  작성: agibop.com · 업데이트: 2026년 5월                                 
##                                                                          
##  [실행 방법]                                                              
##  # 핵심 서비스만 (권장 시작 구성)                                         
##  docker compose --profile core --profile database --profile manage up -d  
##                                                                          
##  # 이미지 생성 추가                                                       
##  docker compose --profile core --profile database \                       
##    --profile manage --profile imaging up -d                               
##                                                                          
##  # 전체 실행 (전부 필요한 경우)                                           
##  docker compose \                                                         
##    --profile core --profile database --profile manage \                   
##    --profile automation --profile imaging --profile voice \                
##    --profile monitoring up -d                                             
##                                                                          
##  [프로파일 목록]                                                          
##  core       : Ollama + Open WebUI + Pipelines + SearXNG  (LLM 핵심)      
##  database   : Qdrant + PostgreSQL + Redis                (데이터 레이어)  
##  manage     : Portainer + Nginx PM + Watchtower + Uptime (관리 도구)      
##  automation : n8n + Dify                                 (자동화)         
##  imaging    : ComfyUI + A1111                            (이미지 생성)    
##  voice      : Whisper + Kokoro TTS                       (음성 AI)        
##  monitoring : Prometheus + Grafana + 3 Exporters         (모니터링)       
##═══════════════════════════════════════════════════════════════════════════

name: ai-server  # 스택 이름 — 모든 리소스(볼륨·네트워크)의 접두사가 됨

##───────────────────────────────────────────────────────────────────────────
## YAML 앵커 (Anchors) — 반복되는 설정을 한 곳에서 관리
## &이름으로 정의하고, *이름으로 참조 (DRY 원칙)
##───────────────────────────────────────────────────────────────────────────

# GPU 전체 사용 — Ollama처럼 GPU 전체를 독점하는 서비스용
x-gpu-all: &gpu-all
  deploy:
    resources:
      reservations:
        devices:
          - driver: nvidia
            count: all       # 모든 GPU 사용. 1개만 쓰려면 count:1
            capabilities: [gpu]

# GPU 0번 고정 — LLM과 이미지 생성을 GPU별 분리할 때 사용
# 멀티 GPU 서버에서 Ollama는 GPU 0, ComfyUI는 GPU 1로 분리 가능
x-gpu-0: &gpu-0
  deploy:
    resources:
      reservations:
        devices:
          - driver: nvidia
            device_ids: ['0']   # GPU 0번만. 싱글 GPU 서버에서는 gpu-all과 동일
            capabilities: [gpu]

# GPU 1번 고정 — 듀얼 GPU 서버에서 이미지 생성용으로 분리
x-gpu-1: &gpu-1
  deploy:
    resources:
      reservations:
        devices:
          - driver: nvidia
            device_ids: ['1']   # GPU 1번. 싱글 GPU 서버라면 gpu-0으로 교체
            capabilities: [gpu]

# 컨테이너 재시작 정책 — 서버 재부팅 시 자동 시작, 수동 stop만 제외
x-restart: &restart
  restart: unless-stopped

# 로그 설정 — 파일이 무한히 커지는 것을 방지
x-logging: &logging
  logging:
    driver: json-file
    options:
      max-size: "100m"  # 파일당 최대 100MB
      max-file: "3"     # 최대 3개 파일 로테이션 (총 최대 300MB/서비스)


services:

##═══════════════════════════════════════════════════════════════════════════
##  🧠 CORE 프로파일 — LLM 추론 핵심 스택
##  이 프로파일이 AI 서버의 최소 실행 단위입니다.
##═══════════════════════════════════════════════════════════════════════════

  #────────────────────────────────────────────────────────────────────────
  #  OLLAMA — LLM 추론 엔진 (AI 서버의 심장)
  #  ─────────────────────────────────────────
  #  역할: GPU에서 AI 모델(Llama, Qwen, Gemma 등)을 실행하는 백엔드 엔진
  #  왜 쓰는가:
  #    - Open WebUI의 채팅 응답, n8n AI 노드, RAG 임베딩 생성 모두 담당
  #    - API 호환(OpenAI 형식)이라 기존 코드 그대로 로컬 전환 가능
  #    - GPU 자동 감지, Flash Attention, 멀티 모델 동시 로딩 지원
  #  제외 조건: OpenAI/Claude 외부 API만 쓰고 로컬 LLM이 필요 없을 때
  #  연동: ← open-webui(채팅), ← n8n(자동화), ← dify(AI앱), ← pipelines
  #────────────────────────────────────────────────────────────────────────
  ollama:
    image: ollama/ollama:latest
    container_name: ai-ollama
    profiles: [core]
    <<: [*gpu-0, *restart, *logging]
    ports:
      - "11434:11434"  # REST API 포트 — UFW로 외부 차단 권장 (내부망만 사용)
    volumes:
      # 볼륨 설명:
      # ollama_data: 다운로드한 AI 모델 파일 저장 (각 모델 4~40GB)
      #   → 이 볼륨이 없으면 컨테이너 재시작마다 모델을 다시 다운로드해야 함
      - ollama_data:/root/.ollama
      # 호스트의 모델 폴더를 읽기 전용으로 마운트 (외부 GGUF 파일 사용 시)
      # GGUF 파일을 DATA_ROOT/models에 넣으면 Ollama가 직접 로드 가능
      - ${DATA_ROOT:-/home/ubuntu/ai-server/data}/models:/external_models:ro
    environment:
      # OLLAMA_NUM_PARALLEL: 동시에 처리할 요청 수
      #   24GB VRAM: 2~4 / 12GB VRAM: 1~2 / 8GB VRAM: 1 권장
      #   높이면 처리량↑ 하지만 각 요청에 할당되는 VRAM↓
      - OLLAMA_NUM_PARALLEL=${OLLAMA_NUM_PARALLEL:-2}
      # OLLAMA_MAX_LOADED_MODELS: 동시에 VRAM에 올려둘 모델 수
      #   여러 모델을 번갈아 쓸 때 재로딩 없이 빠른 전환 가능
      #   VRAM이 부족하면 1로 설정 (모델 교체 시 언로드 후 재로드)
      - OLLAMA_MAX_LOADED_MODELS=${OLLAMA_MAX_LOADED_MODELS:-2}
      # OLLAMA_FLASH_ATTENTION: Flash Attention 2 활성화
      #   Ampere(RTX 30xx) 이상 GPU에서 VRAM 30% 절약 + 속도↑
      #   Pascal(GTX 10xx) 이하에서는 0으로 설정
      - OLLAMA_FLASH_ATTENTION=${OLLAMA_FLASH_ATTENTION:-1}
      # OLLAMA_KEEP_ALIVE: 마지막 요청 후 모델을 VRAM에 유지하는 시간
      #   0: 즉시 언로드 (VRAM 절약), -1: 영구 유지, 30m: 30분 후 언로드
      - OLLAMA_KEEP_ALIVE=${OLLAMA_KEEP_ALIVE:-30m}
      - OLLAMA_ORIGINS=*  # CORS 허용 — Open WebUI 등 다른 컨테이너에서 접근 가능
      - OLLAMA_MAX_QUEUE=${OLLAMA_MAX_QUEUE:-512}  # 대기열 최대 크기
    healthcheck:
      # 30초마다 API를 확인해 서비스 정상 여부를 Docker에 알림
      # Open WebUI가 depends_on에서 이 healthcheck을 기다림
      test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 30s  # 첫 번째 체크 전 대기 시간 (모델 초기화 시간 고려)
    networks:
      - ai_internal   # 내부 서비스 통신용 (다른 컨테이너에서 ai-ollama:11434로 접근)


  #────────────────────────────────────────────────────────────────────────
  #  OPEN WEBUI — AI 통합 인터페이스 (ChatGPT 완전 대체)
  #  ─────────────────────────────────────────
  #  역할: 사용자가 AI와 상호작용하는 메인 웹 인터페이스
  #  왜 쓰는가:
  #    - Ollama/OpenAI/Claude를 한 UI에서 선택 사용
  #    - RAG Knowledge Base 관리, 이미지 생성, 음성 입출력 통합
  #    - 멀티유저 권한 관리, LDAP/OAuth 연동, 사용량 추적
  #    - 모바일 앱에서도 동일한 UI로 접근 가능
  #  제외 조건: API 전용 서버이거나 Dify로 완전 대체 시
  #  연동: → ollama(LLM), → qdrant(RAG), → postgres(DB),
  #        → searxng(웹검색), → whisper(STT), → kokoro(TTS),
  #        → comfyui(이미지), → pipelines(필터)
  #────────────────────────────────────────────────────────────────────────
  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: ai-webui
    profiles: [core]
    <<: [*restart, *logging]
    ports:
      - "3000:8080"  # 호스트 3000 → 컨테이너 8080. 브라우저에서 http://서버IP:3000
    volumes:
      # webui_data: Open WebUI 설정, 업로드 파일, RAG 문서 인덱스 저장
      #   → 이 볼륨에 모든 사용자 데이터가 있음. 백업 최우선순위 1위
      - webui_data:/app/backend/data
    environment:
      ## ── Ollama 연결 ─────────────────────────────────────────────────────
      # 컨테이너 내부에서 Ollama에 접근하는 URL
      # "ai-ollama"는 Docker 네트워크의 서비스명 (IP 대신 이름으로 통신)
      # ⚠️ localhost:11434 금지 — 컨테이너 내부에서 localhost는 자기 자신
      - OLLAMA_BASE_URL=http://ai-ollama:11434

      ## ── 보안 ────────────────────────────────────────────────────────────
      # JWT 서명에 사용하는 비밀키 — 반드시 32자 이상 랜덤 문자열로 변경
      # 이 값이 노출되면 토큰 위조 공격 가능. openssl rand -hex 32 로 생성
      - WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY:?WEBUI_SECRET_KEY를 .env에 반드시 설정하세요}
      - WEBUI_NAME=${WEBUI_NAME:-My Private AI}  # 브라우저 탭·로고에 표시되는 이름
      - WEBUI_URL=${WEBUI_URL:-http://localhost:3000}  # 공개 URL (OAuth 콜백에 사용)

      ## ── 사용자 가입 제어 ────────────────────────────────────────────────
      # true: 누구나 가입 가능 (초기 설정 시). false: 가입 차단
      # 관리자 계정 생성 후 반드시 false로 변경하고 docker compose up -d 재실행
      - ENABLE_SIGNUP=${ENABLE_SIGNUP:-true}
      # 신규 가입자 기본 역할: pending(승인 대기), user(즉시 사용 가능), admin
      # 외부 공개 서버라면 pending 설정 필수 (무단 접근 방지)
      - DEFAULT_USER_ROLE=${DEFAULT_USER_ROLE:-pending}
      - JWT_EXPIRES_IN=${JWT_EXPIRES_IN:-86400}  # 로그인 토큰 유효기간 (초). 86400=24시간

      ## ── 데이터베이스 ────────────────────────────────────────────────────
      # PostgreSQL을 사용하면 다중 사용자·팀 운영에 안정적
      # 미설정 시 SQLite로 자동 폴백 (소규모·개인 사용 가능)
      - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@ai-postgres:5432/openwebui

      ## ── RAG (문서 기반 AI 검색) ─────────────────────────────────────────
      # VECTOR_DB: 벡터 저장소 선택 (qdrant 권장. chroma는 단일노드만)
      - VECTOR_DB=${VECTOR_DB:-qdrant}
      # Qdrant 접속 정보 — ai-qdrant는 컨테이너 이름 (Docker 내부 DNS)
      - QDRANT_URI=http://ai-qdrant:6333
      - QDRANT_API_KEY=${QDRANT_API_KEY}

      # 임베딩 모델: 문서를 벡터로 변환하는 모델
      # BAAI/bge-m3: 한국어 포함 다국어 최고 성능 (1024차원, ~1.3GB)
      # nomic-embed-text: Ollama 내장 경량 모델 (768차원, ~270MB, 빠름)
      - RAG_EMBEDDING_MODEL=${RAG_EMBEDDING_MODEL:-BAAI/bge-m3}
      - RAG_EMBEDDING_ENGINE=ollama  # ollama(로컬) 또는 openai(클라우드)
      - RAG_OLLAMA_BASE_URL=http://ai-ollama:11434  # 임베딩에 Ollama 사용

      # 청킹 설정: 문서를 분할하는 단위
      # CHUNK_SIZE: 하나의 청크 크기 (토큰 수). 작을수록 정밀, 클수록 맥락 풍부
      - CHUNK_SIZE=${CHUNK_SIZE:-1000}
      # CHUNK_OVERLAP: 인접 청크 간 겹치는 크기. 0이면 문장이 잘릴 수 있음
      - CHUNK_OVERLAP=${CHUNK_OVERLAP:-100}
      # RAG_TOP_K: 검색 시 반환할 청크 수. 많을수록 맥락↑ 하지만 프롬프트↑
      - RAG_TOP_K=${RAG_TOP_K:-5}
      - RELEVANCE_THRESHOLD=${RELEVANCE_THRESHOLD:-0.35}  # 이 점수 이하 청크 제외
      # ENABLE_RAG_HYBRID_SEARCH: Dense(의미)+Sparse(키워드) 하이브리드 검색
      # true: 검색 품질 20~40% 향상 (Qdrant 전용 기능)
      - ENABLE_RAG_HYBRID_SEARCH=${ENABLE_RAG_HYBRID_SEARCH:-true}

      ## ── 웹 검색 ─────────────────────────────────────────────────────────
      - ENABLE_RAG_WEB_SEARCH=true
      - RAG_WEB_SEARCH_ENGINE=searxng
      # <query>는 Open WebUI가 자동으로 실제 검색어로 교체
      - SEARXNG_QUERY_URL=http://ai-searxng:8080/search?q=&format=json

      ## ── 이미지 생성 연동 ────────────────────────────────────────────────
      - IMAGE_GENERATION_ENGINE=comfyui  # comfyui 또는 automatic1111
      - COMFYUI_BASE_URL=http://ai-comfyui:8188

      ## ── 음성 AI 연동 ────────────────────────────────────────────────────
      # STT: 마이크 → 텍스트 변환 (Whisper 서버 연결)
      - AUDIO_STT_ENGINE=openai         # OpenAI 호환 API 형식 사용
      - AUDIO_STT_OPENAI_API_BASE_URL=http://ai-whisper:8000/v1
      - AUDIO_STT_OPENAI_API_KEY=any    # Whisper는 인증 없음, 아무 값이나 가능
      - AUDIO_STT_MODEL=${WHISPER_MODEL:-large-v3-turbo}
      # TTS: 텍스트 → 음성 변환 (Kokoro 서버 연결)
      - AUDIO_TTS_ENGINE=openai
      - AUDIO_TTS_OPENAI_API_BASE_URL=http://ai-kokoro:8880/v1
      - AUDIO_TTS_OPENAI_API_KEY=any
      - AUDIO_TTS_MODEL=kokoro
      - AUDIO_TTS_VOICE=${KOKORO_DEFAULT_VOICE:-af_sarah}  # 기본 성우 선택

      ## ── Pipelines 연동 ─────────────────────────────────────────────────
      # OpenAI API Base URL을 Pipelines 서버로 설정 → 메시지 전처리 활성화
      - ENABLE_OPENAI_API=true
      - OPENAI_API_BASE_URL=http://ai-pipelines:9099
      - OPENAI_API_KEY=${PIPELINE_API_KEY}

      ## ── 외부 클라우드 API (폴백용) ─────────────────────────────────────
      # 로컬 Ollama 모델이 부족할 때 클라우드 API를 보조로 사용 가능
      # 키가 없으면 비워두어도 됨 (로컬 전용 운영 가능)
      - OPENAI_API_KEYS=${OPENAI_API_KEY:-}
    depends_on:
      ollama:
        condition: service_healthy  # Ollama가 완전히 준비된 후 Open WebUI 시작
      ai-postgres:
        condition: service_healthy
      ai-qdrant:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s  # DB 연결 초기화 시간 고려해 60초 대기
    networks:
      - ai_internal
      - ai_external  # Nginx PM이 외부 트래픽을 라우팅하기 위해 같은 네트워크 필요


  #────────────────────────────────────────────────────────────────────────
  #  PIPELINES — 메시지 전처리·후처리 미들웨어
  #  ─────────────────────────────────────────
  #  역할: 채팅 메시지를 LLM 전달 전후에 Python 코드로 처리
  #  활용: 한국어 자동 감지, PII 마스킹, 콘텐츠 필터, 모델 라우팅
  #  제외 조건: 메시지 전처리가 전혀 필요 없을 때 profiles에서 core 제거
  #────────────────────────────────────────────────────────────────────────
  ai-pipelines:
    image: ghcr.io/open-webui/pipelines:main
    container_name: ai-pipelines
    profiles: [core]
    <<: [*restart, *logging]
    ports:
      - "9099:9099"  # 내부망만 — Open WebUI가 이 포트로 파이프라인 요청 전달
    volumes:
      # pipelines_data: Python 파이프라인 파일(.py)이 저장되는 위치
      #   Admin Panel → Pipelines에서 업로드한 파일이 여기 저장됨
      - pipelines_data:/app/pipelines
    environment:
      # Open WebUI에서 Pipelines 서버에 접속할 때 사용하는 인증 키
      # .env의 PIPELINE_API_KEY와 Open WebUI 설정의 값이 일치해야 함
      - PIPELINES_API_KEY=${PIPELINE_API_KEY}
    networks:
      - ai_internal


  #────────────────────────────────────────────────────────────────────────
  #  SEARXNG — 프라이버시 보호 웹 검색 엔진
  #  ─────────────────────────────────────────
  #  역할: AI가 실시간 인터넷 정보를 검색할 때 사용하는 백엔드
  #  왜 쓰는가: Google/Bing API 키 없이 무료 웹 검색. 사용자 IP 보호
  #  제외 조건: 웹 검색 기능이 불필요하거나 외부 검색 API 사용 시
  #────────────────────────────────────────────────────────────────────────
  ai-searxng:
    image: searxng/searxng:latest
    container_name: ai-searxng
    profiles: [core]
    <<: [*restart, *logging]
    # 포트 외부 노출 없음 — Open WebUI만 내부적으로 사용
    volumes:
      # searxng_data: SearXNG 설정 파일(settings.yml) 저장
      #   사용할 검색엔진(Google, Bing, DuckDuckGo 등) 커스텀 설정 가능
      - searxng_data:/etc/searxng
    environment:
      - SEARXNG_BASE_URL=http://ai-searxng:8080
      - SEARXNG_SECRET_KEY=${WEBUI_SECRET_KEY}  # CSRF 보호용 비밀키
    networks:
      - ai_internal


##═══════════════════════════════════════════════════════════════════════════
##  🗄️ DATABASE 프로파일 — 데이터 레이어
##  core 프로파일과 함께 항상 실행하는 것이 권장입니다.
##═══════════════════════════════════════════════════════════════════════════

  #────────────────────────────────────────────────────────────────────────
  #  QDRANT — 벡터 데이터베이스 (RAG의 핵심)
  #  ─────────────────────────────────────────
  #  역할: 문서를 임베딩 벡터로 저장하고 의미 기반 유사도 검색 제공
  #  왜 쓰는가:
  #    - SQL DB는 키워드 매칭, Qdrant는 의미(Semantics) 검색
  #    - "Docker 네트워크 설정"을 물어도 "컨테이너 통신 방법" 문서를 찾음
  #    - HNSW 인덱스로 수백만 건도 밀리초 검색
  #    - Hybrid 검색(의미+키워드 결합)으로 정확도 20~40% 향상
  #  제외 조건: RAG 기능 완전 불필요 시 (ChromaDB로 대체 가능)
  #────────────────────────────────────────────────────────────────────────
  ai-qdrant:
    image: qdrant/qdrant:latest
    container_name: ai-qdrant
    profiles: [core, database]  # core 프로파일에서도 자동 포함
    <<: [*restart, *logging]
    ports:
      - "6333:6333"  # REST API — Tailscale/내부망에서 직접 접근 허용 가능
      - "6334:6334"  # gRPC — 대량 데이터 삽입 시 REST보다 2~3배 빠름
    volumes:
      # qdrant_data: 모든 벡터와 페이로드(메타데이터) 저장
      #   → 이 볼륨을 삭제하면 Knowledge Base 전체가 사라짐!
      #   → 백업 최우선순위: ollama_data와 함께 1순위
      - qdrant_data:/qdrant/storage
    environment:
      # API Key 인증 활성화 — 키 없이는 컬렉션에 접근 불가
      - QDRANT__SERVICE__API_KEY=${QDRANT_API_KEY:?QDRANT_API_KEY를 .env에 설정하세요}
      - QDRANT__SERVICE__ENABLE_TLS=false  # Nginx/Cloudflare가 TLS 담당하므로 false
      - QDRANT__LOG_LEVEL=INFO
      - QDRANT__TELEMETRY_DISABLED=true    # 원격 측정 비활성화 (프라이버시)
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:6333/healthz"]
      interval: 30s
      timeout: 10s
      retries: 3
    networks:
      - ai_internal


  #────────────────────────────────────────────────────────────────────────
  #  POSTGRESQL — 관계형 DB (n8n·Open WebUI·Dify 공유)
  #  ─────────────────────────────────────────
  #  역할: 워크플로우·사용자·설정 등 구조화된 메타데이터 저장
  #  왜 쓰는가:
  #    - 한 인스턴스로 여러 서비스 DB를 관리 (자원 절약)
  #    - init.sql로 n8n/openwebui/dify DB를 자동 생성
  #    - pgvector 확장으로 벡터 검색도 지원 (Qdrant 보조용)
  #  제외 조건: n8n·Dify 미사용 + Open WebUI SQLite 모드일 때
  #────────────────────────────────────────────────────────────────────────
  ai-postgres:
    image: pgvector/pgvector:pg16  # pgvector 확장 포함 공식 이미지
    container_name: ai-postgres
    profiles: [core, database]
    <<: [*restart, *logging]
    ports:
      # 5432 외부 노출 금지 — 내부 서비스만 접근 가능
      # Tailscale VPN으로 접근 시 UFW에서 tailscale0 인터페이스만 허용
      - "127.0.0.1:5432:5432"  # loopback만 바인딩 (호스트 직접 접근 시)
    volumes:
      # postgres_data: PostgreSQL 데이터 파일 (WAL 포함)
      #   → 손상 시 n8n 워크플로우·대화 기록 전체 손실 가능. 백업 필수
      - postgres_data:/var/lib/postgresql/data
      # init.sql: 컨테이너 최초 실행 시 자동으로 여러 DB를 생성하는 스크립트
      #   → n8n/openwebui/dify DB를 각각 자동 생성 + pgvector 확장 활성화
      - ./configs/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    environment:
      # POSTGRES_USER: 슈퍼유저 계정 이름
      - POSTGRES_USER=${POSTGRES_USER:-aiserver}
      # POSTGRES_PASSWORD: 반드시 강력한 비밀번호로 변경!
      #   .env 파일에서 설정. 설정 안 하면 컨테이너 시작 실패
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD를 .env에 설정하세요}
      # POSTGRES_DB: 기본 DB명 (추가 DB는 init.sql에서 생성)
      - POSTGRES_DB=${POSTGRES_DB:-aiserver_main}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-aiserver}"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - ai_internal


  #────────────────────────────────────────────────────────────────────────
  #  REDIS — 인메모리 캐시 & 메시지 큐
  #  ─────────────────────────────────────────
  #  역할: Dify의 Celery 비동기 태스크 처리 큐
  #  왜 쓰는가:
  #    - 임베딩 처리·파이프라인 실행을 백그라운드에서 비동기 처리
  #    - 매우 낮은 자원 사용 (~50MB RAM)
  #  제외 조건: Dify를 사용하지 않을 경우 제거 가능
  #────────────────────────────────────────────────────────────────────────
  ai-redis:
    image: redis:7-alpine
    container_name: ai-redis
    profiles: [database]
    <<: [*restart, *logging]
    command: >
      redis-server
      --requirepass ${REDIS_PASSWORD:?REDIS_PASSWORD를 .env에 설정하세요}
      --maxmemory 2gb           # 최대 메모리 제한. AI 서버 RAM에 맞게 조정
      --maxmemory-policy allkeys-lru  # 메모리 초과 시 오래된 캐시부터 삭제
      --save 900 1              # 900초 내 1번 이상 변경 시 디스크 저장 (AOF 대안)
    volumes:
      # redis_data: Redis 스냅샷 파일 저장 (컨테이너 재시작 시 데이터 복원)
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 30s
      timeout: 10s
      retries: 3
    networks:
      - ai_internal


##═══════════════════════════════════════════════════════════════════════════
##  🛠️ MANAGE 프로파일 — 서버 관리 도구
##  운영 효율성을 높이는 도구 모음. 핵심 서비스와 독립적으로 실행 가능.
##═══════════════════════════════════════════════════════════════════════════

  #────────────────────────────────────────────────────────────────────────
  #  PORTAINER CE — Docker 관리 GUI (강력 권장!)
  #  ─────────────────────────────────────────
  #  역할: 브라우저에서 모든 Docker 컨테이너·볼륨·이미지를 GUI로 관리
  #  왜 쓰는가:
  #    - "docker ps, docker logs, docker exec" 명령을 클릭으로 대체
  #    - 팀원이 터미널 없이 컨테이너 상태·로그·재시작 가능
  #    - Compose 스택을 시각적으로 관리
  #    - 볼륨 용량 확인, 이미지 정리, 네트워크 확인 모두 UI에서
  #    - 무료(CE) 버전으로 홈랩·팀 운영 충분
  #  제외 조건: CLI만으로 관리할 경우 제거 가능 (개인 고급 사용자)
  #  접속: http://서버IP:9000 → 최초 접속 시 admin 계정 생성
  #────────────────────────────────────────────────────────────────────────
  ai-portainer:
    image: portainer/portainer-ce:latest
    container_name: ai-portainer
    profiles: [manage]
    <<: [*restart, *logging]
    ports:
      - "9000:9000"   # HTTP 관리 UI — Nginx로 HTTPS 래핑 권장
      - "9443:9443"   # HTTPS 관리 UI (자체 서명 인증서 포함)
    volumes:
      # Docker 소켓 — Portainer가 Docker 데몬과 통신하는 필수 연결
      # ⚠️ 보안 주의: /var/run/docker.sock 마운트는 컨테이너에 Docker 전체 권한을 줌
      #   내부망 전용으로만 사용하고 외부 공개 시 강력한 인증 필수
      - /var/run/docker.sock:/var/run/docker.sock
      # portainer_data: Portainer 설정·사용자·환경 정보 저장
      - portainer_data:/data
    networks:
      - ai_internal
      - ai_external


  #────────────────────────────────────────────────────────────────────────
  #  NGINX PROXY MANAGER — GUI 기반 리버스 프록시 & SSL 관리
  #  ─────────────────────────────────────────
  #  역할: 여러 서비스를 서브도메인으로 안전하게 외부 공개
  #  왜 쓰는가:
  #    - nginx.conf 파일 편집 없이 GUI에서 프록시 규칙 설정
  #    - Let's Encrypt SSL 자동 발급·갱신 (클릭 한 번)
  #    - ai.domain.com → :3000, n8n.domain.com → :5678 라우팅
  #    - 접근 제어·기본 인증 레이어 추가 가능
  #  제외 조건: Cloudflare Tunnel 사용 시 Nginx PM 없이도 HTTPS 가능
  #  접속: http://서버IP:81 → [email protected] / changeme (최초)
  #────────────────────────────────────────────────────────────────────────
  ai-nginx-pm:
    image: jc21/nginx-proxy-manager:latest
    container_name: ai-nginx-pm
    profiles: [manage]
    <<: [*restart, *logging]
    ports:
      - "80:80"    # HTTP → HTTPS 자동 리다이렉트
      - "443:443"  # HTTPS (SSL 종료)
      - "81:81"    # 관리 UI — 내부망만 허용 권장 (UFW로 외부 차단)
    volumes:
      # nginx_pm_data: 프록시 설정, SSL 인증서, 사용자 정보 저장
      - nginx_pm_data:/data
      - nginx_pm_letsencrypt:/etc/letsencrypt  # Let's Encrypt 인증서 저장
    networks:
      - ai_internal
      - ai_external


  #────────────────────────────────────────────────────────────────────────
  #  WATCHTOWER — Docker 이미지 자동 업데이트 데몬
  #  ─────────────────────────────────────────
  #  역할: 지정한 주기마다 새 이미지를 확인하고 자동으로 컨테이너를 업데이트
  #  왜 쓰는가:
  #    - Ollama, Open WebUI 등 AI 도구는 주 1~2회 업데이트
  #    - 수동으로 docker compose pull && up -d 를 매번 실행할 필요 없음
  #    - 업데이트 후 Slack/이메일 알림 발송 가능
  #  제외 조건: 버전을 수동으로 고정 관리할 때. 또는 프로덕션에서 
  #             자동 업데이트가 위험한 경우 (WATCHTOWER_LABEL_ENABLE로 선택 제어)
  #────────────────────────────────────────────────────────────────────────
  ai-watchtower:
    image: containrrr/watchtower:latest
    container_name: ai-watchtower
    profiles: [manage]
    <<: [*restart, *logging]
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock  # Docker 데몬 접근 필요
    environment:
      # 업데이트 주기: 매일 새벽 3시 (cron 형식)
      - WATCHTOWER_SCHEDULE=0 0 3 * * *
      - WATCHTOWER_CLEANUP=true         # 이전 이미지 자동 삭제 (디스크 절약)
      - WATCHTOWER_INCLUDE_STOPPED=false # 중지된 컨테이너는 제외
      # 업데이트 완료 시 Slack 알림 (선택)
      # - WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL=https://hooks.slack.com/...
      # 특정 컨테이너 제외: 컨테이너에 라벨 com.centurylinklabs.watchtower.enable=false 추가


  #────────────────────────────────────────────────────────────────────────
  #  UPTIME KUMA — 서비스 가동 상태 모니터링 & 알림
  #  ─────────────────────────────────────────
  #  역할: 각 AI 서비스 URL을 주기적으로 ping해 다운타임을 즉시 감지·알림
  #  왜 쓰는가:
  #    - Ollama가 크래시되거나 Open WebUI가 응답 없을 때 즉시 슬랙 알림
  #    - 예쁜 Status Page로 팀원에게 서비스 상태 공유 가능
  #    - HTTP·TCP·DNS·도커 컨테이너 등 다양한 모니터링 유형 지원
  #  제외 조건: Grafana Alerting으로 대체하거나 알림이 불필요할 때
  #  접속: http://서버IP:3001 → 최초 계정 생성 후 모니터 추가
  #────────────────────────────────────────────────────────────────────────
  ai-uptime:
    image: louislam/uptime-kuma:latest
    container_name: ai-uptime
    profiles: [manage]
    <<: [*restart, *logging]
    ports:
      - "3001:3001"
    volumes:
      # uptime_data: 모니터 설정, 히스토리, 알림 설정 저장
      - uptime_data:/app/data
    networks:
      - ai_internal
      - ai_external


##═══════════════════════════════════════════════════════════════════════════
##  🤖 AUTOMATION 프로파일 — 자동화 & AI 앱 빌더
##═══════════════════════════════════════════════════════════════════════════

  #────────────────────────────────────────────────────────────────────────
  #  N8N — 노코드 AI 워크플로우 자동화 플랫폼
  #  ─────────────────────────────────────────
  #  역할: 이메일·슬랙·GitHub 등 500개+ 서비스를 AI와 자동 연결
  #  왜 쓰는가:
  #    - 이메일 수신 → Ollama 요약 → 슬랙 전달 (코드 없이 드래그 앤 드롭)
  #    - 블로그 RSS 변경 → AI 번역·요약 → 자동 게시
  #    - GitHub PR 생성 → AI 코드 리뷰 → 자동 코멘트
  #  제외 조건: 자동화가 전혀 불필요하거나 Make/Zapier 사용 시
  #────────────────────────────────────────────────────────────────────────
  ai-n8n:
    image: n8nio/n8n:latest
    container_name: ai-n8n
    profiles: [automation]
    <<: [*restart, *logging]
    ports:
      - "5678:5678"  # 웹 에디터 & 웹훅 수신 포트
    volumes:
      # n8n_data: 모든 워크플로우·인증정보·실행 기록 저장
      #   → 백업 중요도 높음. 워크플로우 재설계 비용이 크기 때문
      - n8n_data:/home/node/.n8n
      # 호스트 데이터 폴더를 워크플로우에서 직접 접근 가능하게 마운트
      - ${DATA_ROOT:-/home/ubuntu/ai-server/data}/outputs:/outputs
    environment:
      - N8N_HOST=${N8N_HOST:-localhost}
      - N8N_PORT=5678
      - N8N_PROTOCOL=${N8N_PROTOCOL:-http}
      # 웹훅 URL: 외부 서비스(GitHub, Slack 등)가 n8n에 이벤트를 보낼 주소
      # Nginx/Cloudflare로 외부 공개 후 공개 URL로 설정
      - WEBHOOK_URL=${N8N_WEBHOOK_URL:-http://localhost:5678}
      # 기본 인증 (Nginx 레벨 인증과 이중 보호)
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER:-admin}
      - N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD:?n8n 비밀번호 설정 필요}
      # 암호화 키: 인증정보(API 키, 비밀번호 등)를 암호화하는 키
      # 변경하면 기존 인증정보가 복호화 불가. 처음 설정 후 절대 변경 금지
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY:?n8n 암호화 키 설정 필요}
      - GENERIC_TIMEZONE=Asia/Seoul
      - N8N_DIAGNOSTICS_ENABLED=false  # 원격 사용 데이터 전송 비활성화
      # PostgreSQL 사용 (SQLite 대비 동시성·안정성 우수)
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=ai-postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=${POSTGRES_USER:-aiserver}
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
    depends_on:
      ai-postgres:
        condition: service_healthy
    networks:
      - ai_internal
      - ai_external


  #────────────────────────────────────────────────────────────────────────
  #  DIFY — AI 앱 빌더 & RAG 플랫폼
  #  ─────────────────────────────────────────
  #  역할: 복잡한 AI 앱을 노코드로 구성하고 REST API로 즉시 배포
  #  왜 쓰는가:
  #    - 다단계 RAG, 조건부 분기, 멀티에이전트를 시각적으로 설계
  #    - 완성된 AI 앱을 API 또는 임베드 UI로 즉시 배포
  #    - Open WebUI보다 복잡한 비즈니스 로직의 AI 앱에 적합
  #  제외 조건: Open WebUI로 충분하거나, 커스텀 코드 개발 선호 시
  #  의존성: postgres + redis 필수 (database 프로파일과 함께 실행)
  #────────────────────────────────────────────────────────────────────────
  ai-dify:
    image: langgenius/dify-api:latest
    container_name: ai-dify
    profiles: [automation]
    <<: [*restart, *logging]
    ports:
      - "3002:5001"  # Dify API 서버 포트
    volumes:
      # dify_data: 업로드 파일, 앱 설정, 로그 저장
      - dify_data:/app/api/storage
    environment:
      - SECRET_KEY=${DIFY_SECRET_KEY:?Dify 비밀키 설정 필요}
      # PostgreSQL 연결
      - DB_USERNAME=${POSTGRES_USER:-aiserver}
      - DB_PASSWORD=${POSTGRES_PASSWORD}
      - DB_HOST=ai-postgres
      - DB_PORT=5432
      - DB_DATABASE=dify
      # Redis 연결 (태스크 큐용)
      - REDIS_HOST=ai-redis
      - REDIS_PORT=6379
      - REDIS_PASSWORD=${REDIS_PASSWORD}
      - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD}@ai-redis:6379/1
      # Qdrant 연결 (벡터 검색)
      - VECTOR_STORE=qdrant
      - QDRANT_URL=http://ai-qdrant:6333
      - QDRANT_API_KEY=${QDRANT_API_KEY}
      # Ollama 연결
      - OLLAMA_API_BASE_URL=http://ai-ollama:11434
    depends_on:
      - ai-postgres
      - ai-redis
      - ai-qdrant
    networks:
      - ai_internal


##═══════════════════════════════════════════════════════════════════════════
##  🎨 IMAGING 프로파일 — AI 이미지 생성 (GPU 집중)
##  ⚠️ 주의: Ollama와 GPU를 공유합니다. 동시 실행 시 VRAM 합산 필요
##  권장: 멀티 GPU 서버에서 GPU 번호를 분리하거나 필요할 때만 시작
##═══════════════════════════════════════════════════════════════════════════

  #────────────────────────────────────────────────────────────────────────
  #  COMFYUI — 노드 그래프 이미지 생성 (FLUX.1 최고 지원)
  #  ─────────────────────────────────────────
  #  역할: FLUX.1·SDXL·SD3 등 최신 모델로 무제한 AI 이미지 생성
  #  왜 쓰는가:
  #    - DALL-E($0.04~$0.12/장) 없이 무제한 무료 생성
  #    - FLUX.1 Dev 최초 지원, REST API로 n8n 자동화 가능
  #    - 워크플로우를 JSON으로 저장·공유·재현 가능
  #  제외 조건: 이미지 생성 불필요 또는 VRAM 부족 시
  #  GPU: gpu-1 (멀티 GPU 서버에서 Ollama와 분리 권장)
  #────────────────────────────────────────────────────────────────────────
  ai-comfyui:
    image: yanwk/comfyui-boot:cu124
    container_name: ai-comfyui
    profiles: [imaging]
    <<: [*gpu-1, *restart, *logging]  # 싱글 GPU면 *gpu-all로 교체
    ports:
      - "8188:8188"
    volumes:
      # comfyui_models: Checkpoint·LoRA·VAE·ControlNet 등 모델 파일
      #   → Ollama와 모델 폴더 공유로 스토리지 절약 (shared_models)
      - comfyui_models:/root/ComfyUI/models
      # comfyui_output: 생성된 이미지 저장. 호스트에서 직접 접근 가능
      - comfyui_output:/root/ComfyUI/output
      # comfyui_nodes: ComfyUI Manager로 설치한 커스텀 노드
      - comfyui_nodes:/root/ComfyUI/custom_nodes
    environment:
      - NVIDIA_VISIBLE_DEVICES=all
      # CLI_ARGS: ComfyUI 실행 옵션
      #   --listen 0.0.0.0: 외부 접근 허용 (내부망 접근 위해 필요)
      #   --preview-method auto: 생성 중 미리보기 활성화
      - CLI_ARGS=${COMFYUI_ARGS:---listen 0.0.0.0 --port 8188 --preview-method auto}
    shm_size: 8gb  # 공유 메모리. 배치 이미지 생성 시 부족하면 OOM 발생
    networks:
      - ai_internal


  #────────────────────────────────────────────────────────────────────────
  #  STABLE DIFFUSION A1111 — 직관적 UI 이미지 생성
  #  ─────────────────────────────────────────
  #  역할: 탭 방식 UI로 빠른 이미지 생성 실험. Extension 생태계 풍부
  #  왜 쓰는가:
  #    - 초보자도 쉬운 UI (txt2img, img2img, Inpainting 탭)
  #    - ADetailer·ControlNet·LoRA 등 수백 개 Extension
  #    - ComfyUI와 역할 분리: 실험은 A1111, 자동화는 ComfyUI
  #  제외 조건: ComfyUI만 사용하거나 이미지 생성 불필요 시
  #────────────────────────────────────────────────────────────────────────
  ai-a1111:
    image: universalml/stable-diffusion-webui:latest
    container_name: ai-a1111
    profiles: [imaging]
    <<: [*gpu-1, *restart, *logging]  # 싱글 GPU면 *gpu-all로 교체
    ports:
      - "7860:7860"
    volumes:
      - a1111_models:/stable-diffusion-webui/models
      - a1111_output:/stable-diffusion-webui/outputs
      - a1111_extensions:/stable-diffusion-webui/extensions
    environment:
      - NVIDIA_VISIBLE_DEVICES=all
      # CLI_ARGS 핵심 옵션 설명:
      #   --listen: 외부 접근 허용
      #   --api: REST API 활성화 (n8n 연동 필수)
      #   --xformers: VRAM 20~30% 절약 (RTX 2080 이상 권장)
      #   --medvram: 12GB 이하 VRAM에서 OOM 방지
      - CLI_ARGS=${A1111_ARGS:---listen --api --xformers --no-half-vae}
    networks:
      - ai_internal


##═══════════════════════════════════════════════════════════════════════════
##  🎙️ VOICE 프로파일 — 음성 AI 서비스
##═══════════════════════════════════════════════════════════════════════════

  #────────────────────────────────────────────────────────────────────────
  #  FASTER-WHISPER — 고성능 음성 인식 서버 (STT)
  #  ─────────────────────────────────────────
  #  역할: 마이크 음성을 텍스트로 변환. OpenAI Whisper API 형식 호환
  #  왜 쓰는가:
  #    - OpenAI Whisper API($0.006/분) 없이 무제한 무료 STT
  #    - original Whisper보다 4배 빠르고 VRAM 절반만 사용
  #    - 한국어 포함 99개 언어 지원
  #    - 기존 OpenAI Whisper API 코드를 URL만 변경으로 교체 가능
  #  제외 조건: 음성 입력 기능이 불필요할 때
  #────────────────────────────────────────────────────────────────────────
  ai-whisper:
    image: fedirz/faster-whisper-server:latest-cuda
    container_name: ai-whisper
    profiles: [voice]
    <<: [*gpu-all, *restart, *logging]
    ports:
      - "8000:8000"  # OpenAI /v1/audio/transcriptions 호환 API
    volumes:
      # whisper_models: 다운로드한 Whisper 모델 캐시
      #   first 실행 시 모델 자동 다운로드 (~3GB for large-v3-turbo)
      - whisper_models:/root/.cache/huggingface
    environment:
      # 모델 선택: tiny/base/small/medium/large-v3/large-v3-turbo
      # large-v3-turbo: large-v3와 동일 품질, 처리속도 2배 (권장)
      - WHISPER__MODEL=${WHISPER_MODEL:-large-v3-turbo}
      - WHISPER__INFERENCE_DEVICE=cuda  # GPU 사용. CPU만 있으면 cpu로 변경
      - WHISPER__COMPUTE_TYPE=float16   # FP16으로 VRAM 절반 사용. int8은 더 빠름
      # 언어 고정: ko로 설정하면 한국어 인식 정확도↑. 다국어면 비워둠
      - WHISPER__LANGUAGE=${WHISPER_LANGUAGE:-ko}
      - WHISPER__VAD_FILTER=true  # 무음 구간 자동 감지·제거 (정확도↑)
    networks:
      - ai_internal


  #────────────────────────────────────────────────────────────────────────
  #  KOKORO TTS — 고품질 음성 합성 서버
  #  ─────────────────────────────────────────
  #  역할: AI 텍스트 답변을 자연스러운 음성으로 변환 (TTS)
  #  왜 쓰는가:
  #    - ElevenLabs($22/월) 없이 무제한 무료 TTS
  #    - 82M 파라미터 초경량이지만 ElevenLabs 수준의 자연스러운 음성
  #    - CPU만으로도 실시간 스트리밍 가능 (GPU 불필요)
  #    - OpenAI TTS API 형식 호환 (api.openai.com 주소만 변경)
  #  제외 조건: 음성 출력이 불필요하거나 텍스트만 보는 경우
  #────────────────────────────────────────────────────────────────────────
  ai-kokoro:
    image: ghcr.io/remsky/kokoro-fastapi-cpu:latest  # CPU 버전 (GPU 버전도 있음)
    container_name: ai-kokoro
    profiles: [voice]
    <<: [*restart, *logging]
    ports:
      - "8880:8880"  # OpenAI /v1/audio/speech 호환 API
    volumes:
      - kokoro_models:/app/models  # Kokoro 음성 모델 캐시
    environment:
      # 기본 성우 선택: af_sarah(여성·부드러움), af_bella(여성·활기참),
      #   am_adam(남성·차분함), am_michael(남성·전문적)
      - KOKORO_DEFAULT_VOICE=${KOKORO_DEFAULT_VOICE:-af_sarah}
    networks:
      - ai_internal


##═══════════════════════════════════════════════════════════════════════════
##  📊 MONITORING 프로파일 — 관측 가능성 스택
##═══════════════════════════════════════════════════════════════════════════

  #────────────────────────────────────────────────────────────────────────
  #  PROMETHEUS — 메트릭 수집 & 시계열 DB
  #  역할: GPU·CPU·RAM·컨테이너의 모든 수치를 시계열로 저장
  #  제외 조건: Grafana 없이 Uptime Kuma만으로 모니터링할 경우
  #────────────────────────────────────────────────────────────────────────
  ai-prometheus:
    image: prom/prometheus:latest
    container_name: ai-prometheus
    profiles: [monitoring]
    <<: [*restart, *logging]
    ports:
      - "9090:9090"
    volumes:
      - prometheus_data:/prometheus  # 수집된 메트릭 데이터 저장 (30일 보존)
      # prometheus.yml: 어떤 서비스에서 메트릭을 수집할지 설정
      - ./configs/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - ./configs/prometheus/alert_rules.yml:/etc/prometheus/alert_rules.yml:ro
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=30d'  # 30일 이전 데이터 자동 삭제
      - '--web.enable-lifecycle'              # API로 설정 리로드 가능
    networks:
      - ai_internal


  #────────────────────────────────────────────────────────────────────────
  #  GRAFANA — 메트릭 시각화 & 알림 대시보드
  #  역할: Prometheus 데이터를 실시간 그래프·경고로 시각화
  #  접속: http://서버IP:3003 → admin / (GRAFANA_ADMIN_PASSWORD)
  #────────────────────────────────────────────────────────────────────────
  ai-grafana:
    image: grafana/grafana:latest
    container_name: ai-grafana
    profiles: [monitoring]
    <<: [*restart, *logging]
    ports:
      - "3003:3000"  # 호스트 3003 (3000은 Open WebUI가 사용 중)
    volumes:
      - grafana_data:/var/lib/grafana  # 대시보드·데이터소스·알림 설정 저장
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:?Grafana 비밀번호 설정 필요}
      - GF_USERS_ALLOW_SIGN_UP=false     # 외부 가입 차단
      - GF_ANALYTICS_REPORTING_ENABLED=false  # 원격 측정 비활성화
    depends_on:
      - ai-prometheus
    networks:
      - ai_internal


  #── GPU 메트릭 수집기 ──────────────────────────────────────────────────
  ai-nvidia-exporter:
    image: utkuozdemir/nvidia_gpu_exporter:1.2.0
    container_name: ai-nvidia-exporter
    profiles: [monitoring]
    <<: [*restart, *logging]
    ports:
      - "9835:9835"  # Prometheus가 이 포트에서 GPU 메트릭 수집
    devices:
      - /dev/nvidiactl            # NVIDIA 제어 장치 (필수)
      - /dev/nvidia0              # GPU 0번. GPU가 여러 개면 nvidia1, nvidia2 추가
    volumes:
      # NVIDIA 관리 라이브러리. 경로가 다르면 find /usr -name "libnvidia-ml.so*"
      - /usr/lib/x86_64-linux-gnu/libnvidia-ml.so.1:/usr/lib/x86_64-linux-gnu/libnvidia-ml.so.1
    networks:
      - ai_internal

  #── 시스템 메트릭 수집기 ───────────────────────────────────────────────
  ai-node-exporter:
    image: prom/node-exporter:latest
    container_name: ai-node-exporter
    profiles: [monitoring]
    <<: [*restart, *logging]
    ports:
      - "9100:9100"
    volumes:
      - /proc:/host/proc:ro  # 프로세스 정보 읽기 전용 마운트
      - /sys:/host/sys:ro    # 시스템 정보 읽기 전용 마운트
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
    network_mode: host  # 호스트 네트워크 직접 접근 (정확한 네트워크 메트릭)

  #── 컨테이너 메트릭 수집기 ─────────────────────────────────────────────
  ai-cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: ai-cadvisor
    profiles: [monitoring]
    <<: [*restart, *logging]
    ports:
      - "8090:8080"
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    networks:
      - ai_internal


##═══════════════════════════════════════════════════════════════════════════
##  📦 볼륨 정의 — 모든 영속 데이터 중앙 관리
##  이름을 명시하면 Docker가 자동으로 ai-server_ 접두사를 제거하고
##  지정한 이름 그대로 볼륨을 생성합니다.
##═══════════════════════════════════════════════════════════════════════════
volumes:
  # Core
  ollama_data:      { name: ai-ollama-data }       # ★★★ AI 모델 파일 (가장 큰 볼륨)
  webui_data:       { name: ai-webui-data }         # ★★★ 사용자·대화·설정
  pipelines_data:   { name: ai-pipelines-data }     # 파이프라인 Python 파일
  searxng_data:     { name: ai-searxng-data }       # 검색엔진 설정
  # Database
  qdrant_data:      { name: ai-qdrant-data }        # ★★★ 벡터 DB (RAG 핵심)
  postgres_data:    { name: ai-postgres-data }      # ★★★ 관계형 DB
  redis_data:       { name: ai-redis-data }         # 캐시 (재생성 가능)
  # Management
  portainer_data:   { name: ai-portainer-data }     # Portainer 설정
  nginx_pm_data:    { name: ai-nginx-pm-data }      # 프록시 규칙·SSL
  nginx_pm_letsencrypt: { name: ai-nginx-pm-le }    # Let's Encrypt 인증서
  uptime_data:      { name: ai-uptime-data }        # 모니터 설정·히스토리
  # Automation
  n8n_data:         { name: ai-n8n-data }           # ★★ 워크플로우 (복구 불가)
  dify_data:        { name: ai-dify-data }          # Dify 앱 설정
  # Imaging
  comfyui_models:   { name: ai-comfyui-models }     # 이미지 생성 모델 파일
  comfyui_output:   { name: ai-comfyui-output }     # 생성된 이미지
  comfyui_nodes:    { name: ai-comfyui-nodes }      # 커스텀 노드
  a1111_models:     { name: ai-a1111-models }       # A1111 모델 파일
  a1111_output:     { name: ai-a1111-output }       # A1111 생성 이미지
  a1111_extensions: { name: ai-a1111-ext }          # A1111 Extensions
  # Voice
  whisper_models:   { name: ai-whisper-models }     # Whisper 모델 캐시
  kokoro_models:    { name: ai-kokoro-models }      # Kokoro TTS 모델
  # Monitoring
  prometheus_data:  { name: ai-prometheus-data }    # 30일치 메트릭 데이터
  grafana_data:     { name: ai-grafana-data }       # 대시보드 설정


##═══════════════════════════════════════════════════════════════════════════
##  🌐 네트워크 정의 — 내부/외부 레이어 분리
##═══════════════════════════════════════════════════════════════════════════
networks:
  # ai_internal: 서비스 간 내부 통신 전용
  # 컨테이너 이름(ai-ollama, ai-postgres 등)이 DNS처럼 동작
  # 이 네트워크의 포트는 외부에서 직접 접근 불가
  ai_internal:
    name: ai-internal-net
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

  # ai_external: Nginx PM이 외부 트래픽을 받아 내부 서비스로 전달하는 경로
  # open-webui, n8n, portainer 등 외부 공개가 필요한 서비스만 포함
  ai_external:
    name: ai-external-net
    driver: bridge
    ipam:
      config:
        - subnet: 172.21.0.0/16
SECTION05

환경변수(.env) 완전 해설

~/ai-server/compose/.env 파일의 모든 변수를 서비스별로 완전하게 설명합니다. 필수는 반드시 변경, 선택은 기본값으로 두어도 됩니다.

🖥️
서버 공통 설정
변수명필수기본값설명
AI_SERVER_IP선택192.168.1.253서버 내부 IP. 스크립트에서 참조용으로 사용
DATA_ROOT선택/home/ubuntu/ai-server/data호스트의 데이터 루트 디렉토리. 모델 파일·생성 결과물 저장 경로
🔐
보안 핵심 키 (절대 노출 금지)
openssl rand -hex 32 로 생성 권장
변수명필수기본값설명
WEBUI_SECRET_KEY필수Open WebUI JWT 서명 키. 32자 이상 랜덤. 변경 시 모든 세션 만료
QDRANT_API_KEY필수Qdrant 접근 인증 키. 없으면 누구나 벡터 DB에 접근 가능
POSTGRES_PASSWORD필수PostgreSQL 슈퍼유저 비밀번호. 설정 후 절대 변경 금지 (마이그레이션 필요)
REDIS_PASSWORD필수Redis 인증 비밀번호
N8N_ENCRYPTION_KEY필수n8n의 API Key·비밀번호 암호화 키. 변경 시 기존 인증정보 복호화 불가
PIPELINE_API_KEY선택any_stringOpen WebUI ↔ Pipelines 서버 인증 키
DIFY_SECRET_KEY선택Dify 사용 시 필수. 32자 랜덤 문자열
🦙
Ollama 설정
변수명필수기본값설명 및 권장값
OLLAMA_NUM_PARALLEL선택2동시 요청 처리 수. 24GB VRAM: 2~4, 12GB: 1~2, 8GB: 1
OLLAMA_MAX_LOADED_MODELS선택2VRAM에 동시 상주할 모델 수. 모델 전환 시 재로딩 시간 단축
OLLAMA_FLASH_ATTENTION선택11: 활성화(RTX 20xx 이상). 0: 비활성화(구형 GPU). VRAM 30% 절약
OLLAMA_KEEP_ALIVE선택30m모델 VRAM 유지 시간. 0=즉시 해제, -1=영구 유지, 30m=30분
💬
Open WebUI 설정
변수명필수기본값설명
WEBUI_NAME선택My Private AI브라우저 탭·로그인 화면에 표시되는 서비스 이름
WEBUI_URL선택http://localhost:3000외부 공개 URL. Google OAuth 사용 시 정확히 일치해야 함
ENABLE_SIGNUP선택true가입 허용 여부. 관리자 계정 생성 후 반드시 false로 변경
DEFAULT_USER_ROLE선택pending신규 가입자 기본 역할. pending(관리자 승인 필요) 권장
RAG_EMBEDDING_MODEL선택BAAI/bge-m3RAG 임베딩 모델. 한국어: BAAI/bge-m3 권장. 경량: nomic-embed-text
CHUNK_SIZE선택1000문서 청킹 크기(토큰). 작으면 정밀↑ 검색 속도↑, 크면 맥락↑
RAG_TOP_K선택5검색 반환 청크 수. 많을수록 맥락 풍부, 프롬프트 토큰↑
ENABLE_RAG_HYBRID_SEARCH선택trueDense+Sparse 하이브리드 검색. Qdrant 사용 시 true 권장
OPENAI_API_KEY선택OpenAI API 키. 없으면 외부 API 사용 불가 (로컬만 사용 가능)
🎙️
음성 AI 설정
변수명필수기본값설명
WHISPER_MODEL선택large-v3-turbotiny/base/small/medium/large-v3/large-v3-turbo. 한국어 권장: medium 이상
WHISPER_LANGUAGE선택ko언어 고정 (한국어 정확도↑). 다국어 서버라면 제거
KOKORO_DEFAULT_VOICE선택af_sarah기본 성우. af_sarah(여성)/am_adam(남성)/af_bella(활기)/am_michael(전문)
🚨
.env 파일 보안 필수 체크사항

① .env 파일을 Git에 절대 커밋하지 마세요 — .gitignore에 추가 필수. ② 파일 권한을 chmod 600 .env로 설정 (소유자만 읽기). ③ 모든 비밀번호는 openssl rand -hex 32로 생성한 랜덤 값 사용. ④ POSTGRES_PASSWORD, N8N_ENCRYPTION_KEY는 설정 후 절대 변경 금지.

SECTION06

볼륨 & 네트워크 완전 해설 — 데이터 저장 구조

📦 볼륨별 저장 내용 & 백업 중요도

ai-ollama-data
서비스: ollama
다운로드한 AI 모델 파일 전체 저장. Qwen 14B 하나가 9GB, Llama 70B는 40GB+. 이 볼륨이 가장 큰 용량을 차지합니다.
⚡ 백업 중요도: ★★★ (모델 재다운로드 시간 수십 분~수 시간)
ai-webui-data
서비스: open-webui
사용자 계정·대화 기록·업로드 파일·Knowledge Base 메타데이터 저장. PostgreSQL과 중복되는 부분도 있지만 파일 첨부는 여기만 저장.
⚡ 백업 중요도: ★★★
ai-qdrant-data
서비스: qdrant
모든 Knowledge Base의 벡터·페이로드 저장. 이 볼륨을 삭제하면 RAG 데이터 전체 소멸. 재구축에 임베딩 재처리 시간이 필요합니다.
⚡ 백업 중요도: ★★★
ai-postgres-data
서비스: postgres
n8n 워크플로우·실행기록, Open WebUI 사용자·설정, Dify 앱 구성이 모두 저장됩니다. WAL(Write-Ahead Log) 포함.
⚡ 백업 중요도: ★★★
ai-n8n-data
서비스: n8n
워크플로우 JSON·실행 기록·인증정보 암호화 데이터. 복구 불가능한 인증정보가 있을 수 있어 중요합니다.
⚡ 백업 중요도: ★★
ai-nginx-pm-letsencrypt
서비스: nginx-pm
Let’s Encrypt SSL 인증서 저장. 손실 시 재발급 가능하지만 Cloudflare에서 일시적 다운타임 발생.
⚡ 백업 중요도: ★★
ai-comfyui-models
서비스: comfyui
FLUX.1·SDXL 등 이미지 생성 모델 파일. 재다운로드 가능하지만 용량이 크고 속도가 느림.
⚡ 백업 중요도: ★
ai-prometheus-data
서비스: prometheus
30일치 메트릭 시계열 데이터. 손실 시 과거 기록만 사라지고 서비스 영향 없음.
⚡ 백업 중요도: ★ (재생성 가능)

🌐 네트워크 레이어 분리 설계

💡
두 네트워크를 분리하는 이유

ai-internal-net: 모든 서비스가 포함. Qdrant, PostgreSQL 같은 DB는 이 네트워크에서만 접근 가능. 외부 인터넷에서 직접 접근 불가. 컨테이너 이름이 DNS처럼 작동해 ai-ollama:11434로 Ollama에 접근합니다. ai-external-net: Nginx PM + 외부 공개 서비스만 포함. Nginx PM이 443포트로 받은 요청을 이 네트워크를 통해 내부 서비스로 전달합니다. DB는 이 네트워크에 포함되지 않아 외부에서 절대 직접 접근 불가합니다.

SECTION07

서비스 기동 · Portainer 설정 · 초기 설정 가이드

bash — 최초 실행 순서 (단계별 실행 권장)
cd ~/ai-server/compose

## ── 1단계: .env 설정 (실행 전 필수!) ─────────────
cp .env.example .env         # 예시 파일 복사
nano .env                    # 비밀번호·키 전부 변경

# 비밀번호 자동 생성 도우미
echo "WEBUI_SECRET_KEY=$(openssl rand -hex 32)"
echo "QDRANT_API_KEY=$(openssl rand -hex 24)"
echo "POSTGRES_PASSWORD=$(openssl rand -hex 16)"
echo "REDIS_PASSWORD=$(openssl rand -hex 16)"
echo "N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)"

## ── 2단계: PostgreSQL 초기화 스크립트 생성 ────────
mkdir -p configs/postgres
cat > configs/postgres/init.sql << 'SQL'
CREATE DATABASE n8n WITH OWNER aiserver ENCODING 'UTF8' TEMPLATE template0;
CREATE DATABASE openwebui WITH OWNER aiserver ENCODING 'UTF8' TEMPLATE template0;
CREATE DATABASE dify WITH OWNER aiserver ENCODING 'UTF8' TEMPLATE template0;
\c aiserver_main;
CREATE EXTENSION IF NOT EXISTS vector;
\c openwebui;
CREATE EXTENSION IF NOT EXISTS vector;
SQL

## ── 3단계: 핵심 서비스 시작 ───────────────────────
# 처음에는 core+database+manage만 시작 (가장 안전한 시작)
docker compose \
  --profile core \
  --profile database \
  --profile manage \
  --env-file .env \
  up -d

## ── 4단계: 기동 상태 모니터링 (모두 healthy 될 때까지) ─
# 약 1~3분 소요. Ctrl+C로 종료
watch -n 3 'docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep ai-'

## ── 5단계: AI 모델 다운로드 ────────────────────────
# 추천 기본 세트 (총 약 20GB)
docker exec -it ai-ollama ollama pull qwen2.5:14b        # 한국어 최강 ~9GB
docker exec -it ai-ollama ollama pull nomic-embed-text   # RAG 임베딩 필수 ~270MB
docker exec -it ai-ollama ollama pull gemma3:12b          # Vision 지원 ~8GB
docker exec -it ai-ollama ollama pull deepseek-r1:8b      # 수학·코딩 ~5GB

# 모델 목록 및 GPU 사용 확인
docker exec -it ai-ollama ollama list
docker exec -it ai-ollama ollama ps

## ── 6단계: Portainer 초기 설정 ─────────────────────
# 브라우저에서 http://서버IP:9000 접속
# 1. 관리자 계정 생성 (최초 5분 내 완료해야 함)
# 2. "Get Started" → "local" 환경 선택
# 3. 모든 컨테이너·볼륨·네트워크가 GUI에서 보임
echo "Portainer: http://$(hostname -I | awk '{print $1}'):9000"

## ── 7단계: Open WebUI 초기 설정 ────────────────────
# 브라우저에서 http://서버IP:3000 접속
# 1. 첫 번째 계정 생성 → 자동으로 Admin 권한 부여
# 2. .env에서 ENABLE_SIGNUP=false 로 변경
# 3. docker compose up -d 로 재시작 (변경사항 적용)
docker compose --profile core --profile database --profile manage up -d

## ── 8단계: 추가 서비스 기동 ────────────────────────
# 자동화 서비스 추가
docker compose --profile automation --env-file .env up -d

# 이미지 생성 (GPU VRAM 여유 있을 때)
docker compose --profile imaging --env-file .env up -d

# 음성 AI
docker compose --profile voice --env-file .env up -d

# 모니터링
docker compose --profile monitoring --env-file .env up -d

## ── 9단계: 전체 상태 확인 ──────────────────────────
# 서비스 상태
docker ps --format "table {{.Names}}\t{{.Status}}" | grep ai-

# 디스크 사용량
docker system df

# GPU 상태
nvidia-smi

📌 Portainer 주요 기능 활용 가이드

작업Portainer 경로CLI 동등 명령
컨테이너 실시간 로그 확인Containers → ai-ollama → Logsdocker logs -f ai-ollama
컨테이너 재시작Containers → 컨테이너 선택 → Restartdocker restart ai-webui
볼륨 크기 확인Volumes → ai-ollama-data → 클릭docker system df -v
컨테이너 내부 접속Containers → 컨테이너 → Consoledocker exec -it ai-ollama bash
환경변수 확인Containers → 컨테이너 → Inspect → Envdocker inspect ai-webui
이미지 업데이트Images → Pull → 이미지명 입력docker compose pull
Compose 스택 전체 보기Stacks → ai-serverdocker compose ps

🎉 글 1 완성! 23개 컨테이너의 역할·필요성·제외 조건을 파악하고, 초주석 통합 Docker Compose로 전체 AI 스택을 단 하나의 파일로 관리합니다. Portainer로 GUI 관리까지 더해졌습니다. 글 2에서는 이 서버를 UFW·Nginx·Cloudflare Tunnel로 안전하게 외부에 공개하는 방법을 다룹니다.

📚 시리즈 다른 글 보기
이전 글
시리즈의 첫 번째 글입니다
다음 글
글 2 — 보안·UFW·Nginx·Cloudflare Tunnel·Tailscale VPN

Leave a reply

Please enter your comment!
Please enter your name here