Retour aux ressources
// guide · déploiement

Déployer Langfuse sur AWS EC2

La stack v3 complète en Docker Compose — Postgres, ClickHouse, Redis et MinIO — derrière un reverse proxy HTTPS. De l’instance vide au premier projet tracé.

GuidePar Julien~15 min · intermédiaire
EC2DockerPostgres 16ClickHouseRedis 7MinIOCaddy
01

L’architecture, en 6 services

Depuis la v3, Langfuse n’est plus un simple conteneur + Postgres. Les traces vivent maintenant dans ClickHouse (une base OLAP colonne), la file d’attente dans Redis, et les payloads bruts dans un stockage S3 — ici MinIO. Six services au total.

!

ClickHouse est indispensable depuis la v3. S’il manque, le conteneur web redémarre en boucle (il ne peut pas joindre clickhouse:9000). On le met en place à l’étape 04.

// flux d’ingestion
SDK
client
web
ui + api
worker
jobs
Postgres
state
ClickHouse
traces
Redis
queue
MinIO
s3
web — sert l’UI de la console et l’API
worker — traite les événements et les tâches de fond
Postgres — l’état transactionnel (users, projets, config)
ClickHouse — les traces, observations et scores
Redis — la file d’attente et le cache
MinIO — le stockage S3 des payloads bruts
02

Prérequis — l’instance EC2

Six conteneurs, dont ClickHouse : vise large sur la RAM. En dessous de 4 Go, le démarrage échoue silencieusement.

Typet3.large — 2 vCPU / 8 Go (minimum 4 Go)
Disque30 Go gp3 au minimum
OSUbuntu 24.04 LTS
Archiamd64 — pas Graviton / ARM
!

Évite les instances t4g / Graviton (ARM64) : les images officielles ciblent amd64 et l’ARM provoque des boucles de crash. Reste sur t3.

Security group — ports à ouvrir

22 / SSHton IP uniquement
80 / HTTPpublic — Caddy redirige vers 443
443 / HTTPSpublic — le trafic Langfuse
i

N’ouvre jamais 3005, 8123 ni 9000 au public : ce sont des ports internes. Pointe aussi un enregistrement DNS A de langfuse.mondomaine.com vers l’IP élastique de l’instance avant l’étape HTTPS.

03

Installer Docker

Une fois connecté en SSH, installe Docker et le plugin Compose via le script officiel.

shellcopier
# Ubuntu 24.04 LTS sur ton EC2 (amd64)
$ sudo apt-get update
$ curl -fsSL https://get.docker.com | sudo sh # Docker + plugin compose
$ sudo usermod -aG docker $USER # docker sans sudo
$ newgrp docker
$ docker --version && docker compose version
i

Docker récent utilise docker compose (avec un espace). Tes commandes docker-compose (avec un tiret) fonctionnent aussi si tu installes l’ancien binaire v1 — dans ce guide on utilise le plugin.

04

Le docker-compose.yml

Crée le dossier et le fichier. Voici le compose complet : Postgres, ClickHouse, Redis et MinIO, avec des healthchecks et un démarrage ordonné.

~/langfuse/docker-compose.ymlcopier
services:
db:
image: postgres:16
restart: always
environment:
POSTGRES_USER: langfuse
POSTGRES_PASSWORD: langfuse
POSTGRES_DB: langfuse
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U langfuse"]
interval: 5s
timeout: 5s
retries: 10
clickhouse: # indispensable depuis la v3
image: clickhouse/clickhouse-server
restart: always
environment:
CLICKHOUSE_USER: clickhouse
CLICKHOUSE_PASSWORD: clickhouse
volumes:
- chdata:/var/lib/clickhouse
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8123/ping"]
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:7
restart: always
command: --requirepass myredissecret
volumes:
- redisdata:/data
minio:
image: minio/minio
restart: always
entrypoint: sh
command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data'
environment:
MINIO_ROOT_USER: minio
MINIO_ROOT_PASSWORD: miniosecret
volumes:
- miniodata:/data
worker:
image: ghcr.io/langfuse/langfuse-worker:latest
restart: always
depends_on:
db: { condition: service_healthy }
clickhouse: { condition: service_healthy }
redis: { condition: service_started }
minio: { condition: service_started }
env_file:
- .env
web:
image: ghcr.io/langfuse/langfuse:latest
restart: always
depends_on:
db: { condition: service_healthy }
clickhouse: { condition: service_healthy }
redis: { condition: service_started }
minio: { condition: service_started }
worker: { condition: service_started }
ports:
- "3005:3000" # retire cette ligne une fois Caddy en place (etape 07)
env_file:
- .env
volumes:
pgdata:
chdata:
redisdata:
miniodata:

Points clés : le service clickhouse et son volume chdata, des healthchecks sur db et clickhouse, et un depends_on conditionnel (condition: service_healthy) pour que web/worker attendent que les bases soient prêtes.

05

Le fichier .env

web et worker lisent tous les deux ce .env. Commence par générer les trois secrets.

Générer les secrets

shellcopier
# genere un secret par ligne, colle-les dans le .env
openssl rand -base64 32 # -> NEXTAUTH_SECRET
openssl rand -base64 32 # -> SALT
openssl rand -hex 32 # -> ENCRYPTION_KEY (64 caracteres hex)

Le fichier complet

~/langfuse/.envcopier
# ---- Coeur ----
DATABASE_URL=postgresql://langfuse:langfuse@db:5432/langfuse
NEXTAUTH_URL=https://langfuse.mondomaine.com # l'URL HTTPS publique EXACTE
NEXTAUTH_SECRET=colle_ici_le_1er_openssl
SALT=colle_ici_le_2eme_openssl
ENCRYPTION_KEY=colle_ici_le_openssl_hex_32
TELEMETRY_ENABLED=true
# ---- ClickHouse ----
CLICKHOUSE_URL=http://clickhouse:8123
CLICKHOUSE_MIGRATION_URL=clickhouse://clickhouse:9000
CLICKHOUSE_USER=clickhouse
CLICKHOUSE_PASSWORD=clickhouse
CLICKHOUSE_CLUSTER_ENABLED=false
# ---- Redis ----
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_AUTH=myredissecret
# ---- S3 / MinIO : upload des evenements ----
LANGFUSE_S3_EVENT_UPLOAD_ENABLED=true
LANGFUSE_S3_EVENT_UPLOAD_BUCKET=langfuse
LANGFUSE_S3_EVENT_UPLOAD_REGION=auto
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=http://minio:9000
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=minio
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=miniosecret
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=true
LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/
# ---- S3 / MinIO : upload multimodal (images, fichiers) ----
LANGFUSE_S3_MEDIA_UPLOAD_ENABLED=true
LANGFUSE_S3_MEDIA_UPLOAD_BUCKET=langfuse
LANGFUSE_S3_MEDIA_UPLOAD_REGION=auto
LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://minio:9000
LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID=minio
LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY=miniosecret
LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE=true
LANGFUSE_S3_MEDIA_UPLOAD_PREFIX=media/
!

NEXTAUTH_URL doit être l’URL HTTPS publique exacte, sinon la connexion casse (redirections OAuth invalides). Et change les mots de passe par défaut (minio, redis, clickhouse) avant toute mise en prod.

06

Lancer la stack

shellcopier
$ mkdir -p ~/langfuse && cd ~/langfuse
# (colle ici docker-compose.yml et .env)
$ docker compose up -d
$ sleep 20
$ docker compose ps
$ echo "--- logs worker ---"
$ docker compose logs worker --tail=40
$ docker compose logs -f web # suis les migrations ClickHouse (1-2 min)

Au premier démarrage, le worker applique les migrations ClickHouse — ça peut prendre une à deux minutes. Suis docker compose logs -f web jusqu’à voir le serveur écouter sur le port 3000.

i

Le web écoute en interne sur 3000 (mappé sur 3005). On ne l’expose pas au monde : le reverse proxy s’en charge à l’étape suivante.

07

Reverse proxy + HTTPS (Caddy)

Caddy gère le HTTPS automatiquement (certificat Let’s Encrypt, renouvellement inclus) — c’est le plus simple. On l’ajoute au même compose pour qu’il joigne web en interne.

~/langfuse/docker-compose.ymlcopier
# a ajouter dans la section services: du docker-compose.yml
caddy:
image: caddy:2
restart: always
depends_on:
- web
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddydata:/data
- caddyconfig:/config
# ... et dans volumes: ajoute
# caddydata:
# caddyconfig:
~/langfuse/Caddyfilecopier
langfuse.mondomaine.com {
reverse_proxy web:3000
}

Retire ensuite le mapping ports: - "3005:3000" du service web (Caddy le joint en interne via web:3000), puis relance : docker compose up -d.

Caddy obtient le certificat tout seul au premier appel sur https://langfuse.mondomaine.com. Si tu préfères Nginx + certbot, le principe est identique : proxy vers web:3000.

08

Première connexion & projet

Ouvre ton domaine en HTTPS. Le premier compte créé est le tien — crée ensuite une organisation, un projet, et récupère les clés API.

shellcopier
# 1. https://langfuse.mondomaine.com -> "Sign up" (1er compte)
# 2. Cree une organisation, puis un projet
# 3. Copie les cles API du projet :
# LANGFUSE_PUBLIC_KEY=pk-lf-...
# LANGFUSE_SECRET_KEY=sk-lf-...
# LANGFUSE_HOST=https://langfuse.mondomaine.com
$ pip install langfuse
$ python -c "from langfuse import Langfuse; Langfuse().auth_check()" # -> True
i

Pour fermer les inscriptions une fois ton compte créé, ajoute AUTH_DISABLE_SIGNUP=true au .env puis relance docker compose up -d.

09

Dépannage

Le conteneur web redémarre en boucle

Presque toujours ClickHouse injoignable. Vérifie que le hostname est bien clickhouse (pas localhost) et que CLICKHOUSE_MIGRATION_URL=clickhouse://clickhouse:9000. Les conteneurs se joignent par leur nom de service sur le réseau Docker.

Vérifier la santé

shellcopier
# vivant ?
$ curl -s https://langfuse.mondomaine.com/api/public/health
# pret a servir du trafic ?
$ curl -s https://langfuse.mondomaine.com/api/public/ready
# 200 = ok · 503 = base injoignable

Autres pièges

Fuseau horaire : Postgres ET ClickHouse doivent tourner en UTC, sinon les requêtes renvoient des résultats vides.
ARM64 : si tu es sur Graviton, repasse sur une instance amd64 (t3).
Page inaccessible : vérifie le security group (80/443 ouverts) et l’enregistrement DNS.
Logs ciblés : docker compose logs web --tail=50 -f (idem pour worker).
!

docker compose down -v supprime les volumes = perte totale des données. Pour juste arrêter, utilise docker compose down sans le -v.

C’est en ligne. Prochaines étapes : brancher un SDK sur tes agents, monter des dashboards de coût, et mettre en place les backups (pg_dump pour Postgres, snapshots pour ClickHouse et MinIO).