From 8c5ea9f96db64262fafea2dcd944ea3f2a09f73f Mon Sep 17 00:00:00 2001 From: huseyincenik Date: Fri, 7 Aug 2026 12:51:41 +0300 Subject: [PATCH 1/2] Add Langfuse template Open-source LLM engineering platform: tracing, prompt management, evaluation and cost/usage analytics for LLM apps. Stack: langfuse-web + langfuse-worker (image tag 4), Postgres 17, ClickHouse 25.12, Redis 7, MinIO. Adapted from the official docker-compose.yml at https://github.com/langfuse/langfuse/blob/main/docker-compose.yml, with all secrets generated via Dokploy's template.toml helpers instead of the upstream hardcoded defaults. MinIO gets its own domain because Langfuse's web UI issues presigned S3 URLs that the browser fetches directly (media attached to traces), so that endpoint has to be reachable from outside the internal network. Validated locally with build-scripts/validate-template.ts, validate-docker-compose.ts and generate-meta.js --check (all pass), plus `docker compose config` with sample secrets to confirm the langfuse-web override of LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT resolves correctly. Not yet deployed on a live Dokploy instance. --- blueprints/langfuse/docker-compose.yml | 135 +++++++++++++++++++++++++ blueprints/langfuse/instructions.md | 26 +++++ blueprints/langfuse/langfuse.svg | 8 ++ blueprints/langfuse/meta.json | 13 +++ blueprints/langfuse/template.toml | 36 +++++++ 5 files changed, 218 insertions(+) create mode 100644 blueprints/langfuse/docker-compose.yml create mode 100644 blueprints/langfuse/instructions.md create mode 100644 blueprints/langfuse/langfuse.svg create mode 100644 blueprints/langfuse/meta.json create mode 100644 blueprints/langfuse/template.toml diff --git a/blueprints/langfuse/docker-compose.yml b/blueprints/langfuse/docker-compose.yml new file mode 100644 index 000000000..cfa8ebcae --- /dev/null +++ b/blueprints/langfuse/docker-compose.yml @@ -0,0 +1,135 @@ +# Langfuse self-hosted, adapted from the official docker-compose.yml: +# https://github.com/langfuse/langfuse/blob/main/docker-compose.yml +# +# MinIO is exposed on its own domain because Langfuse's web UI generates +# presigned S3 URLs that the browser fetches directly (e.g. media in +# traces) - it cannot resolve the internal "minio" hostname. + +services: + postgres: + image: docker.io/postgres:17 + restart: unless-stopped + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: postgres + TZ: UTC + PGTZ: UTC + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 3s + timeout: 3s + retries: 10 + volumes: + - langfuse-postgres-data:/var/lib/postgresql/data + + 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} + healthcheck: + test: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1 + interval: 5s + timeout: 5s + retries: 10 + start_period: 1s + volumes: + - langfuse-clickhouse-data:/var/lib/clickhouse + - langfuse-clickhouse-logs:/var/log/clickhouse-server + + 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} + healthcheck: + test: ["CMD", "mc", "ready", "local"] + interval: 1s + timeout: 5s + retries: 5 + start_period: 1s + volumes: + - langfuse-minio-data:/data + + redis: + image: docker.io/redis:7 + restart: unless-stopped + command: > + --requirepass ${REDIS_AUTH} + --maxmemory-policy noeviction + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 3s + timeout: 10s + retries: 10 + volumes: + - langfuse-redis-data:/data + + langfuse-worker: + image: docker.io/langfuse/langfuse-worker:4 + restart: unless-stopped + depends_on: &langfuse-depends-on + postgres: + condition: service_healthy + minio: + condition: service_healthy + redis: + condition: service_healthy + clickhouse: + condition: service_healthy + environment: &langfuse-worker-env + NEXTAUTH_URL: ${NEXTAUTH_URL} + DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/postgres + SALT: ${SALT} + ENCRYPTION_KEY: ${ENCRYPTION_KEY} + TELEMETRY_ENABLED: "true" + LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES: "false" + CLICKHOUSE_MIGRATION_URL: clickhouse://clickhouse:9000 + CLICKHOUSE_URL: http://clickhouse:8123 + CLICKHOUSE_USER: clickhouse + CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} + 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} + 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} + LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT: http://minio:9000 + LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE: "true" + LANGFUSE_S3_MEDIA_UPLOAD_PREFIX: media/ + LANGFUSE_S3_BATCH_EXPORT_ENABLED: "false" + REDIS_HOST: redis + REDIS_PORT: "6379" + REDIS_AUTH: ${REDIS_AUTH} + REDIS_TLS_ENABLED: "false" + + langfuse-web: + image: docker.io/langfuse/langfuse:4 + restart: unless-stopped + depends_on: *langfuse-depends-on + environment: + <<: *langfuse-worker-env + NEXTAUTH_SECRET: ${NEXTAUTH_SECRET} + # Overrides the worker's internal endpoint above: the browser resolves + # presigned media URLs itself, so this one must be publicly reachable. + LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT: ${LANGFUSE_S3_MEDIA_PUBLIC_ENDPOINT} + +volumes: + langfuse-postgres-data: + langfuse-clickhouse-data: + langfuse-clickhouse-logs: + langfuse-minio-data: + langfuse-redis-data: diff --git a/blueprints/langfuse/instructions.md b/blueprints/langfuse/instructions.md new file mode 100644 index 000000000..5abefd188 --- /dev/null +++ b/blueprints/langfuse/instructions.md @@ -0,0 +1,26 @@ +# Langfuse + +Self-hosted Langfuse: open-source LLM tracing, prompt management, evaluation and cost/usage analytics, based on the official `docker-compose.yml`. + +## Two domains + +This template exposes two domains: + +- The **main domain** serves the Langfuse web app/API (`langfuse-web`, port 3000). +- The **second domain** exposes MinIO's S3 API (port 9000) directly. Langfuse's web UI generates presigned URLs for media (e.g. images attached to traces) that the *browser* fetches straight from MinIO, so this endpoint has to be publicly reachable — it isn't just an internal implementation detail. + +## First boot + +`langfuse-web` and `langfuse-worker` wait on Postgres, ClickHouse, Redis and MinIO to report healthy, then run their own migrations on startup. Once `langfuse-web` is up, open its domain and create the first user — they become the owner of the initial organization. + +## After enabling HTTPS + +By default `NEXTAUTH_URL` and `LANGFUSE_S3_MEDIA_PUBLIC_ENDPOINT` are set to `http://`. If you enable HTTPS on either domain (recommended for production), update the matching environment variable to `https://` and redeploy, otherwise auth callbacks and media URLs will point at the wrong scheme. + +## Licensing + +The core of Langfuse (tracing, prompt management, evaluation, playground) is open-source (MIT) and fully usable self-hosted without a license key. Some enterprise-only features (e.g. SSO/SAML, fine-grained RBAC) live under a separate commercial license — see [self-hosting license docs](https://langfuse.com/self-hosting/license-key). This template does not configure a license key. + +## Versioning + +Images are pinned to the `4` major tag (`langfuse/langfuse:4`, `langfuse/langfuse-worker:4`), matching the upstream compose file. To pin to a specific release instead, replace `4` with a version tag from [Docker Hub](https://hub.docker.com/r/langfuse/langfuse/tags). diff --git a/blueprints/langfuse/langfuse.svg b/blueprints/langfuse/langfuse.svg new file mode 100644 index 000000000..d56d582be --- /dev/null +++ b/blueprints/langfuse/langfuse.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/blueprints/langfuse/meta.json b/blueprints/langfuse/meta.json new file mode 100644 index 000000000..7b39595eb --- /dev/null +++ b/blueprints/langfuse/meta.json @@ -0,0 +1,13 @@ +{ + "id": "langfuse", + "name": "Langfuse", + "version": "4", + "description": "Langfuse is an open-source LLM engineering platform for tracing, prompt management, evaluation, and cost/usage analytics of LLM applications.", + "logo": "langfuse.svg", + "links": { + "github": "https://github.com/langfuse/langfuse", + "website": "https://langfuse.com/", + "docs": "https://langfuse.com/self-hosting" + }, + "tags": ["ai", "llm", "observability", "analytics", "monitoring"] +} diff --git a/blueprints/langfuse/template.toml b/blueprints/langfuse/template.toml new file mode 100644 index 000000000..9d388a88b --- /dev/null +++ b/blueprints/langfuse/template.toml @@ -0,0 +1,36 @@ +[variables] +main_domain = "${domain}" +minio_domain = "${domain}" +postgres_password = "${password:32}" +clickhouse_password = "${password:32}" +minio_root_password = "${password:32}" +redis_auth = "${password:32}" +nextauth_secret = "${password:32}" +salt = "${password:32}" +encryption_key = "${hash:64}" +nextauth_url = "http://${main_domain}" +minio_public_endpoint = "http://${minio_domain}" + +[config] +[[config.domains]] +serviceName = "langfuse-web" +port = 3000 +host = "${main_domain}" + +[[config.domains]] +serviceName = "minio" +port = 9000 +host = "${minio_domain}" + +[config.env] +POSTGRES_PASSWORD = "${postgres_password}" +CLICKHOUSE_PASSWORD = "${clickhouse_password}" +MINIO_ROOT_PASSWORD = "${minio_root_password}" +REDIS_AUTH = "${redis_auth}" +NEXTAUTH_SECRET = "${nextauth_secret}" +SALT = "${salt}" +ENCRYPTION_KEY = "${encryption_key}" +# Set to https:// once HTTPS is enabled on the domain +NEXTAUTH_URL = "${nextauth_url}" +# Set to https:// once HTTPS is enabled on the domain +LANGFUSE_S3_MEDIA_PUBLIC_ENDPOINT = "${minio_public_endpoint}" From e2c3a249369d363e270b33c4c0d45c9cdc7aeab7 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sat, 8 Aug 2026 23:59:09 -0600 Subject: [PATCH 2/2] fix: bind langfuse-web to 0.0.0.0 so Traefik can reach it Docker sets HOSTNAME to the container id, and Next.js standalone binds to it, which resolves to a single network's IP. With Dokploy's proxy network attached as a second network, Traefik got connection refused (502). Setting HOSTNAME=0.0.0.0 fixes routing; verified with a live deploy. Co-Authored-By: Claude Fable 5 --- blueprints/langfuse/docker-compose.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/blueprints/langfuse/docker-compose.yml b/blueprints/langfuse/docker-compose.yml index cfa8ebcae..81dcdf1fa 100644 --- a/blueprints/langfuse/docker-compose.yml +++ b/blueprints/langfuse/docker-compose.yml @@ -122,6 +122,10 @@ services: depends_on: *langfuse-depends-on environment: <<: *langfuse-worker-env + # Bind Next.js to all interfaces: Docker sets HOSTNAME to the container id, + # which resolves to a single network's IP - with Dokploy's extra proxy + # network attached, Traefik would get connection refused (502) otherwise. + HOSTNAME: "0.0.0.0" NEXTAUTH_SECRET: ${NEXTAUTH_SECRET} # Overrides the worker's internal endpoint above: the browser resolves # presigned media URLs itself, so this one must be publicly reachable.