홈랩 AI 서버 완전 구축 — 5편 마스터 시리즈
글 1 전체 목차
사전 준비 — Ubuntu · NVIDIA 드라이버 · Docker
Compose 파일을 실행하기 전에 반드시 완료해야 하는 3가지 선행 작업입니다. 이미 구성된 서버라면 아래 검증 명령어로 상태만 확인하세요.
✅ 선행 작업 체크리스트
| 단계 | 작업 | 검증 명령어 | 정상 결과 |
|---|---|---|---|
| 1 | Ubuntu 24.04 LTS Server 설치 + 기본 설정 | lsb_release -a | Ubuntu 24.04 출력 |
| 2 | NVIDIA 드라이버 설치 (nouveau 차단 후) | nvidia-smi | GPU 정보 + 드라이버 버전 출력 |
| 3 | Docker CE + Compose v2 설치 | docker compose version | Docker Compose v2.x 출력 |
| 4 | NVIDIA Container Toolkit 설치 + Docker 재시작 | docker run --rm --gpus all nvidia/cuda:12.6.0-base-ubuntu22.04 nvidia-smi | 컨테이너 내부에서 GPU 출력 |
| 5 | daemon.json 설정 (UFW 우회 차단, nvidia runtime 기본) | cat /etc/docker/daemon.json | iptables:false, default-runtime:nvidia 확인 |
🔢 CUDA ↔ 드라이버 최소 요구사항 (GPU별 요약)
| GPU | 최소 드라이버 | 권장 드라이버 | 권장 CUDA | PyTorch |
|---|---|---|---|---|
| RTX 3060/3070/3080/3090 (Ampere) | 450.80+ | 560.x | CUDA 12.4~12.6 | 2.5.x / 2.6.x |
| RTX 4060/4070/4080/4090 (Ada) | 525.60+ | 560.x | CUDA 12.4~12.6 | 2.5.x / 2.6.x |
| RTX 5080/5090 (Blackwell) | 570.x+ | 570.x+ | CUDA 12.8 | 2.6.x+ |
Ollama, ComfyUI, vLLM 등은 CUDA 런타임을 Docker 이미지 안에 포함합니다. 호스트에는 NVIDIA 드라이버 + NVIDIA Container Toolkit만 있으면 됩니다. 호스트에서 직접 Python AI 스크립트를 실행할 때만 CUDA Toolkit 전체가 필요합니다.
전체 컨테이너 한눈에 보기 — 역할·필요성·제외 조건
아래 카드는 이 Compose 파일에 포함된 모든 서비스입니다. 왜 써야 하는지, 언제 빼도 되는지를 먼저 파악하고 자신의 환경에 맞게 선택하세요.
내부 URL:
http://ai-ollama:11434브라우저:
http://서버IP:3000API Key로 접근 제어 필수
브라우저:
http://서버IP:9000docker compose pull && docker compose up -d를 매번 수동으로 실행할 필요가 없습니다.서비스 연동 구조도 — 누가 누구와 통신하는가
아래 구조도는 각 서비스가 어떤 방향으로 데이터를 주고받는지 보여줍니다. 화살표 방향은 요청의 방향(A → B: A가 B를 호출)입니다.
🔀 채팅 흐름 (사용자 질문 → 답변 과정)
🎙️ 음성 흐름 (마이크 → 음성 답변 과정)
🗄️ 데이터 저장 흐름
📊 모니터링 흐름
🌐 네트워크 레이어 분리
| 네트워크 | 목적 | 포함 서비스 | 외부 접근 |
|---|---|---|---|
ai-internal-net | 서비스 간 내부 통신 전용 | 모든 서비스 | ❌ 차단 |
ai-external-net | 외부 공개가 필요한 서비스 | open-webui, n8n, nginx-pm | ✅ Nginx 통해서만 |
| host network | 시스템 메트릭 수집 정확도 | node-exporter만 | ❌ 내부만 |
통합 Docker Compose — 초주석 완전판
모든 서비스를 하나의 파일로 관리합니다. YAML 주석이 각 서비스·설정·포트의 이유를 완전하게 설명하므로 파일 자체가 학습 문서입니다. Docker Compose Profiles로 필요한 서비스만 선택 실행합니다.
##═══════════════════════════════════════════════════════════════════════════ ## 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
환경변수(.env) 완전 해설
~/ai-server/compose/.env 파일의 모든 변수를 서비스별로 완전하게 설명합니다. 필수는 반드시 변경, 선택은 기본값으로 두어도 됩니다.
| 변수명 | 필수 | 기본값 | 설명 |
|---|---|---|---|
| AI_SERVER_IP | 선택 | 192.168.1.253 | 서버 내부 IP. 스크립트에서 참조용으로 사용 |
| DATA_ROOT | 선택 | /home/ubuntu/ai-server/data | 호스트의 데이터 루트 디렉토리. 모델 파일·생성 결과물 저장 경로 |
| 변수명 | 필수 | 기본값 | 설명 |
|---|---|---|---|
| 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_string | Open WebUI ↔ Pipelines 서버 인증 키 |
| DIFY_SECRET_KEY | 선택 | — | Dify 사용 시 필수. 32자 랜덤 문자열 |
| 변수명 | 필수 | 기본값 | 설명 및 권장값 |
|---|---|---|---|
| OLLAMA_NUM_PARALLEL | 선택 | 2 | 동시 요청 처리 수. 24GB VRAM: 2~4, 12GB: 1~2, 8GB: 1 |
| OLLAMA_MAX_LOADED_MODELS | 선택 | 2 | VRAM에 동시 상주할 모델 수. 모델 전환 시 재로딩 시간 단축 |
| OLLAMA_FLASH_ATTENTION | 선택 | 1 | 1: 활성화(RTX 20xx 이상). 0: 비활성화(구형 GPU). VRAM 30% 절약 |
| OLLAMA_KEEP_ALIVE | 선택 | 30m | 모델 VRAM 유지 시간. 0=즉시 해제, -1=영구 유지, 30m=30분 |
| 변수명 | 필수 | 기본값 | 설명 |
|---|---|---|---|
| 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-m3 | RAG 임베딩 모델. 한국어: BAAI/bge-m3 권장. 경량: nomic-embed-text |
| CHUNK_SIZE | 선택 | 1000 | 문서 청킹 크기(토큰). 작으면 정밀↑ 검색 속도↑, 크면 맥락↑ |
| RAG_TOP_K | 선택 | 5 | 검색 반환 청크 수. 많을수록 맥락 풍부, 프롬프트 토큰↑ |
| ENABLE_RAG_HYBRID_SEARCH | 선택 | true | Dense+Sparse 하이브리드 검색. Qdrant 사용 시 true 권장 |
| OPENAI_API_KEY | 선택 | — | OpenAI API 키. 없으면 외부 API 사용 불가 (로컬만 사용 가능) |
| 변수명 | 필수 | 기본값 | 설명 |
|---|---|---|---|
| WHISPER_MODEL | 선택 | large-v3-turbo | tiny/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 파일을 Git에 절대 커밋하지 마세요 — .gitignore에 추가 필수. ② 파일 권한을 chmod 600 .env로 설정 (소유자만 읽기). ③ 모든 비밀번호는 openssl rand -hex 32로 생성한 랜덤 값 사용. ④ POSTGRES_PASSWORD, N8N_ENCRYPTION_KEY는 설정 후 절대 변경 금지.
볼륨 & 네트워크 완전 해설 — 데이터 저장 구조
📦 볼륨별 저장 내용 & 백업 중요도
🌐 네트워크 레이어 분리 설계
ai-internal-net: 모든 서비스가 포함. Qdrant, PostgreSQL 같은 DB는 이 네트워크에서만 접근 가능. 외부 인터넷에서 직접 접근 불가. 컨테이너 이름이 DNS처럼 작동해 ai-ollama:11434로 Ollama에 접근합니다. ai-external-net: Nginx PM + 외부 공개 서비스만 포함. Nginx PM이 443포트로 받은 요청을 이 네트워크를 통해 내부 서비스로 전달합니다. DB는 이 네트워크에 포함되지 않아 외부에서 절대 직접 접근 불가합니다.
서비스 기동 · Portainer 설정 · 초기 설정 가이드
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 → Logs | docker logs -f ai-ollama |
| 컨테이너 재시작 | Containers → 컨테이너 선택 → Restart | docker restart ai-webui |
| 볼륨 크기 확인 | Volumes → ai-ollama-data → 클릭 | docker system df -v |
| 컨테이너 내부 접속 | Containers → 컨테이너 → Console | docker exec -it ai-ollama bash |
| 환경변수 확인 | Containers → 컨테이너 → Inspect → Env | docker inspect ai-webui |
| 이미지 업데이트 | Images → Pull → 이미지명 입력 | docker compose pull |
| Compose 스택 전체 보기 | Stacks → ai-server | docker compose ps |
🎉 글 1 완성! 23개 컨테이너의 역할·필요성·제외 조건을 파악하고, 초주석 통합 Docker Compose로 전체 AI 스택을 단 하나의 파일로 관리합니다. Portainer로 GUI 관리까지 더해졌습니다. 글 2에서는 이 서버를 UFW·Nginx·Cloudflare Tunnel로 안전하게 외부에 공개하는 방법을 다룹니다.
