Self-HostingDockerLLM

Langfuse self-hosted with Docker: trace, debug and evaluate your LLM apps (Linux, Windows, Mac)

Before you start

  • 19 min read

Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#

Langfuse is an open-source LLM engineering platform: it records every call your AI app makes (prompt, answer, cost, time), lets you manage prompts and scores the quality, and you can run all of it on your own machine.

Langfuse official logo

Logo: the official Langfuse project (github.com/langfuse/langfuse, file web/public/icon.svg at v4.70.1)

Think of it as a flight recorder for your chatbot, RAG pipeline or agent. When an answer is wrong, you open the trace and see which prompt, which documents and which model call produced it. Your prompts and answers stay on your own server.

At a glance#

  • What you get: a web UI with traces, token and cost numbers, sessions, a prompt library with versions, datasets and evaluation scores.
  • Time needed: about 20 minutes, plus 2 to 3 minutes for the first start.
  • Difficulty: medium. Six containers, but you copy one Compose file and one secrets script.
  • You need: Docker with the Compose plugin, 4 CPU cores and 16 GB RAM recommended by the docs, and a browser.
WhatValue
Current versionv4.70.1, released 26 Oct 2026 (GitHub releases)
Pinned images (official, Docker Hub)langfuse/langfuse:4.70.1 and langfuse/langfuse-worker:4.70.1
Other imagesClickHouse 25.12, Postgres 17, Redis 7, MinIO
Ports3000 (web and API), 9090 (MinIO, for media uploads)
Official docslangfuse.com/self-hosting

Why this version? Langfuse ships almost daily (v4.69.0 on 23 Oct, v4.70.0 on 24 Oct, v4.70.1 on 26 Oct), so we pin one exact tag and you update on purpose. At the time of writing our research service had not yet returned the community breakage check, so we only checked that the tag exists on Docker Hub and that the official Compose file at this tag matches this guide. If a release misbehaves for you, set the tag to the previous release (for example 4.70.0).

Already running a gateway? Our LiteLLM guide shows how to put one OpenAI-compatible endpoint in front of your models; this guide shows how to watch it.

How it fits together#

Your app sends traces to langfuse-web, which stores the raw events and queues them; langfuse-worker takes them off the queue and writes them into ClickHouse, where the UI reads them.

How a trace travels through Langfuse: your LLM app and your browser talk to langfuse-web; web saves raw events in MinIO, queues jobs in Redis and keeps settings in Postgres; langfuse-worker takes jobs from Redis and writes traces into ClickHouse

Remember three things:

  1. Only langfuse-web (port 3000) needs to be reachable by your apps and your browser.
  2. Postgres, ClickHouse, Redis and MinIO are private. The official file already binds most of them to 127.0.0.1.
  3. Traces are written in the background, so they show up a few seconds after the call.

Before you start#

You need Docker Compose and enough memory. ClickHouse is the hungry part.

SystemWhat to install
LinuxDocker Engine + Compose plugin, openssl
WindowsDocker Desktop (WSL2 backend), PowerShell. Give WSL2 enough RAM in .wslconfig
MacDocker Desktop, Terminal. Allow at least 8 GB for Docker in its settings
Run in terminalbash
docker --version
docker compose version

Checklist:

  • [ ] Port 3000 is free
  • [ ] You have 16 GB RAM free for a comfortable setup (docs recommendation for a VM)
  • [ ] You can save secrets in a .env file that only you can read

Tested on 2026-10-26: not run. Commands checked against the official Langfuse docs and the official docker-compose.yml at tag v4.70.1.

Step 1: Create the folder and the secrets#

The official file marks every password with # CHANGEME. We move them all into one .env file and generate each with a random generator.

Here is the full list of secrets from the official file, and what we do with each:

Secret in the official fileHow to make itIn our .env
NEXTAUTH_SECRET (default mysecret)openssl rand -base64 32NEXTAUTH_SECRET
SALT (default mysalt)openssl rand -base64 32SALT
ENCRYPTION_KEY (64 zeros)openssl rand -hex 32 (must be 64 hex characters)ENCRYPTION_KEY
DATABASE_URL (password postgres) and POSTGRES_PASSWORDopenssl rand -hex 24POSTGRES_PASSWORD (the URL is built from it)
CLICKHOUSE_PASSWORD (clickhouse)openssl rand -hex 24CLICKHOUSE_PASSWORD
REDIS_AUTH (myredissecret)openssl rand -hex 24REDIS_AUTH
MINIO_ROOT_PASSWORD and the three LANGFUSE_S3_*_SECRET_ACCESS_KEY (miniosecret)openssl rand -hex 24MINIO_ROOT_PASSWORD (all three S3 secrets reuse it)

Never change ENCRYPTION_KEY later: it encrypts the LLM API keys you save in the UI.

Linux#

Run in terminalbash
mkdir -p ~/docker_volumes/langfuse && cd ~/docker_volumes/langfuse
cat > .env <<EOF
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=$(openssl rand -base64 32)
SALT=$(openssl rand -base64 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
POSTGRES_PASSWORD=$(openssl rand -hex 24)
CLICKHOUSE_PASSWORD=$(openssl rand -hex 24)
REDIS_AUTH=$(openssl rand -hex 24)
MINIO_ROOT_PASSWORD=$(openssl rand -hex 24)
EOF
chmod 600 .env

Windows (PowerShell)#

Windows has no openssl by default, so we use .NET's random generator. It works in Windows PowerShell and PowerShell 7.

Codepowershell
$d = "$HOME\docker_volumes\langfuse"
New-Item -ItemType Directory -Force $d | Out-Null
Set-Location $d
function Hex($n) { $b = New-Object byte[] $n; [Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($b); ($b | ForEach-Object { $_.ToString('x2') }) -join '' }
function B64($n) { $b = New-Object byte[] $n; [Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($b); [Convert]::ToBase64String($b) }
@(
 "NEXTAUTH_URL=http://localhost:3000",
 "NEXTAUTH_SECRET=$(B64 32)",
 "SALT=$(B64 32)",
 "ENCRYPTION_KEY=$(Hex 32)",
 "POSTGRES_PASSWORD=$(Hex 24)",
 "CLICKHOUSE_PASSWORD=$(Hex 24)",
 "REDIS_AUTH=$(Hex 24)",
 "MINIO_ROOT_PASSWORD=$(Hex 24)"
) | Set-Content -Encoding ascii .env

Mac (Terminal)#

Same commands as Linux (macOS ships openssl).

Check it worked#

Run in terminalbash
grep -c "=" .env

Expected: 8. On Windows use (Get-Content .env).Count. Do not print the file in a shared screen.

Step 2: Save the Compose file#

This file is the official one at v4.70.1, trimmed to what a first install needs, with exact image tags, the secrets taken from .env, and the web port bound to localhost.

Everything we removed is an empty optional setting (SMTP, cloud object storage, AI features, allow-lists). Add them back from the official file if you need them. Open an editor in the same folder, paste the file below and save it as docker-compose.yml:

Linux#

Run in terminalbash
cd ~/docker_volumes/langfuse && nano docker-compose.yml

Windows (PowerShell)#

Codepowershell
Set-Location $HOME\docker_volumes\langfuse; notepad docker-compose.yml

Mac (Terminal)#

Same command as Linux.

The Compose file#

File contentyaml
name: langfuse

services:
  langfuse-worker:
    image: langfuse/langfuse-worker:4.70.1
    restart: unless-stopped
    depends_on: &langfuse-depends-on
      postgres:
        condition: service_healthy
      minio:
        condition: service_healthy
      redis:
        condition: service_healthy
      clickhouse:
        condition: service_healthy
    ports:
      - 127.0.0.1:3030:3030
    environment: &langfuse-env
      NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000}
      DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:?set it in .env}@postgres:5432/postgres
      SALT: ${SALT:?set it in .env}
      ENCRYPTION_KEY: ${ENCRYPTION_KEY:?set it in .env}
      TELEMETRY_ENABLED: ${TELEMETRY_ENABLED:-true}
      CLICKHOUSE_MIGRATION_URL: clickhouse://clickhouse:9000
      CLICKHOUSE_URL: http://clickhouse:8123
      CLICKHOUSE_USER: clickhouse
      CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:?set it in .env}
      CLICKHOUSE_CLUSTER_ENABLED: "false"
      LANGFUSE_S3_EVENT_UPLOAD_BUCKET: langfuse
      LANGFUSE_S3_EVENT_UPLOAD_REGION: auto
      LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: minio
      LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD:?set it in .env}
      LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: http://minio:9000
      LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: "true"
      LANGFUSE_S3_EVENT_UPLOAD_PREFIX: events/
      LANGFUSE_S3_MEDIA_UPLOAD_BUCKET: langfuse
      LANGFUSE_S3_MEDIA_UPLOAD_REGION: auto
      LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID: minio
      LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD:?set it in .env}
      LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT: http://minio:9000
      LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE: "true"
      LANGFUSE_S3_MEDIA_UPLOAD_PREFIX: media/
      REDIS_HOST: redis
      REDIS_PORT: "6379"
      REDIS_AUTH: ${REDIS_AUTH:?set it in .env}
      REDIS_TLS_ENABLED: "false"

  langfuse-web:
    image: langfuse/langfuse:4.70.1
    restart: unless-stopped
    depends_on: *langfuse-depends-on
    ports:
      - 127.0.0.1:3000:3000
    environment:
      <<: *langfuse-env
      NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:?set it in .env}
      LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT: http://localhost:9090
      LANGFUSE_S3_MEDIA_UPLOAD_INTERNAL_ENDPOINT: http://minio:9000

  clickhouse:
    image: docker.io/clickhouse/clickhouse-server:25.12
    restart: unless-stopped
    user: "101:101"
    environment:
      CLICKHOUSE_DB: default
      CLICKHOUSE_USER: clickhouse
      CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:?set it in .env}
    volumes:
      - langfuse_clickhouse_data:/var/lib/clickhouse
      - langfuse_clickhouse_logs:/var/log/clickhouse-server
    ports:
      - 127.0.0.1:8123:8123
      - 127.0.0.1:9000:9000
    healthcheck:
      test: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 1s

  minio:
    image: cgr.dev/chainguard/minio
    restart: unless-stopped
    entrypoint: sh
    command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data'
    environment:
      MINIO_ROOT_USER: minio
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?set it in .env}
    ports:
      - 127.0.0.1:9090:9000
      - 127.0.0.1:9091:9001
    volumes:
      - langfuse_minio_data:/data
    healthcheck:
      test: ["CMD", "mc", "ready", "local"]
      interval: 1s
      timeout: 5s
      retries: 5
      start_period: 1s

  redis:
    image: docker.io/redis:7
    restart: unless-stopped
    command: >
      --requirepass ${REDIS_AUTH:?set it in .env}
      --maxmemory-policy noeviction
    ports:
      - 127.0.0.1:6379:6379
    volumes:
      - langfuse_redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 3s
      timeout: 10s
      retries: 10

  postgres:
    image: docker.io/postgres:17
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 3s
      timeout: 3s
      retries: 10
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set it in .env}
      POSTGRES_DB: postgres
      TZ: UTC
      PGTZ: UTC
    ports:
      - 127.0.0.1:5432:5432
    volumes:
      - langfuse_postgres_data:/var/lib/postgresql/data

volumes:
  langfuse_postgres_data:
  langfuse_clickhouse_data:
  langfuse_clickhouse_logs:
  langfuse_minio_data:
  langfuse_redis_data:

What we changed from the official file, and why:

ChangeReason
Exact image tags 4.70.1 (official: floating 4)You decide when to update
127.0.0.1:3000 and 127.0.0.1:9090 (official: all interfaces)Nothing is public until you add HTTPS. If other machines must send traces, see the HTTPS section
restart: unless-stopped (official: always)Same house style as our other guides
${VAR:?...} for every secretCompose refuses to start with a missing secret, instead of silently using postgres or mysecret
Data in named Docker volumes (as in the official file)ClickHouse and MinIO run as their own users; named volumes avoid permission trouble. The folder ~/docker_volumes/langfuse holds the Compose file, .env and backups

Check it worked#

Run in terminalbash
cd ~/docker_volumes/langfuse
docker compose config --quiet && echo OK

On Windows run the same two commands in PowerShell after Set-Location $HOME\docker_volumes\langfuse (use ; if ($?) { "OK" }). Expected: OK. If a secret is missing, Compose stops with an error that names it.

Step 3: Start Langfuse#

Linux#

Run in terminalbash
docker compose up -d
docker compose logs -f langfuse-web

Windows (PowerShell)#

Run in terminalpowershell
docker compose up -d
docker compose logs -f langfuse-web

Mac (Terminal)#

Same commands as Linux.

Check it worked#

Wait 2 to 3 minutes. The docs say langfuse-web then logs "Ready" (press Ctrl+C to leave the log). Then:

Run in terminalbash
docker compose ps

Expected: six services running, and postgres, redis, clickhouse and minio marked healthy. Open http://localhost:3000: you see the Langfuse sign-in page.

First run: your account and API keys#

There is no default login. The first account you create becomes your user, then you create an organization and a project, and the project gives you the API keys.

  1. Open http://localhost:3000 and click Sign up. Use your email and a strong password.
  2. Create an organization, then a project.
  3. Open Settings > API Keys > Create new API key.
  4. Copy the public key (pk-lf-...) and the secret key (sk-lf-...). The secret key is shown once.

Want no clicking? "Headless initialization" creates the organization, project, keys and first user from LANGFUSE_INIT_* variables on langfuse-web (see the docs). After your own account exists you can also stop strangers from signing up: set AUTH_DISABLE_SIGNUP=true on langfuse-web (listed in the configuration reference; VERIFY the name in your version).

Check it worked#

You see an empty project with a Tracing page and your keys saved in a password manager.

Step 5: Send your first trace from Python#

Install the Langfuse SDK, give it your keys and your server address, and swap one import.

Linux#

Run in terminalbash
python3 -m venv .venv && source .venv/bin/activate
pip install langfuse openai
export LANGFUSE_PUBLIC_KEY="<YOUR_PUBLIC_KEY>"
export LANGFUSE_SECRET_KEY="<YOUR_SECRET_KEY>"
export LANGFUSE_BASE_URL="http://localhost:3000"

Windows (PowerShell)#

Run in terminalpowershell
py -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install langfuse openai
$env:LANGFUSE_PUBLIC_KEY = "<YOUR_PUBLIC_KEY>"
$env:LANGFUSE_SECRET_KEY = "<YOUR_SECRET_KEY>"
$env:LANGFUSE_BASE_URL = "http://localhost:3000"

Mac (Terminal)#

Same commands as Linux.

Save this as trace_test.py. The Langfuse OpenAI wrapper is a drop-in replacement for the OpenAI client. It works with any OpenAI-compatible server, for example a local Ollama or LiteLLM (set base_url):

Run in terminalpython
from langfuse import get_client
from langfuse.openai import OpenAI  # drop-in for openai.OpenAI

client = OpenAI(base_url="http://localhost:4000/v1", api_key="<YOUR_LLM_KEY>")  # your OpenAI-compatible endpoint

reply = client.chat.completions.create(
    model="<your-model-name>",
    messages=[{"role": "user", "content": "Say hello in five words."}],
)
print(reply.choices[0].message.content)
get_client().flush()  # send traces before the script exits
Run in terminalbash
python trace_test.py

Check it worked#

Open Tracing in your project. A trace with the prompt, the answer, the token counts and the time appears within a few seconds. If nothing shows up, see Troubleshooting.

Send traces from LiteLLM, n8n and Open WebUI#

Pick the layer you already have: a gateway, a workflow tool or a chat UI.

ToolHowNotes
LiteLLM Proxycallbacks: ["langfuse_otel"] under litellm_settings, plus LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY and LANGFUSE_OTEL_HOST as environment variablesSet LANGFUSE_OTEL_HOST to your server, for example http://localhost:3000. If LiteLLM runs in Docker, localhost is the container: use your machine's address or put both on one Docker network
n8nNo native tracing integration. Use a prompt-management node, or send LLM calls through a gateway (LiteLLM) or OpenRouter's Broadcast featurePer the Langfuse n8n page, read 2026-10-26
Open WebUIThe Langfuse docs have an Open WebUI integration pageFollow that page for your Open WebUI version (VERIFY)

LiteLLM config snippet (from the official Langfuse LiteLLM page):

File contentyaml
litellm_settings:
  callbacks: ["langfuse_otel"]

Because LiteLLM sits in front of every model, one callback traces them all. That is the cheapest way to get coverage across n8n, scripts and chat tools.

Prompts and evaluations#

Prompt management keeps your prompts out of your code and versioned; evaluations score the answers.

  • Prompt management: create a prompt in the UI (Prompts > New prompt), label a version production, and fetch it from code with get_client().get_prompt("<name>"). Changing the prompt no longer needs a redeploy, and every trace shows which prompt version produced it.
  • Scores: attach a number or label to a trace from code, from the UI (human annotation) or with an LLM-as-a-judge evaluator.
  • Datasets and experiments: save test inputs with expected outputs, run your app over them, and compare runs side by side after each change.
  • LLM-as-a-judge needs a model connection: add your own API key under Settings > LLM Connections. It is stored encrypted with your ENCRYPTION_KEY.

For RAG apps, trace the retrieval step too, so you can see which chunks the model was given when an answer is wrong.

Docs: prompt management, evaluation.

How Langfuse compares#

Langfuse is the open-source, self-hostable all-rounder; LangSmith is a hosted product from the LangChain team; Phoenix is a lighter open-source tool focused on tracing and evaluation.

LangfuseLangSmithArize Phoenix
Self-hostYes, Docker Compose or KubernetesEnterprise option (check with the vendor)Yes, single container
Prompt managementBuilt inBuilt inAvailable
EvaluationsDatasets, scores, LLM judgeYesYes
Stack to runSix containersn/a (hosted)Fewer parts
Best forTeams wanting one open platform on their own hardwareLangChain-centred teamsQuick local debugging

The Langfuse column comes from its docs. The other two columns are our general summary, not checked against their current docs on this date. Check them before you choose.

Put it behind HTTPS (with or without Traefik)#

Use this when other machines must reach Langfuse, or you want a proper address. Running only on one computer? Skip it: http://localhost:3000 is fine.

Before you start: set NEXTAUTH_URL in .env to your final address, for example https://langfuse.example.com. Langfuse uses it for login redirects and links. Then run docker compose up -d again.

With Traefik#

Prerequisites: a domain, DNS pointing to the server, ports 80 and 443 open, and Traefik on the external proxy network.

Run in terminalbash
docker network create proxy

Add this to the langfuse-web service (and replace its ports: line, so the port is no longer published). The labels are our example, not from the official docs (VERIFY against your Traefik setup):

File contentyaml
  langfuse-web:
    networks:
      - default
      - proxy
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.langfuse.rule=Host(`<your-domain>`)
      - traefik.http.routers.langfuse.entrypoints=https
      - traefik.http.routers.langfuse.tls=true
      - traefik.http.services.langfuse.loadbalancer.server.port=3000

networks:
  proxy:
    external: true

Without Traefik: Caddy#

Choose Caddy if you have no Traefik yet and run one small server.

CaseResultYou do
Public domainAutomatic Let's EncryptDNS A record to the server, ports 80 and 443 open
Home onlytls internal (local certificate)Trust Caddy's root certificate on each device

Caddyfile for a public domain (Langfuse keeps its port on 127.0.0.1:3000, as in our file):

File contenttext
langfuse.example.com {
    reverse_proxy localhost:3000
}

Caddyfile for home only (point the name at the Caddy machine):

File contenttext
langfuse.home.arpa {
    tls internal
    reverse_proxy localhost:3000
}

Trust the root certificate (pki/authorities/local/root.crt in Caddy's data directory):

Run in terminalbash
# Linux: on the Caddy machine
sudo caddy trust
# other Linux devices, with root.crt copied over
sudo cp root.crt /usr/local/share/ca-certificates/caddy-root.crt && sudo update-ca-certificates
# Windows (PowerShell as administrator)
certutil -addstore -f ROOT root.crt
# Mac
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain root.crt

Run Caddy (Linux: install from the official apt repository on caddyserver.com/docs/install first):

Run in terminalbash
# Linux
sudo systemctl reload caddy
# Windows (PowerShell)
winget install CaddyServer.Caddy
caddy run
# Mac
brew install caddy && caddy run

Or use Docker. Caddy then has to reach Langfuse on the Docker network: put it on the proxy network, give langfuse-web the proxy network (as above, without labels), and use reverse_proxy langfuse-web:3000. Save this in ~/docker_volumes/caddy:

File contentyaml
services:
  caddy:
    image: caddy:2.11.7
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy_data:/data
      - ./caddy_config:/config
    networks:
      - proxy
networks:
  proxy:
    external: true

Caddy passes WebSockets and streaming by default. Set NEXTAUTH_URL to the https address and run docker compose up -d.

Check it worked: curl -I https://langfuse.example.com answers HTTP/2 200 or a redirect to /auth/sign-in.

Two extra points for remote use:

  • Your apps then send traces to https://<your-domain> (set LANGFUSE_BASE_URL to it).
  • Media uploads (images and files in traces) go from the browser straight to MinIO on LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT. Behind a domain this must be an address the browser can reach, such as a second hostname for MinIO. Text-only tracing works without it.

Security and testing#

Keep the databases private, keep secrets in .env, and put HTTPS in front before anything leaves your network.

  • Never forward 3000, 5432, 6379, 8123, 9000 or 9090 to the internet. In our file all but the web port are bound to 127.0.0.1. Reach it from outside through a VPN such as WireGuard or Tailscale, or through HTTPS as above.
  • Replace every default. The official file starts with weak placeholder passwords. Our .env method removes all of them.
  • Lock the file: chmod 600 .env (Windows: keep the folder inside your user profile).
  • Telemetry: the file keeps the official default TELEMETRY_ENABLED=true. Set it to false in the .env and the Compose file if you want no anonymous usage statistics (see the telemetry page).
  • Sign-ups: after your account exists, turn open sign-up off (first-run section).

Tests:

Run in terminalbash
docker compose ps
docker compose logs langfuse-web --tail 50
curl -I http://localhost:3000

Expected: all services running, no errors in the log, and a response from the last command (HTTP/1.1 200 OK, or a redirect to the sign-in page).

Troubleshooting#

ProblemSolution
Compose stops before startingA secret is missing in .env. The error names it. Add it and run again
Page does not load right after up -dFirst start runs database migrations. Wait 2 to 3 minutes and watch docker compose logs -f langfuse-web for "Ready"
langfuse-worker or web keeps restartingRun docker compose logs langfuse-worker --tail 100. Usually ClickHouse, Redis or Postgres is not healthy yet, or a password in .env changed after the first start
Changed a password and now nothing connectsThe database keeps the first password. Change it inside the database, or delete the volume (this erases data) and start fresh
No traces appearCheck LANGFUSE_BASE_URL (no trailing path), the keys, and call get_client().flush() in short scripts. Check docker compose logs langfuse-web for 401 errors (wrong key)
Traces arrive lateEvents wait in Redis until the worker writes them. Check the worker is running
LiteLLM in Docker cannot reach Langfuselocalhost is the container. Use your machine's address, or put both on the same Docker network
Login redirects to the wrong addressNEXTAUTH_URL must be the exact address in your browser, including https://
Out of memory, containers killedClickHouse needs RAM. Give Docker or WSL2 more, or move to a larger machine

Exact error: missing secret#

Run in terminaltext
required variable POSTGRES_PASSWORD is missing a value: set it in .env

Compose reads .env from the folder you run it in. cd into ~/docker_volumes/langfuse first and check the line exists.

Exact error: port in use#

Codetext
Bind for 127.0.0.1:3000 failed: port is already allocated

Something else uses port 3000. Stop it, or change the left side of 127.0.0.1:3000:3000 (and NEXTAUTH_URL).

Known bugs#

We did not complete a review of the open issues for v4.70.1 (the research service had not answered yet). Check github.com/langfuse/langfuse/issues before you update, and roll back by setting the previous tag.

Updates and backups#

Update by changing the tag, back up by stopping the stack and copying the volumes.

Update:

Run in terminalbash
cd ~/docker_volumes/langfuse
# read the release notes, edit both Langfuse image tags to the new version, then:
docker compose pull && docker compose up -d

Windows: the same two lines in PowerShell after Set-Location $HOME\docker_volumes\langfuse. The docs say the database migrations run on start. The official Compose file has no backup feature, so we add one.

Backup script (Linux/Mac). It stops the stack briefly so the databases are consistent:

Run in terminalbash
cd ~/docker_volumes/langfuse
mkdir -p backups
docker compose stop
for v in postgres clickhouse minio redis; do
  docker run --rm -v langfuse_langfuse_${v}_data:/v:ro -v "$PWD/backups":/b alpine \
    tar czf /b/${v}-$(date +%F).tgz -C /v .
done
cp .env backups/env-$(date +%F).bak && chmod 600 backups/env-$(date +%F).bak
docker compose start

Windows (PowerShell):

Codepowershell
Set-Location $HOME\docker_volumes\langfuse
New-Item -ItemType Directory -Force backups | Out-Null
docker compose stop
$day = Get-Date -Format yyyy-MM-dd
foreach ($v in "postgres","clickhouse","minio","redis") {
  docker run --rm -v "langfuse_langfuse_${v}_data:/v:ro" -v "${PWD}\backups:/b" alpine tar czf "/b/$v-$day.tgz" -C /v .
}
Copy-Item .env "backups\env-$day.bak"
docker compose start

The volume names are langfuse_ (project name) plus the volume name from the file. Confirm yours with docker volume ls. Keep the .env copy safe: without the same ENCRYPTION_KEY the saved LLM keys cannot be read.

Schedule: Linux/Mac cron at 03:00 (0 3 * * * <path-to-script>). Windows: Task Scheduler running the PowerShell steps.

Restore test#

Do this once, before you need it. It reads a backup into a throwaway volume and lists it:

Run in terminalbash
docker volume create restore_test
docker run --rm -v restore_test:/v -v ~/docker_volumes/langfuse/backups:/b alpine sh -c 'tar xzf /b/postgres-*.tgz -C /v && ls /v | head'
docker volume rm restore_test

Expected: a list of Postgres files such as PG_VERSION. For a full restore, stop the stack, remove the real volume, create it again, extract the archive into it and start the stack.

Performance and resources#

Plan for 4 cores and 16 GB RAM on a VM, per the official Docker Compose page. We did not measure RAM or CPU ourselves. Measure on your own machine:

Run in terminalbash
docker stats --no-stream

Tips:

  • ClickHouse and the worker use the most memory as traces pile up. Watch them first.
  • The docs say the Docker Compose setup has no high availability or scaling. For heavy traffic use Kubernetes (Helm).
  • Keep traces small: very long prompts and images make the databases and MinIO grow fast. Set a retention period in the project settings if you only need recent data.
  • Redis uses --maxmemory-policy noeviction on purpose: do not change it.

FAQ#

Is Langfuse free to self-host? Yes, the core platform is open source and free to run yourself. Some enterprise features need a license key (see the License Key page in the docs).

Do I need all six containers? Yes for v4: web, worker, Postgres, ClickHouse, Redis and MinIO (or another S3 store). The worker and ClickHouse are what make it fast.

Which image tag should I use? The pinned 4.70.1. The official file uses the floating 4; we pin so updates are your choice. Use the previous release only to roll back.

Does it work on Windows and Mac? Yes, through Docker Desktop. Use at least 8 GB for Docker on a Mac and a generous .wslconfig on Windows.

Can I use it with a local model? Yes. Any app that talks to an OpenAI-compatible server (Ollama, LiteLLM, vLLM) can be traced with the OpenAI wrapper or with LiteLLM's callback.

Does it only work with Python? No. There are SDKs for Python and JavaScript/TypeScript, and an OpenTelemetry endpoint for other languages.

Sources#

Checked against the official docs on 2026-10-26. Not run by DIY Lab Hub. Items marked VERIFY are not confirmed in the official docs.