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 — 검증 완료판 + 확장판
이 섹션은 두 부분으로 나뉩니다. ① 마스터 가이드에서 실제로 구축·트러블슈팅까지 끝낸 7개 서비스(그대로 복붙해서 쓰면 됩니다)와, ② 더 확장하고 싶을 때 참고할 수 있는 추가 서비스 모음(아직 이 시리즈에서 직접 구축·검증하지 않음)입니다.
실제로 구축하면서 가장 안정적이었던 구조는 역할별로 분리하는 것이었습니다.
Ollama → 네이티브 systemd 설치 (GPU 직접 접근, Docker 레이어 없이 최고 성능)
Dify·Firecrawl → 각자 공식 git clone 단독 설치 (업데이트 시 git pull 한 줄로 해결)
나머지 7개 → 바로 아래 통합 compose 하나로 관리
① 검증 완료 — 통합 7서비스 docker-compose.yml
# /mnt/data/docker-compose.yml
# 통합 서비스: Portainer · Watchtower · Open WebUI · ComfyUI · n8n · Meilisearch · Cloudflared
x-logging: &default-logging
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
services:
# ────────────────────────────────────────────────────────────
# Portainer — 컨테이너 관리 Web UI
# ────────────────────────────────────────────────────────────
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: unless-stopped
ports:
- "9000:9000"
- "9443:9443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /mnt/data/00_infra/portainer/data:/data
networks:
- ai-common-net
logging: *default-logging
labels:
# Portainer는 Watchtower 자동 업데이트 제외 (수동 관리)
- "com.centurylinklabs.watchtower.enable=false"
# ────────────────────────────────────────────────────────────
# Watchtower — 컨테이너 자동 업데이트 (매일 새벽 4시)
# ────────────────────────────────────────────────────────────
watchtower:
image: containrrr/watchtower:latest
container_name: watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
# Docker API 버전 명시 (불일치 오류 방지)
- DOCKER_API_VERSION=1.45
# 업데이트 후 구 이미지 자동 삭제
- WATCHTOWER_CLEANUP=true
# 중지된 컨테이너는 업데이트 제외
- WATCHTOWER_INCLUDE_STOPPED=false
# 매일 새벽 4시 실행 (cron 형식)
- WATCHTOWER_SCHEDULE=0 0 4 * * *
- TZ=Asia/Seoul
networks:
- ai-common-net
logging: *default-logging
# ────────────────────────────────────────────────────────────
# Open WebUI — Ollama Web UI (ChatGPT 스타일)
# Ollama는 네이티브 설치 → host.docker.internal:11434 으로 접근
# ────────────────────────────────────────────────────────────
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: unless-stopped
ports:
- "3001:8080"
volumes:
- /mnt/data/01_ai/open-webui/data:/app/backend/data
environment:
# 네이티브 Ollama 접근 (host.docker.internal 사용)
- OLLAMA_BASE_URL=http://host.docker.internal:11434
- WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY}
- DEFAULT_LOCALE=ko-KR
- ENABLE_RAG_WEB_SEARCH=true
- ENABLE_IMAGE_GENERATION=true
# ComfyUI 이미지 생성 연동
- COMFYUI_BASE_URL=http://comfyui:8188
- TZ=Asia/Seoul
# 네이티브 Ollama 접근을 위한 host 추가
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- ai-common-net
logging: *default-logging
labels:
- "com.centurylinklabs.watchtower.enable=true"
# ────────────────────────────────────────────────────────────
# ComfyUI — AI 이미지 생성 (RTX 3060 12GB 최적화)
# ────────────────────────────────────────────────────────────
comfyui:
image: yanwk/comfyui-boot:cu128-slim
# cu128: 설치된 CUDA 12.8에 정확히 일치
container_name: comfyui
runtime: nvidia
restart: unless-stopped
ports:
- "8188:8188"
environment:
# ── GPU 설정 ─────────────────────────────────────────────
- NVIDIA_VISIBLE_DEVICES=0
- NVIDIA_DRIVER_CAPABILITIES=compute,utility,video
# RTX 3060 Ampere 아키텍처 명시 (compute 8.6)
- TORCH_CUDA_ARCH_LIST=8.6
# CUDA 모듈 지연 로딩 (시작 2~3초 단축)
- CUDA_MODULE_LOADING=LAZY
# ── ComfyUI 실행 옵션 ────────────────────────────────────
# --cuda-malloc RTX 30xx VRAM 단편화 30% 감소
# --fast 불필요한 안전 검사 제거
# --highvram 12GB: SDXL(6.5GB)·FLUX GGUF Q4(7~8GB) 상주
# --preview-method latent2rgb VAE 없이 빠른 프리뷰
# --cache-none 워크플로우 전환 시 VRAM 즉시 해제 (OOM 방지)
- CLI_ARGS=--listen 0.0.0.0 --port 8188 --cuda-malloc --fast --preview-method latent2rgb --highvram --cache-none
# ── PyTorch 메모리 최적화 (RTX 3060 12GB 기준) ──────────
# gc threshold 0.85: VRAM 85%(10.2GB) 시 GC 실행
# max_split_size 256: 12GB 기준 최적 블록 크기
# expandable_segments: PyTorch 2.x 동적 세그먼트 (단편화 감소)
- PYTORCH_CUDA_ALLOC_CONF=garbage_collection_threshold:0.85,max_split_size_mb:256,expandable_segments:True
- GIT_CONFIG_GLOBAL_SAFE_DIRECTORY=/root/ComfyUI
- TZ=Asia/Seoul
- PYTHONUNBUFFERED=1
volumes:
# yanwk cu128-slim 내부 경로: /root/ComfyUI
- /mnt/data/01_ai/comfyui/models:/root/ComfyUI/models
- /mnt/data/01_ai/comfyui/workflows:/root/ComfyUI/user/default/workflows
- /mnt/data/01_ai/comfyui/output:/root/ComfyUI/output
- /mnt/data/01_ai/comfyui/input:/root/ComfyUI/input
- /mnt/data/01_ai/comfyui/temp:/root/ComfyUI/temp
- /mnt/data/01_ai/comfyui/custom_nodes:/root/ComfyUI/custom_nodes
- /mnt/data/01_ai/comfyui/user:/root/ComfyUI/user
# pip·HuggingFace 캐시 영속화 (재생성 시 재다운로드 방지)
- /mnt/data/01_ai/comfyui/dot-cache:/root/.cache
deploy:
resources:
reservations:
devices:
- driver: nvidia
device_ids: ['0'] # --gpus all 보다 명시적
capabilities: [gpu]
limits:
cpus: '16.0'
memory: 64G
# R730 128GB 기준: Wan 2.2 오프로딩 + FLUX GGUF 처리 여유
# Wan 2.2 비디오 생성 시 공유 메모리 OOM 방지
shm_size: '16g'
ulimits:
memlock:
soft: -1
hard: -1 # GPU 메모리 전송 최적화
nofile:
soft: 65536
hard: 65536 # 대량 모델 파일 처리
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:8188/system_stats > /dev/null || exit 1"]
interval: 30s
timeout: 15s
retries: 5
# 첫 실행: Manager 설치 + custom_nodes 의존성 설치 여유
start_period: 180s
networks:
- ai-common-net
logging: *default-logging
labels:
# ComfyUI는 자동 업데이트 제외 (워크플로우 호환성 관리)
- "com.centurylinklabs.watchtower.enable=false"
# ────────────────────────────────────────────────────────────
# n8n-postgres — n8n 전용 DB (SQLite 기본값 대신 Postgres 권장)
# 동시 워크플로우 실행이 많아지면 SQLite는 "database is locked" 발생 가능
# ────────────────────────────────────────────────────────────
n8n-postgres:
image: postgres:15-alpine
container_name: n8n-pg-db
restart: unless-stopped
ports:
- "127.0.0.1:15434:5432" # 외부 접속용, 로컬호스트로만 제한 (Dify 15432·Firecrawl 15433과 겹치지 않음)
volumes:
- /mnt/data/02_automation/n8n-postgres/data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=${N8N_DB_PASSWORD}
- POSTGRES_DB=n8n
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n"]
interval: 10s
timeout: 5s
retries: 5
networks:
- ai-common-net
logging: *default-logging
labels:
- "com.centurylinklabs.watchtower.enable=true"
# ────────────────────────────────────────────────────────────
# n8n — 워크플로우 자동화
# ────────────────────────────────────────────────────────────
n8n:
image: n8nio/n8n:latest
container_name: n8n
restart: unless-stopped
depends_on:
n8n-postgres:
condition: service_healthy
ports:
- "5678:5678"
volumes:
# n8n은 내부에서 node 유저(uid:1000)로 실행
# → 사전에 chown 1000:1000 필요 (Section 05 참조)
- /mnt/data/02_automation/n8n/data:/home/node/.n8n
environment:
# 최초 생성 후 절대 변경 금지 (변경 시 모든 자격증명 무효화)
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- N8N_HOST=${HOST_IP}
- N8N_PORT=5678
- N8N_PROTOCOL=http
- WEBHOOK_URL=http://${HOST_IP}:5678
- GENERIC_TIMEZONE=Asia/Seoul
- N8N_LOG_LEVEL=info
# 실행 이력 자동 정리 (7일 보존)
- EXECUTIONS_DATA_PRUNE=true
- EXECUTIONS_DATA_MAX_AGE=168
# ── DB: Postgres 사용 (서비스명 n8n-postgres로 접근, container_name 아님) ──
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=n8n-postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${N8N_DB_PASSWORD}
# N8N_RUNNERS_ENABLED는 최신 버전에서 deprecated (기본 내장되어 더 이상 불필요)
# 네이티브 Ollama, NAS SSH 등 호스트 서비스 접근
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- ai-common-net
logging: *default-logging
labels:
- "com.centurylinklabs.watchtower.enable=true"
# ────────────────────────────────────────────────────────────
# Meilisearch — 고속 검색 엔진
# ────────────────────────────────────────────────────────────
meilisearch:
image: getmeili/meilisearch:latest
container_name: meilisearch
restart: unless-stopped
ports:
- "7700:7700"
volumes:
# Meilisearch는 uid 1000으로 실행
# → 사전에 chown 1000:1000 필요 (Section 05 참조)
- /mnt/data/02_automation/meilisearch/data:/meili_data
environment:
- MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
- MEILI_ENV=production
- TZ=Asia/Seoul
networks:
- ai-common-net
logging: *default-logging
labels:
- "com.centurylinklabs.watchtower.enable=true"
# ────────────────────────────────────────────────────────────
# Cloudflared — 공인 IP 없이 외부 안전 접속
# network_mode: host → 내부 서비스에 직접 접근 가능
# ────────────────────────────────────────────────────────────
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
restart: unless-stopped
network_mode: host
command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TOKEN}
environment:
- TZ=Asia/Seoul
logging: *default-logging
labels:
- "com.centurylinklabs.watchtower.enable=false"
# ────────────────────────────────────────────────────────────────
# 네트워크 정의 — Dify·Firecrawl이 external로 참조
# ────────────────────────────────────────────────────────────────
networks:
ai-common-net:
name: ai-common-net
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16Ollama 네이티브 설치, Dify·Firecrawl 공식 단독 설치 방법은 분량상 별도로 정리되어 있습니다 — 4편(n8n·Dify 자동화)에서 Dify 설치·연동 전 과정을 실제 트러블슈팅까지 포함해 다룹니다.
② ⚠️ 검증 전 확장 서비스 — 직접 구축·테스트하지 않음
범용 DB(Qdrant·Postgres·Redis 단독), Pipelines, SearXNG, Nginx Proxy Manager, Uptime Kuma, A1111, Whisper, Kokoro TTS, Prometheus·Grafana·Exporter 3종 — 총 15개 서비스입니다. 별도 파일이 아니라 ①번의 기존 docker-compose.yml에 그대로 추가하는 방식입니다.
# 아래 서비스 블록들을 기존 /mnt/data/docker-compose.yml의 # services: 섹션 안에 그대로 추가하세요 (새 파일 만들지 않음) pipelines: image: ghcr.io/open-webui/pipelines:main container_name: 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-common-net #──────────────────────────────────────────────────────────────────────── # SEARXNG — 프라이버시 보호 웹 검색 엔진 # ───────────────────────────────────────── # 역할: AI가 실시간 인터넷 정보를 검색할 때 사용하는 백엔드 # 왜 쓰는가: Google/Bing API 키 없이 무료 웹 검색. 사용자 IP 보호 # 제외 조건: 웹 검색 기능이 불필요하거나 외부 검색 API 사용 시 #──────────────────────────────────────────────────────────────────────── searxng: image: searxng/searxng:latest container_name: 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://searxng:8080 - SEARXNG_SECRET_KEY=${WEBUI_SECRET_KEY} # CSRF 보호용 비밀키 networks: - ai-common-net ##═══════════════════════════════════════════════════════════════════════════ ## 🗄️ DATABASE 프로파일 — 데이터 레이어 ## core 프로파일과 함께 항상 실행하는 것이 권장입니다. ##═══════════════════════════════════════════════════════════════════════════ #──────────────────────────────────────────────────────────────────────── # QDRANT — 벡터 데이터베이스 (RAG의 핵심) # ───────────────────────────────────────── # 역할: 문서를 임베딩 벡터로 저장하고 의미 기반 유사도 검색 제공 # 왜 쓰는가: # - SQL DB는 키워드 매칭, Qdrant는 의미(Semantics) 검색 # - "Docker 네트워크 설정"을 물어도 "컨테이너 통신 방법" 문서를 찾음 # - HNSW 인덱스로 수백만 건도 밀리초 검색 # - Hybrid 검색(의미+키워드 결합)으로 정확도 20~40% 향상 # 제외 조건: RAG 기능 완전 불필요 시 (ChromaDB로 대체 가능) #──────────────────────────────────────────────────────────────────────── qdrant: image: qdrant/qdrant:latest container_name: 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-common-net #──────────────────────────────────────────────────────────────────────── # POSTGRESQL — 관계형 DB (n8n·Open WebUI·Dify 공유) # ───────────────────────────────────────── # 역할: 워크플로우·사용자·설정 등 구조화된 메타데이터 저장 # 왜 쓰는가: # - 한 인스턴스로 여러 서비스 DB를 관리 (자원 절약) # - init.sql로 n8n/openwebui/dify DB를 자동 생성 # - pgvector 확장으로 벡터 검색도 지원 (Qdrant 보조용) # 제외 조건: n8n·Dify 미사용 + Open WebUI SQLite 모드일 때 #──────────────────────────────────────────────────────────────────────── postgres: image: pgvector/pgvector:pg16 # pgvector 확장 포함 공식 이미지 container_name: 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-common-net #──────────────────────────────────────────────────────────────────────── # REDIS — 인메모리 캐시 & 메시지 큐 # ───────────────────────────────────────── # 역할: Dify의 Celery 비동기 태스크 처리 큐 # 왜 쓰는가: # - 임베딩 처리·파이프라인 실행을 백그라운드에서 비동기 처리 # - 매우 낮은 자원 사용 (~50MB RAM) # 제외 조건: Dify를 사용하지 않을 경우 제거 가능 #──────────────────────────────────────────────────────────────────────── redis: image: redis:7-alpine container_name: 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-common-net ##═══════════════════════════════════════════════════════════════════════════ ## 🛠️ MANAGE 프로파일 — 서버 관리 도구 ## 운영 효율성을 높이는 도구 모음. 핵심 서비스와 독립적으로 실행 가능. ##═══════════════════════════════════════════════════════════════════════════ #──────────────────────────────────────────────────────────────────────── # PORTAINER CE — Docker 관리 GUI (강력 권장!) # ───────────────────────────────────────── # 역할: 브라우저에서 모든 Docker 컨테이너·볼륨·이미지를 GUI로 관리 # 왜 쓰는가: # - "docker ps, docker logs, docker exec" 명령을 클릭으로 대체 # - 팀원이 터미널 없이 컨테이너 상태·로그·재시작 가능 # - Compose 스택을 시각적으로 관리 # - 볼륨 용량 확인, 이미지 정리, 네트워크 확인 모두 UI에서 # - 무료(CE) 버전으로 홈랩·팀 운영 충분 # 제외 조건: CLI만으로 관리할 경우 제거 가능 (개인 고급 사용자) # 접속: http://서버IP:9000 → 최초 접속 시 admin 계정 생성 #──────────────────────────────────────────────────────────────────────── nginx-pm: image: jc21/nginx-proxy-manager:latest container_name: 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-common-net - ai_external #──────────────────────────────────────────────────────────────────────── # WATCHTOWER — Docker 이미지 자동 업데이트 데몬 # ───────────────────────────────────────── # 역할: 지정한 주기마다 새 이미지를 확인하고 자동으로 컨테이너를 업데이트 # 왜 쓰는가: # - Ollama, Open WebUI 등 AI 도구는 주 1~2회 업데이트 # - 수동으로 docker compose pull && up -d 를 매번 실행할 필요 없음 # - 업데이트 후 Slack/이메일 알림 발송 가능 # 제외 조건: 버전을 수동으로 고정 관리할 때. 또는 프로덕션에서 # 자동 업데이트가 위험한 경우 (WATCHTOWER_LABEL_ENABLE로 선택 제어) #──────────────────────────────────────────────────────────────────────── uptime: image: louislam/uptime-kuma:latest container_name: uptime profiles: [manage] <<: [*restart, *logging] ports: - "3005:3001" # 호스트 3005 (3001은 Open WebUI가 이미 사용 중 — 충돌 발견) volumes: # uptime_data: 모니터 설정, 히스토리, 알림 설정 저장 - uptime_data:/app/data networks: - ai-common-net - ai_external ##═══════════════════════════════════════════════════════════════════════════ ## 🤖 AUTOMATION 프로파일 — 자동화 & AI 앱 빌더 ##═══════════════════════════════════════════════════════════════════════════ #──────────────────────────────────────────────────────────────────────── # N8N — 노코드 AI 워크플로우 자동화 플랫폼 # ───────────────────────────────────────── # 역할: 이메일·슬랙·GitHub 등 500개+ 서비스를 AI와 자동 연결 # 왜 쓰는가: # - 이메일 수신 → Ollama 요약 → 슬랙 전달 (코드 없이 드래그 앤 드롭) # - 블로그 RSS 변경 → AI 번역·요약 → 자동 게시 # - GitHub PR 생성 → AI 코드 리뷰 → 자동 코멘트 # 제외 조건: 자동화가 전혀 불필요하거나 Make/Zapier 사용 시 #──────────────────────────────────────────────────────────────────────── a1111: image: universalml/stable-diffusion-webui:latest container_name: 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-common-net ##═══════════════════════════════════════════════════════════════════════════ ## 🎙️ VOICE 프로파일 — 음성 AI 서비스 ##═══════════════════════════════════════════════════════════════════════════ #──────────────────────────────────────────────────────────────────────── # FASTER-WHISPER — 고성능 음성 인식 서버 (STT) # ───────────────────────────────────────── # 역할: 마이크 음성을 텍스트로 변환. OpenAI Whisper API 형식 호환 # 왜 쓰는가: # - OpenAI Whisper API($0.006/분) 없이 무제한 무료 STT # - original Whisper보다 4배 빠르고 VRAM 절반만 사용 # - 한국어 포함 99개 언어 지원 # - 기존 OpenAI Whisper API 코드를 URL만 변경으로 교체 가능 # 제외 조건: 음성 입력 기능이 불필요할 때 #──────────────────────────────────────────────────────────────────────── whisper: image: fedirz/faster-whisper-server:latest-cuda container_name: 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-common-net #──────────────────────────────────────────────────────────────────────── # KOKORO TTS — 고품질 음성 합성 서버 # ───────────────────────────────────────── # 역할: AI 텍스트 답변을 자연스러운 음성으로 변환 (TTS) # 왜 쓰는가: # - ElevenLabs($22/월) 없이 무제한 무료 TTS # - 82M 파라미터 초경량이지만 ElevenLabs 수준의 자연스러운 음성 # - CPU만으로도 실시간 스트리밍 가능 (GPU 불필요) # - OpenAI TTS API 형식 호환 (api.openai.com 주소만 변경) # 제외 조건: 음성 출력이 불필요하거나 텍스트만 보는 경우 #──────────────────────────────────────────────────────────────────────── kokoro: image: ghcr.io/remsky/kokoro-fastapi-cpu:latest # CPU 버전 (GPU 버전도 있음) container_name: 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-common-net ##═══════════════════════════════════════════════════════════════════════════ ## 📊 MONITORING 프로파일 — 관측 가능성 스택 ##═══════════════════════════════════════════════════════════════════════════ #──────────────────────────────────────────────────────────────────────── # PROMETHEUS — 메트릭 수집 & 시계열 DB # 역할: GPU·CPU·RAM·컨테이너의 모든 수치를 시계열로 저장 # 제외 조건: Grafana 없이 Uptime Kuma만으로 모니터링할 경우 #──────────────────────────────────────────────────────────────────────── prometheus: image: prom/prometheus:latest container_name: 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-common-net #──────────────────────────────────────────────────────────────────────── # GRAFANA — 메트릭 시각화 & 알림 대시보드 # 역할: Prometheus 데이터를 실시간 그래프·경고로 시각화 # 접속: http://서버IP:3003 → admin / (GRAFANA_ADMIN_PASSWORD) #──────────────────────────────────────────────────────────────────────── grafana: image: grafana/grafana:latest container_name: grafana profiles: [monitoring] <<: [*restart, *logging] ports: - "3004:3000" # 호스트 3004 (3003은 Firecrawl이 이미 사용 중 — 충돌 발견) 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: - prometheus networks: - ai-common-net #── GPU 메트릭 수집기 ────────────────────────────────────────────────── nvidia-exporter: image: utkuozdemir/nvidia_gpu_exporter:1.2.0 container_name: 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-common-net #── 시스템 메트릭 수집기 ─────────────────────────────────────────────── node-exporter: image: prom/node-exporter:latest container_name: 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 # 호스트 네트워크 직접 접근 (정확한 네트워크 메트릭) #── 컨테이너 메트릭 수집기 ───────────────────────────────────────────── cadvisor: image: gcr.io/cadvisor/cadvisor:latest container_name: cadvisor profiles: [monitoring] <<: [*restart, *logging] ports: - "8090:8092" # 호스트 8090 → 컨테이너 내부 8092 (SearXNG가 내부 8080 사용 중 — 충돌 방지) command: - '--port=8092' # ★ 컨테이너 내부 리스닝 포트를 8092로 강제 지정 volumes: - /:/rootfs:ro - /var/run:/var/run:ro - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro networks: - ai-common-net ##═══════════════════════════════════════════════════════════════════════════ ## 📦 볼륨 정의 — 모든 영속 데이터 중앙 관리 ## 이름을 명시하면 Docker가 자동으로 ai-server_ 접두사를 제거하고 ## 지정한 이름 그대로 볼륨을 생성합니다. ##═══════════════════════════════════════════════════════════════════════════ # ↑ networks: ai-common-net 은 기존 docker-compose.yml에 이미 있으니 새로 안 만들어도 됩니다. # ↓ 아래 볼륨들만 기존 volumes: 섹션 맨 아래에 추가하세요. pipelines_data: searxng_data: qdrant_data: postgres_data: redis_data: nginx_pm_data: nginx_pm_letsencrypt: uptime_data: a1111_models: a1111_output: a1111_extensions: whisper_models: kokoro_models: prometheus_data: grafana_data:
# 아래 서비스 블록들을 기존 /mnt/data//mnt/data/02_automation/settings.yml의
# services: 섹션 안에 그대로 추가하세요 (새 파일 만들어서 넣기)
# Read the documentation before extending the defaults:
# https://docs.searxng.org/admin/settings/
use_default_settings: true
# ────────────────────────────────────────────────────────────────────────
# SearXNG 핵심 설정 가이드 (AI 백엔드 최적화)
# ────────────────────────────────────────────────────────────────────────
server:
# Open WebUI 등 내부 서비스에서 접근할 포트
port: 8080
bind_address: "0.0.0.0"
# Docker Compose의 환경 변수나 고유한 랜덤 문자열을 입력 (CSRF 보호용)
secret_key: "my-server-secret-key" # 되도록 Docker-compose.yml 내에 사용 중인 key 사용, Docker Compose가 우선순위 높음
search:
safe_search: 1 # 세이프 서치 (0: 끔, 1: 보통, 2: 엄격)
autocomplete: "duckduckgo" # 검색어 자동 완성 공급자
ban_time_on_fail: 5 # 엔진 실패 시 차단 시간(초)
max_ban_time_on_fail: 120 # 최대 차단 시간(초)
# 사용할 검색 엔진 설정 (AI가 참조할 핵심 데이터 소스)
engines:
- name: google
engine: google
shortcut: google
categories: general
timeout: 3.0
disabled: false # 구글 검색 활성화 (자주 막히면 트래픽 조절 필요)
- name: duckduckgo
engine: duckduckgo
shortcut: ddg
categories: general
timeout: 2.0
disabled: false # 구글 백업용 무료 검색엔진
- name: bing
engine: bing
shortcut: bi
categories: general
timeout: 2.0
disabled: false # 빙 검색 활성화
- name: wikipedia
engine: wikipedia
shortcut: wp
categories: general
timeout: 1.5
disabled: false # 지식 베이스 보완용
# UI 및 프라이버시 설정
ui:
static_use_hash: true
default_locale: "ko" # 기본 언어 설정 (한국어)
# 아웃코잉 프록시 (필요 시 주석 해제하여 사용 - 구글 밴 방지용)
# outgoing:
# request_timeout: 5.0
# 권한변경 후 searxng 재실행
# 모든 사용자가 읽을 수 있도록 권한 부여
chmod 644 searxng/settings.yml
# 컨테이너 실행
docker compose up -d searxng
환경변수(.env) 완전 해설
Portainer·Watchtower·Open WebUI·ComfyUI·n8n·Meilisearch·Cloudflared의 실제 검증된 .env 설정은 ①번 섹션의 마스터 가이드 compose에 맞춰 작성되어 있습니다. 아래 표는 그 외 ⚠️ 검증 전 확장 서비스(범용DB·Pipelines·SearXNG·Nginx PM·Uptime·A1111·Whisper·Kokoro·모니터링)용 참고 자료입니다.
~/ai-server/compose/.env 파일의 모든 변수를 서비스별로 완전하게 설명합니다. 필수는 반드시 변경, 선택은 기본값으로 두어도 됩니다.
| 변수명 | 필수 | 기본값 | 설명 |
|---|---|---|---|
| AI_SERVER_IP | 선택 | 192.168.1.100 | 서버 내부 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는 설정 후 절대 변경 금지.
볼륨 & 네트워크 완전 해설 — 데이터 저장 구조
Ollama는 네이티브 설치라 Docker 볼륨이 아니라 /mnt/data/01_ai/ollama/models 같은 호스트 경로를 직접 씁니다. 아래 ai-ollama-data 등 일부 카드는 ⚠️ 검증 전 구조(통합 compose에 Ollama를 넣는 방식) 기준입니다 — 실제 적용 시 ①번 섹션의 마스터 가이드 구조를 따르세요.
📦 검증 7서비스 — 실제 호스트:컨테이너 경로 매칭표
"이 볼륨이 실제로 어디 저장되는 거지?"가 가장 헷갈리는 부분입니다. 마스터 가이드는 전부 호스트 경로를 직접 마운트(bind mount)하는 방식이라 — Docker가 알아서 관리하는 이름 모를 볼륨이 아니라 — /mnt/data/... 아래 실제 폴더를 그대로 열어보면 데이터가 보입니다.
| 서비스 | 호스트 경로 (실제 파일 위치) | 컨테이너 내부 경로 | 백업 중요도 |
|---|---|---|---|
| Ollama (네이티브, Docker 아님) | /mnt/data/01_ai/ollama/models | — (호스트에 직접 설치) | ★★★ 모델 재다운로드 수십분~수시간 |
| Portainer | /mnt/data/00_infra/portainer/data | /data | ★ (설정만, 재구성 쉬움) |
| Watchtower | /var/run/docker.sock | /var/run/docker.sock | — (영속 데이터 없음, 소켓 공유만) |
| Open WebUI | /mnt/data/01_ai/open-webui/data | /app/backend/data | ★★★ 대화기록·계정·첨부파일 |
| ComfyUI (8개 경로로 세분화) | /mnt/data/01_ai/comfyui/models | /root/ComfyUI/models | ★ models만 큼(재다운로드 가능), workflows는 ★★ |
/mnt/data/01_ai/comfyui/workflows | /root/ComfyUI/user/default/workflows | ||
/mnt/data/01_ai/comfyui/output | /root/ComfyUI/output | ||
/mnt/data/01_ai/comfyui/input | /root/ComfyUI/input | ||
/mnt/data/01_ai/comfyui/temp | /root/ComfyUI/temp | ||
/mnt/data/01_ai/comfyui/custom_nodes | /root/ComfyUI/custom_nodes | ||
/mnt/data/01_ai/comfyui/user | /root/ComfyUI/user | ||
| n8n | /mnt/data/02_automation/n8n/data | /home/node/.n8n | ★★★ 워크플로우·자격증명 암호화 데이터 |
| n8n-postgres | /mnt/data/02_automation/n8n-postgres/data | /var/lib/postgresql/data | ★★★ n8n 실행기록·DB |
| Meilisearch | /mnt/data/02_automation/meilisearch/data | /meili_data | ★★ 검색 인덱스 |
| Cloudflared | 영속 볼륨 없음 — 토큰(CLOUDFLARE_TOKEN)만으로 동작 | — | |
Dify는 /mnt/data/01_ai/dify/docker/volumes/ 아래, Firecrawl은 /mnt/data/02_automation/firecrawl/ 아래에 각자의 공식 compose가 알아서 관리합니다 — 둘 다 공식 git clone 설치라 이 통합 compose와는 별개입니다.
📦 (검증 전) 확장 서비스 볼륨 — Docker 네임드 볼륨 방식
docker volume inspect [볼륨명]으로 확인해야 합니다 (보통 /var/lib/docker/volumes/... 아래).🌐 네트워크 레이어 분리 설계
ai-internal-net: 모든 서비스가 포함. Qdrant, PostgreSQL 같은 DB는 이 네트워크에서만 접근 가능. 외부 인터넷에서 직접 접근 불가. 컨테이너 이름이 DNS처럼 작동해 ai-ollama:11434로 Ollama에 접근합니다. ai-external-net: Nginx PM + 외부 공개 서비스만 포함. Nginx PM이 443포트로 받은 요청을 이 네트워크를 통해 내부 서비스로 전달합니다. DB는 이 네트워크에 포함되지 않아 외부에서 절대 직접 접근 불가합니다.
서비스 기동 · Portainer 설정 · 초기 설정 가이드
아래는 ⚠️ 검증 전 통합 compose(Ollama·Dify 포함) 기준 절차입니다. 실제 검증된 순서는: ① Ollama 네이티브 설치 → ② 통합 7서비스 compose 실행 → ③ Dify 공식 설치 → ④ Firecrawl 공식 설치입니다.
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로 안전하게 외부에 공개하는 방법을 다룹니다.
양자화 완전 이론 — 정밀도 vs 품질 트레이드오프
AI 모델의 가중치는 원래 32비트 부동소수점(FP32)으로 저장됩니다. 이를 더 낮은 비트 수로 변환하는 것이 양자화(Quantization)입니다. Llama 3.3 70B 모델을 예로 들면 FP32로는 280GB, FP16으로는 140GB, INT4로는 35GB가 필요합니다. RTX 3090(24GB VRAM) 한 장으로 70B를 구동하려면 반드시 강력한 양자화가 필요합니다.
| 정밀도 | 비트 | 70B VRAM | 품질 손실 | 속도 | 사용 시나리오 |
|---|---|---|---|---|---|
| FP32 | 32bit | 280GB | 🟢 없음 (기준) | 🔴 최저 | 학습(Training)에만 사용 |
| BF16 | 16bit | 140GB | 🟢 거의 없음 | 🟡 보통 | A100/H100 학습·파인튜닝 |
| FP16 | 16bit | 140GB | 🟢 거의 없음 | 🟡 보통 | vLLM 추론 기본값 |
| INT8 (GPTQ) | 8bit | 70GB | 🟡 미미 | 🟢 빠름 | 품질 우선 양자화 |
| Q5_K_M (GGUF) | ~5bit | ~48GB | 🟡 미미 | 🟢 빠름 | 멀티 GPU 추천 |
| Q4_K_M (GGUF) | ~4bit | ~42GB | 🟡 소폭 | 🟢 빠름 | 단일 24GB VRAM 추천 |
| Q3_K_M (GGUF) | ~3bit | ~32GB | 🔴 눈에 띔 | 🟢 매우 빠름 | VRAM 극한 절약 (품질 희생) |
GGUF는 llama.cpp 포맷으로 CPU+GPU 혼합 추론이 가능합니다. VRAM이 부족할 때 일부 레이어를 RAM에서 처리합니다. GPTQ는 GPU 전용이며 INT4/INT8 정밀도로 속도가 빠릅니다. AWQ는 최신 양자화 방법으로 같은 INT4라도 GPTQ보다 품질이 더 좋습니다. 결론: GPU VRAM이 충분하면 AWQ > GPTQ, VRAM이 부족해 CPU 메모리도 활용해야 하면 GGUF를 선택하세요.
vLLM — OpenAI 호환 고성능 추론 서버 ⚠️ 검증 전
vLLM은 UC Berkeley에서 개발한 LLM 추론 엔진으로, PagedAttention이라는 혁신적인 메모리 관리 기법으로 Ollama 대비 최대 24배 높은 처리량을 달성합니다. 특히 여러 사용자가 동시에 요청할 때의 차이가 극적입니다. OpenAI API와 완전히 호환되어 기존 코드를 변경 없이 그대로 사용할 수 있습니다.
기존 LLM 추론은 각 요청마다 고정된 KV Cache 메모리를 미리 할당합니다. 요청이 10개이면 최대 길이 기준으로 10배 메모리를 낭비합니다. PagedAttention은 OS의 가상 메모리처럼 KV Cache를 페이지 단위로 동적 할당합니다. 실제 사용하는 만큼만 VRAM을 씁니다. 결과적으로 같은 VRAM에서 훨씬 많은 동시 요청을 처리하고, Continuous Batching으로 요청이 완료된 슬롯에 즉시 새 요청을 채웁니다.
| 항목 | Ollama | vLLM |
|---|---|---|
| 동시 사용자 처리 | 순차 처리 (기다림) | Continuous Batching (즉시 처리) |
| 처리량 (Throughput) | 기준 1x | 최대 24x |
| API 호환성 | Ollama 전용 + OpenAI 호환 | 완전 OpenAI 호환 |
| 모델 지원 | GGUF 중심 | HuggingFace 모든 모델 |
| LoRA 어댑터 | 기본 지원 | 동적 LoRA 로딩 지원 |
| 추천 상황 | 개인 사용, 소규모 팀 | 멀티유저, 프로덕션 서비스 |
services:
vllm:
image: vllm/vllm-openai:latest
container_name: vllm_server
restart: unless-stopped
ports:
- "8001:8000" # Ollama 8000과 충돌 방지
volumes:
- huggingface_cache:/root/.cache/huggingface
environment:
- HUGGING_FACE_HUB_TOKEN=your_hf_token # 비공개 모델 접근용
command: >
--model Qwen/Qwen2.5-14B-Instruct
--served-model-name qwen2.5-14b
--api-key your_vllm_api_key
--max-model-len 16384
--gpu-memory-utilization 0.90
--tensor-parallel-size 1 # GPU 수 (멀티 GPU 시 2, 4 등)
--enable-chunked-prefill
--enable-prefix-caching # 시스템 프롬프트 캐싱 (성능↑)
--max-num-seqs 256 # 최대 동시 시퀀스 수
--host 0.0.0.0
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
shm_size: '16gb' # PagedAttention 공유 메모리
networks:
- ai_net
volumes:
huggingface_cache:
networks:
ai_net:
external: true
name: ai_networkfrom openai import OpenAI # vLLM을 OpenAI 클라이언트로 그대로 사용! client = OpenAI( api_key="your_vllm_api_key", base_url="http://192.168.1.100:8001/v1" ) ## 스트리밍 응답 stream = client.chat.completions.create( model="qwen2.5-14b", messages=[{"role": "user", "content": "AI 서버 최적화 방법을 알려줘"}], max_tokens=500, temperature=0.7, stream=True ) for chunk in stream: if chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="", flush=True) ## 배치 처리 (여러 요청 동시) import asyncio from openai import AsyncOpenAI async_client = AsyncOpenAI( api_key="your_vllm_api_key", base_url="http://192.168.1.100:8001/v1" ) async def batch_inference(prompts: list[str]) -> list[str]: """여러 프롬프트를 동시에 처리 (vLLM의 진가)""" tasks = [ async_client.chat.completions.create( model="qwen2.5-14b", messages=[{"role": "user", "content": p}], max_tokens=300, ) for p in prompts ] results = await asyncio.gather(*tasks) return [r.choices[0].message.content for r in results] # 100개 요청 동시 처리 (Ollama는 순차, vLLM은 병렬) prompts = [f"질문 {i}: AI 서버 구축 팁을 한 줄로" for i in range(100)] answers = asyncio.run(batch_inference(prompts)) print(f"✅ {len(answers)}개 응답 동시 완료")
Ollama(포트 11434)는 Open WebUI 연동·개인 채팅용, vLLM(포트 8001)은 n8n·자동화·배치 처리·API 서비스용으로 역할을 분리하면 최적의 성능을 발휘합니다. Open WebUI에서는 두 백엔드를 동시에 등록해 모델별로 선택할 수 있습니다.
GGUF 양자화 실전 — llama.cpp 서버 ⚠️ 검증 전
HuggingFace에서 내려받은 원본 모델을 직접 GGUF로 양자화하면, 공개된 양자화 버전이 없는 최신 모델도 즉시 사용할 수 있습니다. 또한 정밀도를 직접 선택해 VRAM과 품질을 최적으로 균형 잡을 수 있습니다.
# llama.cpp GPU 지원 빌드 git clone https://github.com/ggerganov/llama.cpp cd llama.cpp cmake -B build -DLLAMA_CUDA=ON -DCUDA_ARCHITECTURES=89 # RTX 4090: 89, RTX 3090: 86 cmake --build build --config Release -j$(nproc) # HuggingFace에서 원본 모델 다운로드 pip install huggingface_hub huggingface-cli download Qwen/Qwen2.5-14B-Instruct \ --local-dir ./models/qwen2.5-14b-fp16 # FP16으로 변환 (중간 단계) python convert_hf_to_gguf.py ./models/qwen2.5-14b-fp16 \ --outtype f16 \ --outfile ./models/qwen2.5-14b-f16.gguf # 다양한 양자화 수준으로 변환 ./build/bin/llama-quantize \ ./models/qwen2.5-14b-f16.gguf \ ./models/qwen2.5-14b-Q4_K_M.gguf \ Q4_K_M # 12GB VRAM: Q4_K_M 추천 ./build/bin/llama-quantize \ ./models/qwen2.5-14b-f16.gguf \ ./models/qwen2.5-14b-Q8_0.gguf \ Q8_0 # 16GB VRAM: Q8_0 고품질 # 양자화 품질 벤치마크 (perplexity로 품질 측정) ./build/bin/llama-perplexity \ -m ./models/qwen2.5-14b-Q4_K_M.gguf \ -f wikitext-2-raw/wiki.test.raw # llama.cpp 서버 실행 (OpenAI 호환) ./build/bin/llama-server \ --model ./models/qwen2.5-14b-Q4_K_M.gguf \ --host 0.0.0.0 \ --port 8002 \ --n-gpu-layers 48 \ # GPU로 올릴 레이어 수 (많을수록 빠름) --ctx-size 8192 \ --n-parallel 4 \ # 동시 처리 요청 수 --flash-attn # Flash Attention 활성화
💾 VRAM별 최적 GGUF 양자화 전략
| VRAM | 14B 모델 | 27B 모델 | 70B 모델 | n-gpu-layers |
|---|---|---|---|---|
| 8GB | Q4_K_M (전체) | Q4_K_M 일부만 | 불가 | ~35 |
| 12GB | Q8_0 (고품질) | Q4_K_M (일부) | 불가 | ~48 |
| 16GB | FP16 (원본급) | Q8_0 (전체) | Q4_K_M 일부 | 전체 |
| 24GB | FP16 + 빠름 | FP16 (전체) | Q4_K_M (대부분) | 전체 |
| 48GB (듀얼) | — | FP16 완벽 | Q8_0 (전체) | 전체 |
# Modelfile 생성 (커스텀 파라미터 포함) cat > Modelfile << 'EOF' FROM /path/to/qwen2.5-14b-Q4_K_M.gguf PARAMETER num_ctx 8192 PARAMETER num_gpu 48 PARAMETER num_thread 8 PARAMETER temperature 0.7 PARAMETER top_p 0.9 PARAMETER repeat_penalty 1.1 SYSTEM """당신은 한국어 전문 AI 어시스턴트입니다. 항상 정확하고 도움이 되는 답변을 제공합니다.""" EOF # Ollama에 등록 docker exec -it ollama ollama create qwen2.5-14b-custom -f Modelfile # 등록 확인 docker exec -it ollama ollama list docker exec -it ollama ollama run qwen2.5-14b-custom "안녕하세요"
LoRA 파인튜닝 — Unsloth로 나만의 특화 모델 ⚠️ 검증 전
LoRA(Low-Rank Adaptation)는 원본 모델 가중치를 고정하고 작은 어댑터 레이어만 학습하는 파인튜닝 방법입니다. Unsloth는 LoRA 파인튜닝을 기존 방법보다 2~5배 빠르게, VRAM은 60~70% 절약하면서 수행합니다. RTX 3090(24GB)으로 14B 모델을 2~3시간 만에 특화시킬 수 있습니다.
RAG로도 해결되지 않는 상황에서 파인튜닝을 고려하세요. 특정 도메인 용어를 기본으로 이해해야 할 때(법률, 의학, 특수 산업), 특정 출력 형식을 항상 지켜야 할 때(특정 JSON 스키마, 보고서 양식), 특정 말투·페르소나를 유지해야 할 때(브랜드 챗봇)가 대표적입니다. 단순히 최신 정보 업데이트나 일반 질답은 RAG가 더 효율적입니다.
## 설치: pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git" ## pip install --no-deps trl peft accelerate bitsandbytes from unsloth import FastLanguageModel from trl import SFTTrainer from transformers import TrainingArguments from datasets import Dataset import torch ## ── 1. 모델 로드 (4bit 양자화로 VRAM 절약) ──────── model, tokenizer = FastLanguageModel.from_pretrained( model_name = "Qwen/Qwen2.5-14B-Instruct", max_seq_length = 4096, dtype = None, # 자동 감지 (RTX: BF16) load_in_4bit = True, # 24GB VRAM으로 14B 학습 가능 ) ## ── 2. LoRA 어댑터 설정 ───────────────────────── model = FastLanguageModel.get_peft_model( model, r = 32, # LoRA 랭크. 높을수록 정교하지만 느림. 16~64 권장 target_modules = ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], lora_alpha = 64, # LoRA 스케일. r * 2 권장 lora_dropout = 0.05, bias = "none", use_gradient_checkpointing = "unsloth", # VRAM 40% 추가 절약 random_state = 42, ) ## ── 3. 학습 데이터셋 준비 ────────────────────── # Alpaca 형식 데이터 (instruction, input, output) ALPACA_PROMPT = """Below is an instruction that describes a task. Write a response that appropriately completes the request. ### Instruction: {} ### Input: {} ### Response: {}""" def format_prompts(examples): instructions = examples["instruction"] inputs = examples.get("input", [""] * len(instructions)) outputs = examples["output"] texts = [] for inst, inp, out in zip(instructions, inputs, outputs): text = ALPACA_PROMPT.format(inst, inp, out) + tokenizer.eos_token texts.append(text) return {"text": texts} # 커스텀 데이터셋 (예: AI 서버 구축 Q&A) raw_data = [ { "instruction": "Docker Compose에서 GPU를 사용하는 방법을 설명해줘", "input": "", "output": "Docker Compose에서 GPU를 사용하려면 services 하위에 deploy.resources.reservations.devices 블록을 추가하고..." }, # ... 수백~수천 개의 Q&A 데이터 ] dataset = Dataset.from_list(raw_data).map( format_prompts, batched=True, remove_columns=["instruction", "input", "output"] ) ## ── 4. 학습 설정 ────────────────────────────── trainer = SFTTrainer( model=model, tokenizer=tokenizer, train_dataset=dataset, dataset_text_field="text", max_seq_length=4096, dataset_num_proc=4, packing=True, # 짧은 샘플 패킹으로 학습 효율↑ args=TrainingArguments( per_device_train_batch_size=2, gradient_accumulation_steps=4, # 실질 배치: 2*4 = 8 warmup_steps=10, num_train_epochs=3, learning_rate=2e-4, fp16=not torch.cuda.is_bf16_supported(), bf16=torch.cuda.is_bf16_supported(), logging_steps=10, optim="adamw_8bit", # 8bit Adam: VRAM 절약 weight_decay=0.01, lr_scheduler_type="cosine", output_dir="./outputs/lora_checkpoint", save_steps=100, ) ) # 학습 시작 (RTX 3090, 14B, 1000 샘플 기준 ~2시간) trainer_stats = trainer.train() print(f"학습 완료: {trainer_stats.metrics}") ## ── 5. GGUF 변환 후 Ollama 배포 ────────────── model.save_pretrained_gguf( "lora_model", tokenizer, quantization_method="q4_k_m" # 동시에 GGUF로 저장 ) # Ollama에 배포 import subprocess subprocess.run([ "docker", "exec", "-it", "ollama", "ollama", "create", "my-custom-model", "-f", "/models/lora_model/Modelfile" ])
📊 데이터셋 품질이 파인튜닝 성능을 결정
Flash Attention · KV Cache · 배치 최적화 ⚠️ 검증 전
같은 GPU, 같은 모델이라도 세부 설정에 따라 추론 속도가 2~3배 차이납니다. Flash Attention, KV Cache 최적화, 배치 전략은 추가 비용 없이 성능을 극적으로 개선합니다.
| 최적화 기법 | 효과 | 필요 조건 | 적용 방법 |
|---|---|---|---|
| Flash Attention 2 | 속도 2~4x, VRAM 30% 절약 | CUDA 11.8+, Ampere GPU 이상 | vLLM: 자동 / Ollama: 환경변수 설정 |
| Flash Attention 3 | FA2 대비 추가 1.5~2x | Hopper GPU (H100) 이상 | pip install flash-attn==3.x |
| Prefix Caching | 시스템 프롬프트 재사용 90%↓ | vLLM 0.4+ | --enable-prefix-caching |
| Speculative Decoding | 소형 드래프트 모델로 2~3x 속도 | 보조 모델 필요 | vLLM --speculative-model 옵션 |
| Chunked Prefill | 긴 프롬프트 처리 효율화 | vLLM 0.3+ | --enable-chunked-prefill |
# docker-compose.yml Ollama 환경변수에 추가 environment: - OLLAMA_FLASH_ATTENTION=1 # Flash Attention 활성화 - OLLAMA_NUM_PARALLEL=4 # 동시 요청 처리 수 - OLLAMA_MAX_LOADED_MODELS=2 # 메모리 상주 모델 수 제한 - OLLAMA_KEEP_ALIVE=30m # 미사용 모델 30분 후 언로드 - OLLAMA_MAX_QUEUE=512 # 대기열 최대 크기 # Modelfile에서 모델별 KV Cache 최적화 cat > /tmp/Modelfile << 'EOF' FROM qwen2.5:14b PARAMETER num_ctx 8192 # KV Cache 크기 (컨텍스트 길이) PARAMETER num_batch 512 # 배치 크기 (높을수록 빠름, VRAM↑) PARAMETER num_gpu 48 # GPU 레이어 수 (최대 = 모두 GPU) PARAMETER num_thread 8 # CPU 스레드 (GPU 보조) PARAMETER use_mlock 1 # 메모리 고정 (스왑 방지) PARAMETER use_mmap 1 # 메모리 매핑 활성화 PARAMETER rope_frequency_base 1000000 # RoPE 스케일링 (긴 컨텍스트) EOF docker exec -it ollama ollama create qwen2.5-14b-optimized -f /tmp/Modelfile
멀티 GPU 추론 — 텐서 병렬화로 70B 구동 ⚠️ 검증 전
RTX 3090 두 장(48GB VRAM)으로 70B 모델을 실행하려면 텐서 병렬화(Tensor Parallelism)가 필요합니다. vLLM이 이를 가장 쉽게 지원합니다. 텐서 병렬화는 각 레이어의 가중치 행렬을 여러 GPU에 분할해 동시에 연산합니다.
services:
vllm-70b:
image: vllm/vllm-openai:latest
container_name: vllm_70b
restart: unless-stopped
ports:
- "8003:8000"
volumes:
- huggingface_cache:/root/.cache/huggingface
command: >
--model meta-llama/Llama-3.3-70B-Instruct
--tensor-parallel-size 2 # GPU 2개로 분산
--gpu-memory-utilization 0.92
--max-model-len 8192
--api-key your_api_key
--host 0.0.0.0
--quantization awq # AWQ 양자화 버전 사용
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all # 모든 GPU 사용
capabilities: [gpu]
ipc: host # GPU 간 통신 최적화 (필수!)
shm_size: '32gb'
networks:
- ai_net
networks:
ai_net:
external: true
name: ai_network- 처리량 10배 향상 — vLLM PagedAttention + Continuous Batching으로 동시 100명 사용 가능
- 24GB VRAM에서 70B 구동 — GGUF Q4_K_M + GPU/CPU 혼합으로 느리지만 동작 가능
- 나만의 특화 모델 — Unsloth LoRA로 2~4시간 만에 도메인 전문가 AI 완성
- 추론 속도 3배 향상 — Flash Attention 2 + Prefix Caching + 최적 배치 설정
- 70B 고품질 추론 — 듀얼 GPU 텐서 병렬화로 GPT-4급 로컬 LLM 운영



