# 为何选择此技术栈目标是满足中小企业或技术团队采用本地AI的四个实际需求:一个推理引擎(Ollama),一个基于Web的用户界面(Open WebUI),一个用于RAG的向量数据库(Qdrant),以及一个无代码自动化层(n8n)。整个系统通过Traefik反向代理封装,负责HTTPS(通过Let's Encrypt)和路由配置。
每个服务都在独立的容器中运行,拥有自己的持久化存储卷。您可以禁用其中一个服务而不影响其他服务,单独更新某个组件,或通过一条命令在预发布环境中复制整个栈。
i
本指南不涵盖的内容
这不是 Kubernetes 指南。对于使用单台服务器的 10–50 人团队(单个 GPU 上最多约 10 名并发用户),Docker Compose 完全够用。超出这一规模,或需要多节点高可用性时,请考虑 k3s 或 Nomad。
# 目标架构✓
本地智能体套件
在你的机器上执行操作的智能体:具备智能体能力的 Cline、MCP、n8n + Ollama、本地自动化。
Traefik(端口80/443) 反向代理 + 通过 Let's Encrypt 实现自动 TLS 终止。所有外部流量均经由该代理转发。
Open WebUI(内部) 类似 ChatGPT 的聊天界面,在 chat.votre-domaine.fr 上提供服务。支持本地身份验证或 OIDC。
Ollama(内部) 推理守护进程。未公开暴露,仅可通过Docker网络访问。
Qdrant(内部) 用于 RAG 的向量数据库。存储您文档的嵌入向量。
n8n 无代码自动化,可通过 n8n.votre-domaine.fr 访问。
Prometheus + Grafana 可选,仅在内部开放访问,用于监控 GPU/CPU/RAM。 # 先决条件
Linux服务器 Ubuntu 22.04 LTS 或 Debian 12,需root SSH权限,最低16 GB内存,200 GB磁盘空间。
推荐使用 NVIDIA GPU RTX 3090 24 GB 或 RTX 4090 可流畅运行 14B–32B 模型,RTX 4070 12 GB 则适合运行 7B 模型。没有 GPU 时,请在 CPU 上使用 3B 模型。
一个域名 需有DNS访问权限,以便创建子域名。这是使用Let's Encrypt的必要条件。
Docker Engine + Compose v2 通过官方脚本 get.docker.com 安装。自 2022 年起,已包含 compose 插件。
NVIDIA Container Toolkit 如果您有 GPU,它能让 Docker 将 GPU 提供给容器使用。 # 1. 准备服务器从全新安装的 Ubuntu 22.04 系统开始。需要安装三项:Docker、NVIDIA Container Toolkit(如果配备 GPU),以及配置妥当的防火墙。
Docker安装 ⧉ 复制
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
docker compose versionNVIDIA Container Toolkit ⧉ 复制
distribution=$(. /etc/os-release; echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker测试Docker是否能识别GPU ⧉ 复制
docker run --rm --gpus all nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smiUFW 防火墙最简配置 ⧉ 复制
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP - redir vers HTTPS'
sudo ufw allow 443/tcp comment 'HTTPS Traefik'
sudo ufw enable!
请勿直接暴露 Ollama
Ollama 的11434端口无任何认证。若在互联网上开放此端口,任何人都可占用您的GPU并通过API提取您的模型。所有流量均经Traefik代理并需认证。
# 2. 注释版 docker-compose.yml创建工作目录和主文件。这是部署的核心——每个部分都附有注释,方便您按需调整,而不会破坏整个部署。
目录结构 ⧉ 复制
mkdir -p /opt/llm-stack/{traefik,ollama,open-webui,qdrant,n8n}
cd /opt/llm-stack
touch docker-compose.yml .env.env(需补充) ⧉ 复制
DOMAIN=votre-domaine.fr
[email protected]
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=changez-moi-vraiment
TRAEFIK_DASHBOARD_AUTH=admin:$apr1$xxxxxxx # generé avec htpasswddocker-compose.yml ⧉ 复制
name: llm-stack
networks:
proxy: # réseau public exposé par Traefik
internal: # réseau privé Ollama <-> WebUI <-> Qdrant
volumes:
ollama_data:
open_webui_data:
qdrant_data:
n8n_data:
traefik_certs:
services:
# --- Traefik : reverse proxy + Let's Encrypt automatique ---
traefik:
image: traefik:v3.2
restart: unless-stopped
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --entrypoints.websecure.address=:443
- --certificatesresolvers.le.acme.email=${ACME_EMAIL}
- --certificatesresolvers.le.acme.storage=/certs/acme.json
- --certificatesresolvers.le.acme.tlschallenge=true
- --api.dashboard=true
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik_certs:/certs
networks: [proxy]
labels:
- traefik.enable=true
- traefik.http.routers.dashboard.rule=Host(`traefik.${DOMAIN}`)
- traefik.http.routers.dashboard.service=api@internal
- traefik.http.routers.dashboard.entrypoints=websecure
- traefik.http.routers.dashboard.tls.certresolver=le
- traefik.http.routers.dashboard.middlewares=dashboard-auth
- traefik.http.middlewares.dashboard-auth.basicauth.users=${TRAEFIK_DASHBOARD_AUTH}
# --- Ollama : moteur d'inférence, JAMAIS exposé en public ---
ollama:
image: ollama/ollama:latest
restart: unless-stopped
volumes:
- ollama_data:/root/.ollama
environment:
- OLLAMA_HOST=0.0.0.0:11434
- OLLAMA_KEEP_ALIVE=24h
- OLLAMA_NUM_PARALLEL=2
networks: [internal]
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
# --- Open WebUI : interface chat, exposée en HTTPS ---
open-webui:
image: ghcr.io/open-webui/open-webui:main
restart: unless-stopped
depends_on: [ollama]
environment:
- OLLAMA_BASE_URL=http://ollama:11434
- WEBUI_AUTH=true
- ENABLE_SIGNUP=false
volumes:
- open_webui_data:/app/backend/data
networks: [proxy, internal]
labels:
- traefik.enable=true
- traefik.http.routers.chat.rule=Host(`chat.${DOMAIN}`)
- traefik.http.routers.chat.entrypoints=websecure
- traefik.http.routers.chat.tls.certresolver=le
- traefik.http.services.chat.loadbalancer.server.port=8080
# --- Qdrant : base vectorielle pour le RAG ---
qdrant:
image: qdrant/qdrant:latest
restart: unless-stopped
volumes:
- qdrant_data:/qdrant/storage
networks: [internal]
# --- n8n : automatisation no-code ---
n8n:
image: docker.n8n.io/n8nio/n8n:latest
restart: unless-stopped
environment:
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER}
- N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD}
- N8N_HOST=n8n.${DOMAIN}
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.${DOMAIN}/
volumes:
- n8n_data:/home/node/.n8n
networks: [proxy, internal]
labels:
- traefik.enable=true
- traefik.http.routers.n8n.rule=Host(`n8n.${DOMAIN}`)
- traefik.http.routers.n8n.entrypoints=websecure
- traefik.http.routers.n8n.tls.certresolver=le
- traefik.http.services.n8n.loadbalancer.server.port=5678→
为什么需要两个网络
internal网络无法访问反向代理。Ollama和Qdrant仅位于该网络中——攻击者即使通过Traefik进入,也无法直接访问它们的API。Open WebUI则同时连接两个网络:通过proxy接收公网流量,并通过internal与Ollama通信。
# 3. 配置 Traefik 和 DNS在首次运行 docker compose up 之前,请创建三个指向您服务器公网 IP 的 A 类 DNS 记录:
chat.votre-domaine.fr Open WebUI 用户界面。
n8n.votre-domaine.fr 工作流编辑器n8n
traefik.votre-domaine.fr Traefik 控制台(需基本身份验证访问) 生成 Traefik 控制台的 basic auth 哈希值。注意:在 .env 文件中需将 $ 符号加倍(docker-compose 需要转义)
基本认证哈希值 ⧉ 复制
sudo apt install apache2-utils
htpasswd -nb admin VotreMotDePasse | sed -e 's/\$/\$\$/g'!
Let's Encrypt 和速率限制
Let's Encrypt 对每个域名每周最多颁发 50 个证书。测试阶段,请使用 --certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory 指向预发布服务器。等一切正常运行后,再进行更改。
# 4. 首次启动与验证01
启动服务栈
在 /opt/llm-stack 目录中运行 docker compose up -d。Traefik 会在几秒内向 Let's Encrypt 申请证书——请留意日志。
02
检查服务健康状态
docker compose ps doit afficher tous les services en running. Aucun ne doit redémarrer en boucle.
03
下载第一个模型
docker compose exec ollama ollama pull qwen3.5:9b (6,6 Go, 256k de contexte, le polyvalent 8 Go de référence en 2026). Pour un modèle plus costaud sur un GPU 24 Go : qwen3.8:27b (≈18 Go de VRAM, 262k de contexte, licence Apache 2.0).
04
测试用户界面
打开 https://chat.votre-domaine.fr。第一个创建的账户即为管理员。ENABLE_SIGNUP=false 会阻止之后的所有公开注册。
05
确认GPU是否被使用
docker compose exec ollama ollama ps : la colonne PROCESSOR doit afficher 100% GPU.
Traefik 日志实时查看 ⧉ 复制
docker compose logs -f traefik | grep -i acme# 5. 加强安全性配置不当、暴露在公网的技术栈会在一小时内遭到扫描。以下是在生产环境中使用 Docker Compose 部署 LLM 时必须遵守的六条规则,以免您的部署出现在别人的 shodan.io 搜索结果中。
高强度且不重复使用的密码 使用 openssl rand -base64 32 生成您的敏感凭据。切勿使用 .env 文件中的示例密码。
禁用公开注册 在 Open WebUI 上设置 ENABLE_SIGNUP=false。否则,任何人都可以创建账户并使用您的 GPU。
通过 Traefik 中间件设置安全响应头 添加 secureHeaders 中间件,包含 HSTS、frame-deny、content-type-nosniff。仅三行代码,效果显著
按容器限制资源 在 deploy.resources.limits 下,设置 memory 和 cpus。防止一个崩溃的服务导致整个服务器瘫痪。
尽可能使用只读容器 在 Traefik 和 Qdrant 中添加 read_only: true。为临时写入目录使用 Tmpfs。
每月更新 watchtower 可自动拉取镜像,但更建议使用 cron 配合明确的 git 标签,以掌控生产环境中的版本。 设置安全响应头的中间件 ⧉ 复制
# À ajouter sur le service open-webui en labels:
- traefik.http.routers.chat.middlewares=secure-headers
- traefik.http.middlewares.secure-headers.headers.stsSeconds=31536000
- traefik.http.middlewares.secure-headers.headers.stsIncludeSubdomains=true
- traefik.http.middlewares.secure-headers.headers.frameDeny=true
- traefik.http.middlewares.secure-headers.headers.contentTypeNosniff=true
- traefik.http.middlewares.secure-headers.headers.browserXssFilter=true→
Fail2ban 配置在 Traefik 前面
为阻止针对基本认证的暴力破解(Traefik 仪表板、n8n),请安装 fail2ban,并配置解析 Traefik 日志的过滤器。尝试 5 次 → 封禁 IP 1 小时。配置只需 10 行。
# 6. Prometheus + Grafana 监控生产环境中必须监控三项指标:Ollama 的显存使用量、Open WebUI 的请求延迟,以及 Let's Encrypt 证书的状态(Traefik 通过 /metrics 提供所有这些指标)。
需添加至 docker-compose.yml 的配置块 ⧉ 复制
prometheus:
image: prom/prometheus:latest
restart: unless-stopped
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
networks: [internal]
nvidia-exporter:
image: utkuozdemir/nvidia_gpu_exporter:latest
restart: unless-stopped
devices: [/dev/nvidiactl, /dev/nvidia0]
volumes:
- /usr/lib/x86_64-linux-gnu/libnvidia-ml.so.1:/usr/lib/x86_64-linux-gnu/libnvidia-ml.so.1:ro
- /usr/bin/nvidia-smi:/usr/bin/nvidia-smi:ro
networks: [internal]
grafana:
image: grafana/grafana:latest
restart: unless-stopped
volumes:
- grafana_data:/var/lib/grafana
networks: [proxy, internal]
labels:
- traefik.enable=true
- traefik.http.routers.grafana.rule=Host(`grafana.${DOMAIN}`)
- traefik.http.routers.grafana.entrypoints=websecure
- traefik.http.routers.grafana.tls.certresolver=le
- traefik.http.services.grafana.loadbalancer.server.port=3000prometheus.yml ⧉ 复制
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'traefik'
static_configs:
- targets: ['traefik:8080']
- job_name: 'nvidia'
static_configs:
- targets: ['nvidia-exporter:9835']
- job_name: 'qdrant'
static_configs:
- targets: ['qdrant:6333']在 Grafana 侧,导入 ID 为 14574(NVIDIA GPU Exporter)和 17346(Traefik v3)的仪表板。5 分钟内即可获得完整视图:GPU 显存、GPU 温度、按子域名的 HTTP 请求速率、P95 延迟。
# 7. 备份与更新五个卷包含所有关键数据:ollama_data(模型)、open_webui_data(账户+对话+RAG)、qdrant_data(向量)、n8n_data(工作流)、traefik_certs(证书)。
/usr/local/bin/backup-llm-stack.sh ⧉ 复制
#!/bin/bash
set -e
BACKUP_DIR=/var/backups/llm-stack
TS=$(date +%Y%m%d-%H%M)
mkdir -p "$BACKUP_DIR"
for vol in open_webui_data qdrant_data n8n_data traefik_certs; do
docker run --rm \
-v llm-stack_${vol}:/data \
-v "$BACKUP_DIR":/backup \
alpine tar czf "/backup/${vol}-${TS}.tar.gz" -C /data .
done
find "$BACKUP_DIR" -name '*.tar.gz' -mtime +30 -delete每天凌晨 3 点执行的 Cron 定时任务 ⧉ 复制
sudo chmod +x /usr/local/bin/backup-llm-stack.sh
echo "0 3 * * * root /usr/local/bin/backup-llm-stack.sh" | sudo tee /etc/cron.d/llm-stack-backupi
为什么不备份 ollama_data
模型很快就会达到数十GB的大小,可通过 ollama pull 重新下载。无需将其包含在每日备份中——建议仅备份模型列表:docker compose exec ollama ollama list > models.txt。
更新时请采取稳妥做法:固定版本(使用 image: ollama/ollama:0.5.4,而不是 :latest),每次更新前为 VPS 创建快照,并先在预发布环境中测试。
规范更新 ⧉ 复制
cd /opt/llm-stack
docker compose pull
docker compose up -d
docker image prune -f# 故障排除
Traefik 不生成证书 请确保端口 80 和 443 已开放,并且 DNS 正确指向服务器。运行命令 docker compose logs traefik | grep -i error 可发现 90% 的问题(DNS 未传播、速率限制、挑战失败)。
Open WebUI 显示 'No models found' 您尚未在Ollama中拉取模型。运行 `docker compose exec ollama ollama pull qwen3.5:9b`。同时检查 `OLLAMA_BASE_URL=http://ollama:11434`(使用服务名称,而不是localhost)。
容器中未检测到GPU 未安装nvidia-container-toolkit或Docker在配置后未重启。请运行命令 docker run --rm --gpus all nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi 进行测试。
n8n 循环重定向至 HTTP 必须设置WEBHOOK_URL和N8N_PROTOCOL=https。否则n8n会误认为使用HTTP,导致浏览器出现异常。
Grafana 无法通过 Traefik 访问 在其 environment 配置项中添加 GF_SERVER_ROOT_URL=https://grafana.${DOMAIN} 和 GF_SERVER_SERVE_FROM_SUB_PATH=false。
多用户时延迟急剧上升 对现有显存容量而言,OLLAMA_NUM_PARALLEL 设置过高。请根据您的显卡将其降低至 1 或 2。在负载期间查看 ollama ps 的输出。 # 深入了解您的技术栈已在运行,并已配置监控和备份。接下来可从三个顺理成章的方向进一步提升其专业化水平:
将 RAG 接入您的文档 基于 ChromaDB 和 Mistral 的 RAG 指南可轻松适配至 Qdrant——嵌入和分块逻辑相同,仅向量数据库不同。
实现业务流程自动化 《使用 n8n 和 Ollama 自动化》指南介绍了十套具体方案(邮件翻译、潜在客户分类、RSS 摘要),可直接利用您的技术栈。
满足合规性要求 《本地 LLM 与 GDPR》指南详细介绍了企业部署时需要记录的法律义务(处理活动记录、数据保留期限、用户权利)。 这份指南对您有帮助吗?
有反馈、发现了错误,或想补充说明?请告诉我们,让这份指南对每个人都更有帮助。
👍 有用 👎 不清楚 ✏️ 报告错误