在 VPS 上搭建私有 RAG 系统:分步教程
配置一个 VPS,运行 Ollama 和向量存储,并交付一个可工作的私有 RAG API —— 包含每一条命令。
这是一份实践教程:完成后你将在单个 VPS 上拥有一个私有 RAG 系统,能够回答关于你自己文档的问题,且没有任何数据离开你的服务器。我们将配置服务器、安装 Ollama 并拉取模型、搭建向量存储、构建摄取 → 分块 → 嵌入 → 存储管道、使用 LlamaIndex 连接检索与生成,并将整个系统暴露在 FastAPI 端点之后。如果你先需要概念背景——什么是 RAG 以及为什么要自托管——请阅读 自托管 RAG 完全指南 。本页是实践方法。
以下每条命令都是真实可运行的。你只需要更改你的域名、你的文档以及你选择的模型。
▶ 自己跑一遍 —— aquila-starter 是这篇指南的一键自托管版本:Ollama + Qdrant + FastAPI,
docker compose up即起。Fork 下来据为己有。
你将构建什么
一个请求带着问题到达你的 FastAPI 端点。API 在本地嵌入该问题,向向量存储请求最相关的文档块,将这些块塞入提示词中,并发送给本地 LLM 以生成答案——并附上引用。无需托管的嵌入 API,无需托管的向量数据库,无需第三方介入循环。
[Your docs] -> ingest -> chunk -> embed (Ollama) -> store (Qdrant)
|
HTTP request -> FastAPI -> embed query -> retrieve --+--> LLM (Ollama) -> answer + sources
技术栈:Ollama(嵌入+生成)、Qdrant(向量存储)、LlamaIndex(编排)、FastAPI(API)。全部开源,全部在单台机器上。
步骤 0 —— 确定规格并配置 VPS
你并不需要 GPU 来运行它。你在本地运行什么决定了规格。
| 本地运行内容 | 推荐 VPS 规格 | 备注 |
|---|---|---|
| 嵌入 + 向量存储,仅生成使用云端 LLM | 2 vCPU / 4 GB RAM(约 20–30 美元/月) | 最佳性价比。可处理数万个块,检索迅速。 |
| 全部本地化,包含 CPU 上运行的 7–8B 聊天模型 | 4 vCPU / 16 GB RAM | 可用,但生成需要数秒。适合内部工具。 |
| 全部本地化,使用消费级 GPU | GPU 实例(12–24 GB VRAM) | 本地生成速度快;不同成本层级——参见成本分解。 |
一台 2 vCPU / 4 GB 的机器在 DigitalOcean Basic Droplet 上大约 24 美元/月,或者在 Hetzner 上大约 4–8 欧元/月(CX23/CPX22 级别),截至 2026 年 6 月。本教程我们将假设使用 4 vCPU / 16 GB RAM,这样你也可以选择运行本地聊天模型;如果仅使用云端 LLM 进行生成,可以降到 4 GB。
启动一个 Ubuntu 24.04 LTS 实例,然后通过 SSH 登录并执行基础操作:
ssh root@YOUR_SERVER_IP
# Create a non-root user and harden a little
adduser rag && usermod -aG sudo rag
ufw allow OpenSSH && ufw enable
# Base packages
apt update && apt upgrade -y
apt install -y python3-venv python3-pip curl docker.io
systemctl enable --now docker
然后以 rag 用户身份重新登录进行后续操作。
步骤 1 —— 安装 Ollama 并拉取模型
Ollama 在本地运行你的嵌入模型和(可选)聊天模型。一条命令即可安装:
curl -fsSL https://ollama.com/install.sh | sh
拉取一个嵌入模型,以及(可选)一个小型聊天模型:
ollama pull nomic-embed-text # embeddings — small, fast, runs on CPU
ollama pull llama3.1:8b # local chat model (optional; skip if using a cloud LLM)
nomic-embed-text 是一个可靠的默认选择:几百 MB,在 CPU 上运行流畅,对大多数知识库来说已经足够好。如果你想比较替代方案,请参见最佳本地嵌入模型。确认 Ollama 正在服务:
curl http://localhost:11434/api/tags # should list the models you pulled
Ollama 默认监听 localhost:11434——保持这样,使其永不暴露到互联网。
步骤 2 —— 搭建向量存储
我们将使用 Qdrant——一个快速的、基于 Rust 的向量数据库,支持原生混合搜索和简洁的 Docker 部署。这是一个稳妥的生产选择。(如果你已经在运行 PostgreSQL,pgvector 是一个很好的替代方案,可以简化你的技术栈——参见pgvector vs Qdrant。更广泛的调研请见最佳自托管向量数据库。)
以容器方式运行 Qdrant,数据持久化到磁盘:
docker run -d --name qdrant \
-p 127.0.0.1:6333:6333 \
-v $(pwd)/qdrant_storage:/qdrant/storage \
qdrant/qdrant
注意 127.0.0.1: 绑定——Qdrant 仅能从本地机器访问,而非公共互联网。检查是否启动:
curl http://localhost:6333/healthz # "healthz check passed"
这就是你的整个数据层。对这个概念还不熟悉?什么是向量数据库 解释了为何这对检索至关重要。
步骤 3 —— 设置 Python 项目
创建一个虚拟环境并安装编排库:
mkdir ~/private-rag && cd ~/private-rag
python3 -m venv .venv && source .venv/bin/activate
pip install \
llama-index-core \
llama-index-embeddings-ollama \
llama-index-llms-ollama \
llama-index-vector-stores-qdrant \
qdrant-client fastapi "uvicorn[standard]"
将你的源文档——PDF、Markdown、文本、HTML——放入 ./knowledge 文件夹:
mkdir knowledge
# scp/rsync your docs in, e.g.:
# rsync -av ~/Documents/handbook/ rag@YOUR_SERVER_IP:~/private-rag/knowledge/
步骤 4 —— 构建摄取管道(摄取 → 分块 → 嵌入 → 存储)
该脚本加载你的文档,将其分割成块,通过 Ollama 在本地嵌入每个块,并将向量写入 Qdrant。现在运行一次,以后每当文档变更时再次运行。
创建 ingest.py:
import qdrant_client
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, StorageContext
from llama_index.core.node_parser import SentenceSplitter
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.vector_stores.qdrant import QdrantVectorStore
COLLECTION = "kb"
# 1. Ingest — load every file under ./knowledge (PDF, md, txt, html, ...)
docs = SimpleDirectoryReader("./knowledge").load_data()
# 2. Chunk — ~800 tokens with overlap so boundaries aren't lost
splitter = SentenceSplitter(chunk_size=800, chunk_overlap=100)
# 3. Embed — local model via Ollama (same model MUST be used at query time)
embed_model = OllamaEmbedding(model_name="nomic-embed-text")
# 4. Store — write vectors + text + metadata into Qdrant
client = qdrant_client.QdrantClient(host="localhost", port=6333)
vector_store = QdrantVectorStore(client=client, collection_name=COLLECTION)
storage = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(
docs,
storage_context=storage,
embed_model=embed_model,
transformations=[splitter],
)
print(f"Indexed {len(docs)} documents into Qdrant collection '{COLLECTION}'.")
运行它:
python ingest.py
索引是最重的步骤,且是一次性或增量的——在 CPU 上花费几分钟是可以接受的。这里的分块大小和重叠是 RAG 中最最重要的质量杠杆:太大则相关性被稀释,太小则块失去上下文。从 800/100 开始,并根据真实问题进行调整。
步骤 5 —— 连接检索与生成
现在查询侧:使用相同模型嵌入问题,从 Qdrant 检索 top 块,并用本地 LLM 生成基于实据的答案。创建 query.py:
import qdrant_client
from llama_index.core import VectorStoreIndex
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.llms.ollama import Ollama
from llama_index.vector_stores.qdrant import QdrantVectorStore
COLLECTION = "kb"
client = qdrant_client.QdrantClient(host="localhost", port=6333)
vector_store = QdrantVectorStore(client=client, collection_name=COLLECTION)
embed_model = OllamaEmbedding(model_name="nomic-embed-text")
llm = Ollama(model="llama3.1:8b", request_timeout=120.0) # or a cloud LLM
# Rebuild the index handle from the existing Qdrant collection
index = VectorStoreIndex.from_vector_store(vector_store, embed_model=embed_model)
query_engine = index.as_query_engine(llm=llm, similarity_top_k=5)
def answer(question: str):
resp = query_engine.query(question)
sources = [n.metadata.get("file_name", "unknown") for n in resp.source_nodes]
return str(resp), sources
if __name__ == "__main__":
text, sources = answer("How do I rotate the API keys?")
print(text)
print("Sources:", sources)
similarity_top_k=5 请求五个最相关的块——一个合理的默认值。太少数则答案不完整;太多则超出上下文窗口并混淆模型。在命令行测试它:
python query.py
如果你更愿意仅将云端 LLM 用于最终生成步骤(更快,同时在索引期间保持文档隐私),将 Ollama(...) 行替换为该提供商的 LlamaIndex LLM——只有给定查询检索到的少数块会离开服务器,绝不会是整个语料库。其中的语义匹配部分在什么是语义搜索中解释,向量本身在什么是嵌入中解释。
步骤 6 —— 通过 FastAPI 对外暴露
将查询函数封装到 HTTP 端点中。创建 app.py:
from fastapi import FastAPI
from pydantic import BaseModel
from query import answer
app = FastAPI(title="Private RAG")
class Query(BaseModel):
question: str
@app.get("/healthz")
def healthz():
return {"status": "ok"}
@app.post("/ask")
def ask(q: Query):
text, sources = answer(q.question)
return {"answer": text, "sources": sources}
运行它:
uvicorn app:app --host 127.0.0.1 --port 8000
在另一个 shell 中测试:
curl -X POST http://localhost:8000/ask \
-H "Content-Type: application/json" \
-d '{"question": "How do I rotate the API keys?"}'
你现在拥有一个私有 RAG API。整个管道——嵌入、向量搜索、生成——都在你的 VPS 上运行。将它绑定到 127.0.0.1 并在前面放置一个反向代理(下一节),而不是直接暴露 uvicorn。
进入生产环境
上述教程是一个可工作的系统,但尚未加固。在真实用户接触之前:
- 反向代理 + TLS。 在 FastAPI 前面放置 Caddy 或 Nginx,终止 HTTPS(Caddy 两行即可获得免费 Let’s Encrypt 证书),仅暴露端口 80/443。保持 Ollama(
11434)、Qdrant(6333)和 uvicorn(8000)绑定到127.0.0.1。 - 在进程管理器下运行 uvicorn。 使用
systemd单元(或带有 uvicorn workers 的gunicorn),以便 API 在崩溃时重启并在重启后存活。Qdrant 容器同样如此(--restart unless-stopped)。 - 添加认证层。 在
/ask前使用 API 密钥头或你现有的认证。切勿交付一个开放的 RAG 端点。 - 增量重新索引。 文档会变更。按计划运行
ingest.py,或通过文档 ID 进行 upsert/删除,以免过时的块污染你的答案。 - 混合搜索 + 重排序。 纯语义搜索会错过精确匹配——错误码、SKU、名称。Qdrant 原生支持稠密+稀疏向量;添加关键词/BM25 组件和重排序器来提升精确度。
- 备份。 定期快照 Qdrant
storage卷(以及你的原始knowledge文件夹)。你的索引就是数据;像对待数据库一样对待它。 - 评估循环。 编写 20–50 个代表性问题,注明每个问题应该由哪个文档回答,并衡量正确来源出现在 top-k 中的频率。每次你更改块大小、更换嵌入模型或添加重排序器时重新运行——否则 “演示看起来不错” 就是你唯一的度量标准。
成本现实
一旦运行起来,你的账单就是 VPS 的费用——在 DigitalOcean 类机器上固定 约 20–30 美元/月(在 Hetzner 更便宜),与查询量无关。没有按 token 的嵌入费用,也没有按向量的存储费用,因为两者都在你已经付费的硬件上运行。与托管 OpenAI + Pinecone 设置(包括托管账单的实际去向)的完整比较,请参见自托管 RAG vs OpenAI + Pinecone:真实成本分解。
常见问题
我真的需要 GPU 吗? 不需要。嵌入和向量搜索在 CPU 上运行良好。GPU 仅加快本地答案生成。最实惠的可行方案是在 2 vCPU / 4 GB VPS 上运行嵌入 + Qdrant,并仅对生成步骤调用云端 LLM。
我可以将 Qdrant 替换为 Chroma 或 pgvector 吗?
可以——LlamaIndex 抽象了存储。使用 Chroma 做原型,如果你已经在运行 PostgreSQL 则用 pgvector,当你想要一个快速、生产就绪的引擎时用 Qdrant。管道的形状不变;只有 *VectorStore 那一行改变。在最佳自托管向量数据库中比较它们。
在这种设置中是否有任何数据发送给第三方?
使用本地 LLM(通过 Ollama 的 llama3.1:8b),没有任何数据离开机器——嵌入、检索和生成都是本地的。如果你选择在生成步骤使用云端 LLM,则仅发送该单个查询检索到的小部分块,绝不会是全部语料库。
LlamaIndex 还是 LangChain? 两者都可以。LlamaIndex 稍微更侧重于检索,本文中的代码片段使用了它;如果你的应用已经依赖 LangChain,那也没问题。相同的六个步骤——摄取、分块、嵌入、存储、检索、生成——对两者都适用。
我的答案错误或含糊。我应该先修复什么?
检索(而不是 LLM)几乎总是瓶颈。构建上面提到的小型评估集,然后调整块大小/重叠、similarity_top_k,并在你换成更大的模型之前添加混合搜索。
Aquila 是私有、自托管 AI 搜索的独立指南——基于这样的信念:你应该拥有你的索引,而不是租用它。如果本文让你拥有一个可工作的 RAG API,探索更多指南或订阅获取关于 RAG、向量数据库和嵌入的诚实、供应商中立的文章。掌控你自己的搜索。