πŸ“ Tutorials
Β· 4 min read

Self-Hosted AI Observability with Langfuse + Docker (2026)


Every prompt, every completion, every token count β€” when you’re building AI applications, observability isn’t optional. Langfuse is one of the best open-source platforms for tracing and monitoring LLM apps, and the self-hosted option means your data never leaves your infrastructure. This guide walks you through a complete Docker Compose deployment with PostgreSQL and ClickHouse so you can have production-grade AI observability running in minutes.

If you’re new to Langfuse, start with our complete Langfuse guide for an overview of its features and how it fits into the broader LLM observability landscape.

Why Self-Host Langfuse?

The managed Langfuse Cloud works well, but self-hosting makes sense in several scenarios:

  • GDPR and data privacy β€” Prompts and completions often contain personal data. Keeping everything on-prem removes third-party data processing concerns entirely. We cover this in depth in our self-hosted AI and GDPR guide.
  • Data sovereignty β€” Regulated industries (healthcare, finance, government) often require that data stays within specific jurisdictions or networks.
  • Air-gapped environments β€” If your infrastructure has no outbound internet access, self-hosting is the only path. Pair it with a local Ollama setup for a fully offline AI stack.
  • Cost control β€” High-volume tracing can get expensive on managed plans. Self-hosting costs you only the compute and storage you provision.
  • Customization β€” Full control over retention policies, backup schedules, and network configuration.

For a broader look at why enterprises are moving toward self-hosted AI tooling, see our enterprise self-hosted AI guide.

Prerequisites

Before starting, make sure you have:

  • Docker Engine 24+ and Docker Compose v2 installed
  • At least 4 GB of free RAM (8 GB recommended for production workloads)
  • 2 CPU cores minimum
  • 10 GB disk space for the database and ClickHouse storage
  • A machine running Linux, macOS, or Windows with WSL2

Docker Compose Setup

Create a project directory and add the following docker-compose.yml:

mkdir langfuse-self-hosted && cd langfuse-self-hosted
# docker-compose.yml
version: "3.9"

services:
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: langfuse
      POSTGRES_USER: langfuse
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-langfuse-secret}
    volumes:
      - pg_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U langfuse"]
      interval: 5s
      timeout: 3s
      retries: 5

  clickhouse:
    image: clickhouse/clickhouse-server:24-alpine
    restart: unless-stopped
    environment:
      CLICKHOUSE_DB: langfuse
      CLICKHOUSE_USER: langfuse
      CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse-secret}
    volumes:
      - ch_data:/var/lib/clickhouse
    healthcheck:
      test: ["CMD", "clickhouse-client", "--query", "SELECT 1"]
      interval: 5s
      timeout: 3s
      retries: 5

  langfuse:
    image: langfuse/langfuse:latest
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
      clickhouse:
        condition: service_healthy
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://langfuse:${POSTGRES_PASSWORD:-langfuse-secret}@postgres:5432/langfuse
      CLICKHOUSE_URL: http://clickhouse:8123
      CLICKHOUSE_USER: langfuse
      CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse-secret}
      NEXTAUTH_URL: http://localhost:3000
      NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-change-me-to-a-random-string}
      SALT: ${SALT:-another-random-string-here}
      TELEMETRY_ENABLED: "false"

volumes:
  pg_data:
  ch_data:

Environment Variables

Create a .env file next to your compose file with secure values:

# .env
POSTGRES_PASSWORD=your-strong-pg-password
CLICKHOUSE_PASSWORD=your-strong-ch-password
NEXTAUTH_SECRET=$(openssl rand -base64 32)
SALT=$(openssl rand -base64 32)

Key variables explained:

VariablePurpose
DATABASE_URLPostgreSQL connection string for Langfuse metadata
CLICKHOUSE_URLClickHouse endpoint for high-volume trace storage
NEXTAUTH_SECRETSigns session tokens β€” must be random and kept secret
SALTUsed for hashing API keys β€” generate once, never change
TELEMETRY_ENABLEDSet to false to disable anonymous usage reporting

Starting Langfuse

Bring everything up:

docker compose up -d

Watch the logs until Langfuse reports it’s ready:

docker compose logs -f langfuse

You should see output indicating the database migrations completed and the server is listening on port 3000. Open http://localhost:3000 in your browser.

First Login

  1. Navigate to http://localhost:3000
  2. Click Sign Up to create your admin account
  3. After signing in, go to Settings β†’ API Keys
  4. Create a new API key pair β€” you’ll need the Public Key and Secret Key for your application

Save these keys securely. You’ll use them in the next step.

Connecting Your Python App

Install the Langfuse Python SDK:

pip install langfuse

Add tracing to your application with the decorator approach:

from langfuse.decorators import observe, langfuse_context
import os

os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-lf-..."
os.environ["LANGFUSE_SECRET_KEY"] = "sk-lf-..."
os.environ["LANGFUSE_HOST"] = "http://localhost:3000"

@observe()
def generate_response(user_input: str) -> str:
    # Your LLM call here (OpenAI, Anthropic, local model, etc.)
    response = call_your_llm(user_input)
    return response

result = generate_response("Explain quantum computing")
langfuse_context.flush()

The @observe() decorator automatically captures inputs, outputs, latency, and token usage. Call flush() at the end to ensure all traces are sent before the process exits.

Verifying Traces

After running your instrumented code:

  1. Open the Langfuse dashboard at http://localhost:3000
  2. Navigate to Traces in the sidebar
  3. You should see your function call with full input/output details, timing, and metadata

If traces don’t appear, check that:

  • Your LANGFUSE_HOST points to the correct URL
  • The API keys match what’s configured in the dashboard
  • You called flush() before the process exited
  • The Langfuse container is healthy: docker compose ps

Backup Strategy

Both databases need regular backups:

# PostgreSQL backup
docker compose exec postgres pg_dump -U langfuse langfuse > backup_pg_$(date +%Y%m%d).sql

# ClickHouse backup
docker compose exec clickhouse clickhouse-client \
  --user langfuse \
  --password your-strong-ch-password \
  --query "BACKUP DATABASE langfuse TO Disk('backups', 'langfuse_$(date +%Y%m%d)')"

Schedule these with cron for automated daily backups. Store copies off-host β€” a mounted NFS share or an object storage bucket works well.

Updating Langfuse

To pull the latest version:

docker compose pull langfuse
docker compose up -d langfuse

Langfuse runs database migrations automatically on startup. Check the Langfuse changelog before upgrading to review breaking changes. For major version bumps, snapshot your databases first.

What’s Next

You now have a fully self-hosted AI observability stack. From here you can:

  • Add more team members and configure role-based access
  • Set up prompt management and versioning through the Langfuse UI
  • Create evaluation datasets for systematic testing
  • Configure alerting on latency or error rate thresholds
  • Put Langfuse behind a reverse proxy with TLS for production use

Pair this setup with a self-hosted Ollama instance and you have a completely private AI development environment β€” no data leaves your network, ever.