Como hospedar o Langfuse para rastrear agentes de IA
Hospede o Langfuse no seu VPS com recursos mínimos reais, imagens fixadas, TLS, retenção do ClickHouse para não lotar o disco e backups testados.
Por que rastrear um agente de IA
Você instala o Langfuse no seu próprio servidor para ver o que o agente realmente fez durante uma execução. O Langfuse é uma ferramenta open source de observabilidade de LLM (large language model). Regista cada prompt, cada resposta do modelo, cada chamada de ferramenta e cada token, e agrupa tudo num único trace que pode abrir e consultar. Ao executá-lo no seu próprio VPS, esses prompts nunca saem de um servidor que você controla.
O motivo é simples. Não pode corrigir um problema de custos ou de qualidade que não consegue ver. A fatura do fornecedor informa que terça-feira custou quatro vezes mais do que segunda-feira. Um trace informa qual execução do agente causou isso, qual prompt cresceu para 40,000 tokens e qual ciclo de novas tentativas foi executado nove vezes antes de desistir. A fatura fornece o número. O trace mostra o código que o produziu.
Este guia usa três termos. Um trace é uma execução completa do seu agente, do início ao fim. Uma observation é uma etapa dessa execução: um span para código comum ou uma generation para uma chamada a um modelo. Um score é um número associado a um trace, resultante de uma avaliação humana ou de um avaliador automatizado. O Langfuse utiliza OpenTelemetry (OTel), o padrão independente de fornecedor para tracing distribuído, por isso a instrumentação que já tem pode enviar dados para ele.
O que o self-hosting do Langfuse executa
O Langfuse v4 não é um único contentor. É composto por dois contentores de aplicação e quatro serviços de armazenamento. Num único VPS, os seis executam na sua máquina.
langfuse-webdisponibiliza a interface web e a API de ingestão.langfuse-workerprocessa a fila em segundo plano. Analisa os lotes de ingestão, calcula os custos e executa o trabalho noturno de retenção.- O Postgres armazena dados transacionais, como utilizadores, organizações, projetos, chaves de API e prompts.
- O ClickHouse armazena os próprios dados de traces, ou seja, observações e scores. É um armazenamento colunar concebido para consultas analíticas. Por isso, um dashboard sobre mais de cem milhões de linhas continua a responder rapidamente.
- O Redis é a fila e a cache entre o web e o worker.
- O MinIO fornece armazenamento de objetos compatível com S3 na máquina. Armazena todos os eventos recebidos em bruto e qualquer conteúdo multimédia anexado.
O Langfuse publica os recursos mínimos para os três componentes que executam o processamento.
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
}
]Só o ClickHouse requer 8 GiB de memória. O contentor web e o worker requerem 4 GiB cada. Estes são os valores mínimos publicados para os 3 componentes dimensionados pelo Langfuse. O Postgres, o Redis e o MinIO também precisam de memória adicional. O guia próprio de Docker Compose do projeto recomenda uma máquina com 4 cores, 16 GiB de memória e cerca de 100 GiB de armazenamento. Esse valor corresponde a essa conta, sem margem adicional.
Não tente executar isto num plano de 2 GiB. O ClickHouse inicia e aceita escritas durante algum tempo, mas depois termina durante uma mesclagem em segundo plano, porque uma mesclagem carrega partes grandes de uma tabela para a memória. Verá docker compose ps a indicar o contentor clickhouse como restarting, dmesg com uma linha semelhante a Out of memory: Killed process 1234 (clickhouse-serv), e todos os dashboards do Langfuse a devolverem 500. Com uma carga mais baixa, o ClickHouse recusa a consulta e regista DB::Exception: Memory limit (total) exceeded. Oito GiB é suficiente para um programador enviar alguns milhares de traces por dia. Planeie 16 GiB.
Implantar o Langfuse com Docker Compose
Clone o repositório. A stack, a ligação entre os serviços e o ambiente predefinido estão todos no seu docker-compose.yml.
git clone https://github.com/langfuse/langfuse.git
cd langfuseTodos os valores que tem de alterar estão marcados com # CHANGEME nesse ficheiro. Gere primeiro os três segredos da aplicação.
openssl rand -base64 32 # NEXTAUTH_SECRET
openssl rand -base64 32 # SALT
openssl rand -hex 32 # ENCRYPTION_KEYENCRYPTION_KEY tem de ter 256 bits, escritos como 64 caracteres hexadecimais. É exatamente isso que openssl rand -hex 32 imprime. Este valor cifra dados sensíveis em repouso, incluindo quaisquer chaves de fornecedores de LLM que armazene na instância. Se o alterar depois de existirem dados, essas linhas deixam de poder ser decifradas. Por isso, trate-o como permanente desde o primeiro arranque. SALT é usado para calcular o hash das suas chaves da API do Langfuse. Alterá-lo invalida todas as chaves que os seus agentes já utilizam.
Defina depois POSTGRES_PASSWORD, CLICKHOUSE_PASSWORD, REDIS_AUTH e MINIO_ROOT_PASSWORD. A palavra-passe do MinIO aparece em quatro locais: uma vez como MINIO_ROOT_PASSWORD e depois novamente como LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY, LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY e LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY. Se omitir uma ocorrência, o MinIO rejeita esse cliente com SignatureDoesNotMatch. Essa mensagem aparece no log do worker, enquanto a interface Web continua a parecer saudável. Manter estes valores num ficheiro env, em vez de os colocar no ficheiro Compose versionado, é o padrão descrito em ficheiros env e segredos do Docker Compose.
Fixar as tags das imagens antes de começar
O ficheiro fornecido usa langfuse/langfuse:4 e langfuse/langfuse-worker:4. Essas tags mudam. O Langfuse executa automaticamente as migrações do Postgres e do ClickHouse no arranque. Por isso, um docker compose pull de rotina meses mais tarde pode tornar-se uma migração de esquema não planeada numa base de dados que não tinha uma cópia de segurança nessa manhã. Fixe ambas as imagens numa única release através de um docker-compose.override.yml. O Compose aplica esse ficheiro sobre o ficheiro fornecido, para que um git pull posterior não substitua as suas alterações.
services:
langfuse-web:
image: docker.io/langfuse/langfuse:4.3.1
langfuse-worker:
image: docker.io/langfuse/langfuse-worker:4.3.1A versão 4.3.1 era a versão atual da série 4.3 em agosto de 2026 (a versão 4.4.0 foi lançada entretanto). Consulte a página de releases do projeto no GitHub, fixe a versão que estiver atual no dia da implementação e altere esse número de forma deliberada. As imagens de armazenamento no ficheiro fornecido já estão fixadas nas versões principais, postgres:17, clickhouse-server:25.12 e redis:7. Devem receber o mesmo tratamento.
Inicie a stack.
docker compose up -d
docker compose ps
docker compose logs -f langfuse-workerO primeiro arranque executa as migrações. Aguarde um ou dois minutos antes de testar as respostas. docker compose ps deve apresentar seis serviços no estado running. Se o worker reiniciar continuamente, o respetivo log mostra o motivo: CLICKHOUSE_MIGRATION_URL utiliza o protocolo nativo do ClickHouse na porta 9000, não a porta HTTP 8123. Apontá-lo para 8123 falha nesse ponto, enquanto o contentor Web continua a parecer saudável.
Verifique o estado a partir do próprio servidor.
curl -s "http://localhost:3000/api/public/health?failIfDatabaseUnavailable=true"
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3000/api/public/readyUma chamada /api/public/health simples apenas prova que o processo da API está ativo. Ela ignora deliberadamente a base de dados, para que o serviço continue a responder quando o Postgres tem uma interrupção breve. A forma failIfDatabaseUnavailable=true é a adequada para um monitorizar, e devolve 503 quando a base de dados está inacessível. /api/public/ready devolve 200 quando as migrações terminam e o contentor aceita tráfego. Ambas são verificações HTTP normais. Por isso, uma página de estado do Uptime Kuma pode monitorizá-las e indicar que a stack está indisponível antes dos seus agentes.
Coloque o TLS à frente e feche as portas adicionais
O ficheiro Compose fornecido publica 3000:3000 para o contentor web e 9090:9000 para o MinIO. Ambos ficam associados a todas as interfaces. Num IP público, isso significa que qualquer pessoa que faça uma varredura à porta 3000 chega à página de registo, e qualquer pessoa que faça uma varredura à porta 9090 está a comunicar com o bucket que contém os seus prompts brutos.
Uma regra de firewall, por si só, não as fecha. O Docker escreve as suas próprias regras DNAT na tabela nat, e essas regras são avaliadas antes de o ufw chegar a ver o pacote através das regras de filtro. Por isso, ufw deny 3000 deixa a porta publicada aberta. Este problema é suficientemente comum para ter um guia próprio: porque as portas publicadas pelo Docker ignoram o ufw. Em vez disso, associe a porta ao loopback no seu ficheiro de substituição.
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 tem de ser o endereço público exato, incluindo o esquema, porque o fluxo de autenticação constrói o URL de callback a partir desse valor. Se o deixar como http://localhost:3000 atrás de um proxy HTTPS, o percurso de início de sessão redireciona o browser para um endereço a que não consegue aceder.
Agora aponte um reverse proxy para 127.0.0.1:3000 e deixe-o gerir o certificado. O Traefik no mesmo projeto Compose é a escolha habitual, e os labels de encaminhamento são os descritos em executar várias aplicações atrás de um reverse proxy Traefik. O Caddy faz o mesmo em duas linhas se o Langfuse for a única aplicação no servidor. Verifique com curl -sI https://langfuse.example.com/api/public/ready e confirme a partir de uma segunda máquina que curl http://YOUR_IP:3000 agora termina por timeout.
Há uma ressalva relativa ao MinIO. O Langfuse disponibiliza os ficheiros multimédia anexados ao browser através de URLs pré-assinados que apontam para esse endpoint S3. Por isso, se utilizar traces multimodais com imagens ou áudio, um MinIO acessível apenas pelo loopback fará com que esses anexos não sejam carregados. Consulte a página de configuração do armazenamento de blobs antes de o colocar atrás de um proxy, porque o endpoint escrito no URL pré-assinado tem de corresponder ao endereço que publica. Os traces de texto simples não são afetados.
Crie a sua conta na primeira visita e mantenha a instância sob o seu controlo. Defina LANGFUSE_ALLOWED_ORGANIZATION_CREATORS com o seu próprio endereço de email, para que um desconhecido que chegue à página não possa criar uma organização no seu servidor.
Envie o seu primeiro trace
Crie um projeto na interface web e copie as chaves pública e secreta nas definições do projeto. O SDK Python lê três variáveis de ambiente.
export LANGFUSE_PUBLIC_KEY="pk-lf-..."
export LANGFUSE_SECRET_KEY="sk-lf-..."
export LANGFUSE_BASE_URL="https://langfuse.example.com"LANGFUSE_BASE_URL é o nome da variável no SDK v4, lançado em março de 2026. O código e os guias mais antigos usam LANGFUSE_HOST. Se os seus traces estiverem a chegar ao Langfuse Cloud em vez do seu servidor, a causa é a URL base não definida, porque o valor predefinido aponta para a instância alojada.
pip install langfuse opentelemetry-instrumentation-anthropic anthropicimport 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()O decorador @observe abre uma observação em torno da função, captura os argumentos e o valor devolvido e aninha-a na observação que já estiver ativa. AnthropicInstrumentor é a instrumentação OpenTelemetry para o cliente Anthropic e transforma cada chamada messages.create numa generation que inclui o nome do modelo, a utilização de tokens e a latência, sem alterar o local da chamada.
Duas chamadas fazem as verificações por si. langfuse.auth_check() devolve False quando as chaves são inválidas ou a URL base está errada, o que é mais rápido do que tentar perceber por que motivo o dashboard está vazio. langfuse.flush() bloqueia até os spans em fila serem enviados. Os processos de curta duração precisam desta chamada, porque o SDK agrupa os spans em segundo plano e um script que termina imediatamente leva consigo o lote que ainda não foi enviado.
Por que o ClickHouse continua a crescer?
Os traces são os dados que mais crescem na maioria das instalações self-hosted. Cada execução de um agente grava uma linha por etapa, e as entradas e saídas são armazenadas integralmente. Por isso, um agente que gera muitas mensagens e usa prompts longos produz muito mais bytes por dia do que a aplicação que está a monitorizar. Se nada for feito, o ClickHouse enche o disco. Quando o disco fica cheio, a ingestão para em vez de apenas ficar mais lenta.
Aqui crescem duas coisas distintas, e cada uma precisa de uma correção diferente.
A primeira são os seus próprios dados de traces. A correção é configurar a retenção. Abra as definições do projeto na interface web e defina um período de retenção em dias. O Langfuse aceita um mínimo de 3 dias. Todas as noites, uma tarefa seleciona traces, observações, scores e ativos multimédia mais antigos do que esse período e elimina-os do ClickHouse e do armazenamento de blobs. A tarefa precisa da permissão DeleteObject no bucket. As credenciais root do MinIO no ficheiro compose predefinido já têm essa permissão. A eliminação é permanente. Por isso, configure primeiro uma exportação para o armazenamento de blobs se precisar de manter o histórico a longo prazo. Não escreva manualmente cláusulas TTL nas próprias tabelas do Langfuse. A tarefa de retenção mantém o ClickHouse e o bucket sincronizados, enquanto um TTL manual elimina os dados apenas de um dos lados.
Escolha o período com base na utilização real. A análise de custos e qualidade é feita com dados de alguns dias, não de vários meses. Trinta dias é um ponto de partida razoável para uma equipa pequena. 14 dias são suficientes se só abrir um trace quando algo falha.
A segunda coisa são as próprias tabelas de logs do sistema do ClickHouse. Isto surpreende algumas pessoas, porque o disco continua a crescer depois de a retenção ser configurada. O ClickHouse grava trace_log, text_log, opentelemetry_span_log, metric_log e asynchronous_metric_log para os seus próprios diagnósticos. Estas tabelas são fornecidas sem TTL, e o Langfuse nunca as lê. Primeiro, descubra para onde o espaço do disco foi realmente utilizado.
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
)Execute-o com docker compose exec clickhouse clickhouse-client --password "$CLICKHOUSE_PASSWORD". Se as tabelas do sistema estiverem perto do topo, desative-as com um overlay de configuração. O ClickHouse combina todos os ficheiros em /etc/clickhouse-server/config.d/ com a configuração principal no arranque.
<clickhouse>
<trace_log remove="1"/>
<text_log remove="1"/>
<opentelemetry_span_log remove="1"/>
<asynchronous_metric_log remove="1"/>
<metric_log remove="1"/>
</clickhouse>Monte-o e reinicie o ClickHouse.
services:
clickhouse:
volumes:
- ./clickhouse-config.d/system-logs.xml:/etc/clickhouse-server/config.d/system-logs.xml:roIsto impede novas gravações. As linhas que já estão no disco permanecem lá. Por isso, recupere o espaço explicitamente com DROP TABLE IF EXISTS system.trace_log e faça o mesmo para cada tabela removida. Se preferir manter os diagnósticos, a alternativa é configurar um TTL agressivo em cada tabela em vez de remove="1", conforme descrito na documentação de escalabilidade do Langfuse.
Vale a pena conhecer mais uma tabela. blob_storage_file_log regista os ficheiros de eventos enviados para o bucket. Se também configurar uma política de ciclo de vida no bucket, defina um TTL correspondente para a tabela, para que os dois não fiquem dessincronizados.
ALTER TABLE blob_storage_file_log MODIFY TTL created_at + INTERVAL 30 DAY DELETE;Configure também um alerta simples df -h no disco de dados. Os traces não crescem de forma uniforme. Crescem no dia em que lança um agente novo, e o primeiro sinal disso não deve ser a falha da ingestão.
Faça backup do Postgres e do ClickHouse
Um backup do Langfuse tem três partes. O Postgres armazena os utilizadores, as organizações, os projetos e as chaves de API. O ClickHouse armazena os traces. O MinIO armazena os eventos brutos. Se restaurar apenas o Postgres, terá um login funcional, mas sem histórico. Se restaurar apenas o ClickHouse, terá histórico ao qual ninguém conseguirá aceder.
O Postgres é um pg_dump simples, que é o método recomendado na documentação de backup do Langfuse.
docker compose exec -T postgres pg_dump -U postgres postgres \
| gzip > langfuse-pg-$(date +%F).sql.gzO ClickHouse exige mais cuidado, porque copiar um diretório de dados ativo enquanto as operações de merge estão em execução não produz um backup consistente. A abordagem simples num único servidor consiste em parar o container e arquivar o volume.
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 clickhouseUse o nome do volume apresentado por docker volume ls, e não o nome escrito no YAML. O ficheiro declara langfuse_clickhouse_data, e o Compose acrescenta o nome do projeto como prefixo. Assim, um clone num diretório chamado langfuse produz langfuse_langfuse_clickhouse_data. Se errar esse nome, docker run cria um novo volume vazio sem emitir nenhum erro, e o arquivo não contém dados.
O container web grava cada evento recebido no bucket antes de o worker o processar. Por isso, uma paragem curta do ClickHouse normalmente faz apenas com que o worker tente processar os eventos novamente depois. Faça isto num período de pouco tráfego e mantenha a paragem curta. Numa instância com mais tráfego, a instrução BACKUP DATABASE default TO S3(...) do próprio ClickHouse cria um backup consistente sem parar o servidor. O MinIO é a terceira peça, e mc mirror ou a replicação do MinIO para um bucket externo cobre essa parte. Seja qual for o método utilizado, retire o backup do servidor. É esse o objetivo de backups restic encriptados numa VPS.
Não é necessário fazer backup do Redis. Ele armazena a fila e a cache. Se o perder, perderá os eventos que estiverem a ser processados naquele momento, mas nada mais antigo.
A ressalva sobre consistência é real e deve ser explicitada. O Postgres e o ClickHouse são exportados em momentos diferentes. Por isso, uma restauração pode deixar uma linha de projeto sem traces ou traces associados a um projeto que já não existe. O Langfuse tolera esta situação, mas faça ambos os dumps com pouca diferença de tempo e durante um período de pouco tráfego. O bucket de eventos é a verdadeira rede de segurança, porque o Langfuse persiste cada evento recebido nesse bucket antes de o processar.
Restaure os dados numa stack de teste pelo menos uma vez. Assim, descobrirá agora que o nome do volume está errado, em vez de descobrir isso durante uma indisponibilidade.
O que analisar primeiro
Quatro aspetos justificam a sua presença na primeira semana.
- Custo por trace. O Langfuse calcula o custo com base no nome do modelo e no consumo de tokens. Ordene os traces por custo e leia o mais caro do início ao fim. Normalmente, a causa é um prompt que cresceu: um documento inteiro colado no contexto ou um histórico de conversação que ninguém reduz. Quando conseguir ver esse problema, controlar quanto custa um agente de IA passa a ser uma tarefa de engenharia, e não uma estimativa.
- Consumo de tokens separado entre entrada e saída. Os tokens de entrada são numerosos e baratos, os tokens de saída são poucos e caros, e os tokens de entrada em cache são ainda mais baratos. A mesma contabilidade é explicada em como é contabilizado o consumo de tokens do Claude Code e aplica-se a qualquer agente que escreva.
- Percentis de latência. A mediana oculta o problema. É nos p95 e p99 que ocorrem os timeouts. Num ciclo de agente, uma chamada lenta a uma ferramenta no p95 é multiplicada pelo número de iterações.
- Chamadas de ferramentas com falhas. Filtre as observações pelo nível
ERROR. Uma ferramenta que falha 5% das vezes não aparece numa taxa de sucesso agregada, mas fica evidente nos traces, onde é possível monitorizar o modelo a repetir a chamada e a consumir tokens para contornar o problema.
Defina a janela de retenção e escolha o dashboard que irá consultar semanalmente, sempre no mesmo dia em que faz o deploy. Uma ferramenta de observabilidade que ninguém abre é uma base de dados que acaba por encher um disco.
FAQ
Quanta memória é necessária para uma instalação self-hosted do Langfuse?
Planeie 4 núcleos de CPU e 16 GiB de memória, que é o recomendado pelo guia Docker Compose do Langfuse para uma única máquina virtual, além de cerca de 100 GiB de armazenamento. Os requisitos mínimos publicados são 8 GiB para o ClickHouse e 4 GiB para cada contentor web e worker. O Postgres, o Redis e o MinIO também precisam de memória adicional. Oito GiB são suficientes para a instância de um programador. Dois GiB não são suficientes: o kernel termina o ClickHouse durante as compactações em segundo plano, e dmesg mostra Out of memory: Killed process.
Porque é que o disco do ClickHouse continua a encher depois de configurar a retenção de dados?
A configuração de retenção aplica-se apenas aos dados próprios do Langfuse. O ClickHouse escreve separadamente as tabelas de diagnóstico trace_log, text_log, opentelemetry_span_log, metric_log e asynchronous_metric_log, que não têm TTL configurado. Consulte system.parts, agrupado por tabela, para identificar a maior tabela. Em seguida, desative as tabelas não utilizadas com uma entrada remove="1" num ficheiro em /etc/clickhouse-server/config.d/, reinicie o ClickHouse e elimine as tabelas existentes para recuperar o espaço já ocupado.
Qual é o período mínimo de retenção de dados no Langfuse?
Três dias. A retenção é configurada por projeto nas definições do projeto ou através da API de projetos. Um trabalho noturno elimina do ClickHouse e do armazenamento de blobs os traces, as observações, as pontuações e os recursos multimédia mais antigos do que esse período. A eliminação não pode ser anulada. Configure primeiro uma exportação para armazenamento de blobs se precisar de conservar o histórico para além desse período.
Tenho de fazer cópias de segurança do Postgres e do ClickHouse?
Sim, porque armazenam dados diferentes. O Postgres armazena utilizadores, organizações, projetos e chaves de API. O ClickHouse armazena os próprios dados dos traces. Uma restauração apenas do Postgres fornece uma instância na qual é possível iniciar sessão, mas sem dados. Faça também uma cópia de segurança do bucket do MinIO, porque armazena os eventos brutos que o Langfuse persiste quando os recebe. No stack, estes eventos são o elemento mais próximo de uma fonte de verdade.
Posso apontar uma configuração OpenTelemetry existente para uma instalação self-hosted do Langfuse?
Sim. O Langfuse v4 e os respetivos SDKs v4 baseiam-se em OpenTelemetry, e as instrumentações OTel do Anthropic e do OpenAI exportam diretamente para ele. Em Python, execute pip install langfuse opentelemetry-instrumentation-anthropic, chame AnthropicInstrumentor().instrument() uma vez no arranque e defina LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY e LANGFUSE_BASE_URL para o seu próprio host. Confirme com langfuse.auth_check() antes de procurar a causa de um dashboard em falta.