SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-13

Langfuse 自托管部署与 AI Agent 追踪指南

在自己的 VPS 上运行 Langfuse,了解实际内存下限、固定镜像标签、TLS 配置、ClickHouse 保留策略,以及可验证的备份方法。

为什么要跟踪 AI agent

您可以自行托管 Langfuse,以查看 agent 在一次运行中实际执行了什么。Langfuse 是一款开源 LLM(大语言模型)可观测性工具。它会记录每条提示词、每次模型响应、每次工具调用和每个 token,然后将它们归入一个可打开和查看的 trace 中。在您自己的 VPS 上运行 Langfuse,意味着这些提示词不会离开您控制的服务器。

原因很直接。您无法修复看不见的成本问题或质量问题。服务商账单只能告诉您,星期二的成本是星期一的4倍。trace 会告诉您,是哪次 agent 运行导致了这笔成本,哪条提示词增长到 40,000 个 token,以及哪个重试循环在放弃前运行了9次。账单提供数字,trace 则提供产生这些数字的代码。

本指南会使用3个术语。trace 是 agent 的一次端到端运行。observation 是该运行中的一个步骤:普通代码对应一个 span,调用模型对应一个 generation。score 是附加到 trace 上的一个数字,可能来自人工审核,也可能来自自动评估器。Langfuse 支持 OpenTelemetry(OTel),这是一种厂商中立的分布式跟踪标准,因此您已有的 instrumentation 可以将数据发送到 Langfuse。

自托管 Langfuse 实际运行的组件

Langfuse v4 不是一个容器。它由两个应用容器和四个存储服务组成。在单台 VPS 上,这 6 个组件都会运行在您的服务器上。

  • langfuse-web 提供 Web 界面和数据摄取 API。
  • langfuse-worker 在后台处理队列。它解析数据摄取批次、计算成本,并运行每晚的保留任务。
  • Postgres 保存用户、组织、项目、API 密钥和提示词等事务数据。
  • ClickHouse 保存跟踪数据本身,也就是观测记录和评分。它是为分析查询设计的列式存储,因此即使仪表板查询超过 1 亿行数据,也能快速返回结果。
  • Redis 是 Web 容器和 worker 之间的队列与缓存。
  • MinIO 在服务器上提供 S3 兼容的对象存储。它保存每个原始传入事件,以及您附加的任何媒体文件。

Langfuse 为负责处理任务的 3 个组件公布了最低资源要求。

ChartLangfuse published minimum resources per component
The data behind this chart
[
  {
    "label": "ClickHouse",
    "cpu_cores": 2,
    "memory_gib": 8
  },
  {
    "label": "Langfuse web",
    "cpu_cores": 2,
    "memory_gib": 4
  },
  {
    "label": "Langfuse worker",
    "cpu_cores": 2,
    "memory_gib": 4
  }
]

仅 ClickHouse 就需要 8 GiB 内存。Web 容器和 worker 各需要 4 GiB。这些是 Langfuse 标注资源需求的 3 个组件的最低值;Postgres、Redis 和 MinIO 仍需额外占用内存。项目自己的 Docker Compose 指南建议使用 4 核、16 GiB 内存和约 100 GiB 存储空间的服务器。这一配置与上述计算结果一致,并不是额外留出的余量。

不要在 2 GiB 配置上尝试运行。ClickHouse 启动后可以处理一段时间的写入,但会在后台合并期间退出,因为合并会将表的大型数据分片加载到内存中。您会看到 docker compose ps 将 clickhouse 容器报告为 restartingdmesg 输出类似 Out of memory: Killed process 1234 (clickhouse-serv) 的日志行,并且所有 Langfuse 仪表板都返回 500。在负载较轻时,ClickHouse 也可能直接拒绝查询,并记录 DB::Exception: Memory limit (total) exceeded。对于每天发送几千条跟踪记录的单名开发者,8 GiB 可以运行。规划资源时应按 16 GiB 计算。

使用 Docker Compose 部署 Langfuse

克隆代码仓库。堆栈、服务连接配置和默认环境变量都位于其 docker-compose.yml 中。

git clone https://github.com/langfuse/langfuse.git
cd langfuse

该文件中所有必须修改的值都标记为 # CHANGEME。首先生成 3 个应用密钥。

openssl rand -base64 32   # NEXTAUTH_SECRET
openssl rand -base64 32   # SALT
openssl rand -hex 32      # ENCRYPTION_KEY

ENCRYPTION_KEY 必须是 256 位,并写为 64 个十六进制字符。这正是 openssl rand -hex 32 的输出格式。它用于加密静态敏感数据,包括您存储在实例中的 LLM 提供商密钥。数据写入后再修改该值将导致这些数据无法解密,因此从首次启动开始就应将其视为永久值。SALT 用于对 Langfuse API 密钥进行哈希处理,因此修改它会使代理当前使用的所有密钥失效。

然后设置 POSTGRES_PASSWORDCLICKHOUSE_PASSWORDREDIS_AUTHMINIO_ROOT_PASSWORD。MinIO 密码会出现 4 次:一次作为 MINIO_ROOT_PASSWORD,之后还会作为 LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEYLANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEYLANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY。漏掉任何一处,MinIO 都会使用 SignatureDoesNotMatch 拒绝对应客户端。该错误会出现在 worker 日志中,而 Web 界面仍可能显示正常。将这些值保存在 env 文件中,而不是纳入受版本控制的 Compose 文件,这是 Docker Compose env 文件和密钥 中介绍的做法。

开始前固定镜像标签

随附文件使用 langfuse/langfuse:4langfuse/langfuse-worker:4。这些标签会移动。Langfuse 启动时会自动执行 Postgres 和 ClickHouse 迁移,因此几个月后执行一次常规 docker compose pull,可能会在当天没有备份的数据库上触发计划外的架构迁移。将两者固定到 docker-compose.override.yml 中的同一版本。Compose 会将该文件中的设置合并到随附文件之上,因此后续执行 git pull 时不会覆盖您的修改。

services:
  langfuse-web:
    image: docker.io/langfuse/langfuse:4.3.1
  langfuse-worker:
    image: docker.io/langfuse/langfuse-worker:4.3.1

截至 August 2026,4.3.1 是当前的 4.3 版本(之后已发布 4.4.0)。查看项目的 GitHub releases 页面,固定为部署当天的当前版本,然后再有计划地升级。随附文件中的存储镜像已经固定到主版本,包括 postgres:17clickhouse-server:25.12redis:7,这些镜像也应采用相同的处理方式。

启动服务。

docker compose up -d
docker compose ps
docker compose logs -f langfuse-worker

首次启动会执行迁移,因此在服务响应前等待 1 到 2 分钟。docker compose ps 应列出 6 个处于 running 状态的服务。如果 worker 反复重启,请查看其日志获取原因:CLICKHOUSE_MIGRATION_URL 使用端口 9000 上的 ClickHouse 原生协议,而不是端口 8123 上的 HTTP 协议。将其指向 8123 会导致该服务失败,但 Web 容器仍可能显示正常。

从服务器本机检查健康状态。

curl -s "http://localhost:3000/api/public/health?failIfDatabaseUnavailable=true"
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3000/api/public/ready

普通的 /api/public/health 调用只能证明 API 进程仍在运行,因为它会有意跳过数据库检查,使服务在 Postgres 短暂中断时仍能继续提供服务。failIfDatabaseUnavailable=true 形式才适合交给监控系统检查;数据库无法访问时,它会返回 503。迁移完成且容器可以接受流量后,/api/public/ready 会返回 200。这两项都是普通的 HTTP 检查,因此 Uptime Kuma 状态页面 可以监控它们,并在您的代理之前发现整个堆栈已停止。

在前端配置 TLS,并关闭额外端口

随附的 Compose 文件为 Web 容器发布 3000:3000,为 MinIO 发布 9090:9000。这两个端口都会绑定到所有网络接口。在公网 IP 上,这意味着任何扫描 3000 端口的人都能访问注册页面,任何扫描 9090 端口的人都在访问存放原始提示词的存储桶。

仅设置防火墙规则无法关闭这些端口。Docker 会将自己的 DNAT 规则写入 nat 表,并且这些规则会在数据包到达 ufw 的 filter 规则之前处理,因此 ufw deny 3000 仍会使发布的端口保持开放。这个问题十分常见,已经有专门的指南说明:为什么 Docker 发布的端口会绕过 ufw。应在 override 文件中将端口绑定到 loopback。

services:
  langfuse-web:
    ports:
      - "127.0.0.1:3000:3000"
    environment:
      NEXTAUTH_URL: https://langfuse.example.com
  minio:
    ports:
      - "127.0.0.1:9090:9000"
      - "127.0.0.1:9091:9001"

NEXTAUTH_URL 必须是包含 scheme 的完整公网地址,因为登录流程会根据该值构建回调 URL。如果在 HTTPS 代理后仍将其保留为 http://localhost:3000,登录往返过程中浏览器会被重定向到无法访问的地址。

现在将反向代理指向 127.0.0.1:3000,并让它管理证书。同一 Compose 项目中的 Traefik 通常是首选方案,路由标签的配置方法见通过一个 Traefik 反向代理运行多个应用。如果 Langfuse 是这台服务器上唯一的应用,Caddy 用两行配置即可完成相同工作。使用 curl -sI https://langfuse.example.com/api/public/ready 验证,然后从第二台机器确认 curl http://YOUR_IP:3000 现在会超时。

MinIO 有一个注意事项。Langfuse 会通过指向该 S3 端点的预签名 URL 将附加媒体提供给浏览器。因此,如果使用包含图像或音频的多模态跟踪记录,loopback-only 的 MinIO 会导致这些附件无法加载。在代理 MinIO 之前,请阅读 blob 存储配置页面,因为写入预签名 URL 的端点必须与实际发布的端点一致。纯文本跟踪记录不受影响。

首次访问时创建账户,然后确保实例由您管理。将 LANGFUSE_ALLOWED_ORGANIZATION_CREATORS 设置为您自己的电子邮件地址,这样访问该页面的陌生人就无法在您的服务器上创建组织。如果您已经在运行将 Authentik 作为自己的身份提供商,Langfuse 支持标准 OIDC 连接。这样账户会与其他应用中的账户一起管理,而不是只保存在这台服务器知道的密码列表中。

发送第一条 trace

在 Web 界面中创建项目,然后从项目设置中复制其公钥和密钥。Python SDK 会读取 3 个环境变量。

export LANGFUSE_PUBLIC_KEY="pk-lf-..."
export LANGFUSE_SECRET_KEY="sk-lf-..."
export LANGFUSE_BASE_URL="https://langfuse.example.com"

LANGFUSE_BASE_URL 是 SDK v4 中的变量名。SDK v4 于 2026 年 3 月发布。旧代码和旧指南使用 LANGFUSE_HOST。如果 trace 被发送到 Langfuse Cloud,而不是您的服务器,原因是 base URL 未设置,因为默认值指向托管实例。

pip install langfuse opentelemetry-instrumentation-anthropic anthropic
import os
from anthropic import Anthropic
from langfuse import get_client, observe
from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor

AnthropicInstrumentor().instrument()
langfuse = get_client()
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

@observe(as_type="tool")
def lookup_order(order_id: str) -> str:
    return f"order {order_id}: shipped"

@observe()
def handle_request(question: str) -> str:
    context = lookup_order("A-1042")
    message = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=512,
        messages=[{"role": "user", "content": f"{context}\n\n{question}"}],
    )
    return message.content[0].text

if __name__ == "__main__":
    assert langfuse.auth_check()
    print(handle_request("Where is my order?"))
    langfuse.flush()

@observe 装饰器会在函数周围创建 observation,捕获函数参数和返回值,并将其嵌套到当前已激活的 observation 下。AnthropicInstrumentor 是 Anthropic 客户端的 OpenTelemetry instrumentation。它会将每次 messages.create 调用转换为一个 generation,其中包含模型名称、token 使用量和延迟,无需修改调用位置。

两个调用可以代您完成检查。langfuse.auth_check() 会在密钥错误或 base URL 错误时返回 False。这样可以更快定位问题,而不必猜测 dashboard 为空的原因。langfuse.flush() 会阻塞,直到排队的 span 发送完成。短生命周期进程需要调用它,因为 SDK 会在后台批量发送数据,而立即退出的脚本会连同尚未发送的批次一起终止。

为什么 ClickHouse 会持续增长?

Trace 是大多数人自行托管时增长最快的数据。每次 agent 运行都会为每个步骤写入一行,并完整保存输入和输出。因此,提示词很长且输出频繁的 agent 每天产生的字节数,可能远超它所监控的应用。置之不理时,ClickHouse 会占满磁盘;磁盘写满后,数据摄取会停止,而不是变慢。

这里有两类数据会增长,需要分别处理。

第一类是您自己的 trace 数据,处理方法是设置保留期限。在 Web 界面中打开项目设置,并按天设置数据保留期限。Langfuse 接受的最短期限为 3 天。随后,系统会在每晚选择超过该期限的 traces、observations、scores 和媒体资源,并将它们从 ClickHouse 和 blob storage 中删除。该任务需要对 bucket 具有 DeleteObject 权限;默认 compose 文件中的 MinIO root 凭据已经具备此权限。删除操作不可恢复。如果需要长期保留历史数据,请先配置 blob storage 导出。不要在 Langfuse 自有表上手写 TTL 子句:保留任务负责让 ClickHouse 和 bucket 保持同步,而手动 TTL 只会删除其中一侧的数据。

根据实际使用情况选择保留期限。成本和质量审查通常针对几天前的数据,而不是几个月前的数据。对于小型团队,30 天是合理的起点;如果您只在出现故障时查看 trace,14 天就足够了。

第二类是 ClickHouse 自己的系统日志表。这一点常常令人意外,因为配置保留期限后磁盘仍会持续增长。ClickHouse 会为自身诊断写入 trace_logtext_logopentelemetry_span_logmetric_logasynchronous_metric_log,这些表默认没有 TTL,Langfuse 也不会读取它们。请先确认磁盘空间实际被什么占用。

SELECT table, formatReadableSize(size) AS size, rows FROM (
    SELECT table, database, sum(bytes) AS size, sum(rows) AS rows
    FROM system.parts
    WHERE active
    GROUP BY table, database
    ORDER BY size DESC
)

使用 docker compose exec clickhouse clickhouse-client --password "$CLICKHOUSE_PASSWORD" 运行它。如果系统表接近列表顶部,请通过配置覆盖文件禁用它们,因为 ClickHouse 启动时会将 /etc/clickhouse-server/config.d/ 中的每个文件合并到主配置上。

<clickhouse>
    <trace_log remove="1"/>
    <text_log remove="1"/>
    <opentelemetry_span_log remove="1"/>
    <asynchronous_metric_log remove="1"/>
    <metric_log remove="1"/>
</clickhouse>

挂载该文件,然后重启 ClickHouse。

services:
  clickhouse:
    volumes:
      - ./clickhouse-config.d/system-logs.xml:/etc/clickhouse-server/config.d/system-logs.xml:ro

这样会停止新的写入。磁盘上已有的行仍会保留,因此请使用 DROP TABLE IF EXISTS system.trace_log 显式回收空间;删除的每个表也都执行相同操作。如果您希望保留诊断数据,也可以不使用 remove="1",而是为每个表设置激进的 TTL;Langfuse 扩展文档对此有明确说明。

还有一张表值得了解。blob_storage_file_log 会记录上传到 bucket 的事件文件。如果您同时为 bucket 设置了生命周期策略,请为该表设置匹配的 TTL,避免两者逐渐产生偏差。

ALTER TABLE blob_storage_file_log MODIFY TTL created_at + INTERVAL 30 DAY DELETE;

同时在数据盘上设置一个简单的 df -h 告警。Trace 不会平稳增长。发布新 agent 的当天,数据量可能会突然增加;而数据摄取失败不应成为您发现这一问题的第一个信号。

备份 Postgres 和 ClickHouse

Langfuse 备份由三部分组成。Postgres 保存用户、组织、项目和 API 密钥。ClickHouse 保存追踪数据。MinIO 保存原始事件。只恢复 Postgres,您只能获得可用的登录功能,没有历史数据。只恢复 ClickHouse,您会得到无人能登录查看的历史数据。

Postgres 是普通的 pg_dump,这也是 Langfuse 备份文档推荐的方式。

docker compose exec -T postgres pg_dump -U postgres postgres \
  | gzip > langfuse-pg-$(date +%F).sql.gz

ClickHouse 需要更谨慎,因为在合并操作运行期间复制实时数据目录,无法得到一致的备份。在单台服务器上,最简单的方法是停止容器,然后归档卷。

docker compose stop clickhouse
docker volume ls | grep clickhouse
docker run --rm -v langfuse_langfuse_clickhouse_data:/data -v "$PWD":/backup alpine \
  tar czf /backup/langfuse-ch-$(date +%F).tar.gz -C /data .
docker compose start clickhouse

请使用 docker volume ls 输出的卷名,而不是 YAML 中写入的名称。该文件声明了 langfuse_clickhouse_data,Compose 会在其前面加上项目名称,因此位于名为 langfuse 的目录中的副本会生成 langfuse_langfuse_clickhouse_data。如果名称错误,docker run 会直接创建一个新的空卷,不会报告错误,您的归档文件也将不包含任何内容。

Web 容器会在 worker 处理每个传入事件之前,将其写入 bucket,因此短暂停止 ClickHouse 通常只会导致 worker 稍后重试。在业务低峰时执行,并尽量缩短停止时间。对于负载较高的实例,ClickHouse 自带的 BACKUP DATABASE default TO S3(...) 语句可以在不停止服务器的情况下创建一致的备份。MinIO 是第三个部分,使用 mc mirror 或将 MinIO 复制到服务器外的 bucket 即可覆盖这一部分。无论采用哪种方式,都应将备份移出服务器;这正是 VPS 上的加密 restic 备份 的用途。

Redis 不需要备份。它保存队列和缓存,因此丢失 Redis 只会导致当前正在处理的事件丢失,不会影响更早的数据。

一致性方面的限制确实存在,应该明确说明。Postgres 和 ClickHouse 在不同时间点导出,因此恢复后可能出现项目记录没有追踪数据,或追踪数据属于已不存在的项目。Langfuse 可以容忍这种情况,但应尽量在相近时间导出两者,并选择低流量时段执行。事件 bucket 才是真正的安全保障,因为 Langfuse 会在处理每个传入事件之前将其持久化到 bucket 中。

至少应将备份恢复到一次临时环境中。这样可以立即发现错误的卷名,而不是等到服务中断时才发现。

首先关注什么

第一周应优先关注以下四项。

  • 每条跟踪记录的成本。 Langfuse 根据模型名称和 token 使用量计算成本,因此应按成本对跟踪记录排序,并从头到尾阅读成本最高的一条。通常可以发现提示词不断膨胀:整个文档被粘贴到上下文中,或者对话历史从未裁剪。看清问题后,控制 AI agent 的成本就会从猜测变成工程任务。
  • 按输入和输出拆分的 token 使用量。 输入 token 数量多但价格低,输出 token 数量少但价格高,缓存的输入 token 价格更低。Claude Code 如何计算 token 使用量详细说明了相同的计费方式,该方式也适用于您自行编写的任何 agent。
  • 延迟百分位数。 中位数会掩盖问题。超时通常出现在 p95 和 p99,而在 agent 循环中,p95 时延的工具调用会随着迭代次数成倍增加。
  • 失败的工具调用。 按级别 ERROR 筛选观测记录。某个工具有 5% 的失败率时,聚合成功率通常无法体现这一问题,但在跟踪记录中会非常明显:您可以看到模型重试,然后消耗 token 绕过该工具。

设置保留时间窗口,并选择一个每周在部署当天检查的仪表板。没人打开的可观测性工具,最终只是一个会填满磁盘的数据库。

FAQ

自托管 Langfuse 需要多少内存?

建议准备 4 个 CPU 核心和 16 GiB 内存。这是 Langfuse Docker Compose 指南针对单台虚拟机的建议配置,此外还需要约 100 GiB 存储空间。已发布的组件最低配置要求是:ClickHouse 需要 8 GiB,web 和 worker 容器各需要 4 GiB;此外,Postgres、Redis 和 MinIO 也需要额外内存。8 GiB 可以运行一个开发者的实例。2 GiB 不够:内核会在后台合并期间终止 ClickHouse,并且 dmesg 会显示 Out of memory: Killed process

设置数据保留期限后,为什么 ClickHouse 磁盘仍会持续被占满?

保留设置只涵盖 Langfuse 自身的数据。ClickHouse 还会单独写入诊断表 trace_logtext_logopentelemetry_span_logmetric_logasynchronous_metric_log,这些表默认没有 TTL。按表对 system.parts 进行分组查询,查看哪个表占用空间最大;然后在 /etc/clickhouse-server/config.d/ 下的文件中通过 remove="1" 条目禁用未使用的表,重启 ClickHouse,再删除已有表,以回收已经占用的空间。

Langfuse 的最短数据保留期限是多少?

3 天。可以在项目设置中按项目配置保留期限,也可以通过 projects API 配置。每晚运行的任务会从 ClickHouse 和 blob storage 中删除早于该期限的 traces、observations、scores 和媒体资源。删除操作无法撤销。如果需要保留超过该期限的历史数据,请先配置 blob storage 导出。

是否必须同时备份 Postgres 和 ClickHouse?

是,因为两者保存的数据不同。Postgres 保存用户、组织、项目和 API keys,ClickHouse 保存实际的 trace 数据。仅恢复 Postgres 后,您可以登录实例,但其中没有任何数据。还应备份 MinIO bucket,因为其中保存了 Langfuse 接收后持久化的原始事件。在整个堆栈中,这些数据最接近源数据。

可以将现有的 OpenTelemetry 配置指向自托管 Langfuse 吗?

可以。Langfuse v4 及其 v4 SDK 基于 OpenTelemetry 构建,Anthropic 和 OpenAI OTel instrumentation 可以直接向其导出数据。在 Python 中运行 pip install langfuse opentelemetry-instrumentation-anthropic,在启动时调用一次 AnthropicInstrumentor().instrument(),并将 LANGFUSE_PUBLIC_KEYLANGFUSE_SECRET_KEYLANGFUSE_BASE_URL 设置为您自己的主机。先使用 langfuse.auth_check() 确认连接,再排查缺少 dashboard 的问题。