为什么要自托管n8n + Langfuse
n8n是开源的工作流自动化引擎,Langfuse是LLM可观测性平台(tracing + evaluation + prompt管理)。两者都支持Docker自托管,搭配使用可以实现:n8n驱动LLM工作流 → Langfuse记录每个节点的输入输出 → 发现Token消耗异常或延迟瓶颈。
我用的是1核2GB的VPS(腾讯云轻量应用服务器,月均约30元),系统Ubuntu 22.04 LTS。这个配置跑单个n8n足够,但加上Langfuse v3(需要PostgreSQL + Redis + Langfuse本身)就开始捉襟见肘。
本文覆盖的5个踩坑(按发生概率排序):
1. n8n容器Running但返回502 — n8n进程崩溃
2. n8n环境变量在Docker中不生效 — 常见于N8N_TIMEOUT_EXECUTION等
3. Langfuse v3强制要求Redis/Valkey — 新增依赖
4. 2GB内存VPS上OOM崩溃 — 交换分区+容器内存限制
5. PostgreSQL连接池耗尽 — 高并发下max_connections不够
n8n容器Running但返回502
**现象:** docker ps显示n8n容器状态为Up,但浏览器访问 http://your-vps:5678 返回502 Bad Gateway。
根因: 容器进程在运行,但n8n主进程(Node.js)崩溃了。容器本身还活着(PID 1是tini/entrypoint.sh),但5678端口没有监听。
排查步骤:
# 1. 查看容器实时日志
docker logs n8n --tail 100 --follow
# 2. 检查容器内是否有n8n进程
docker exec n8n ps aux | grep n8n
# 3. 如果没有node进程,说明n8n主进程崩溃了
# 健康检查
docker exec n8n curl http://localhost:5678/healthz
典型崩溃日志:
Error: ENOENT: no such file or directory, open '/home/node/.n8n/config'
at Object.openSync (node:internal/filesystem:983:3)
at Object.readFileSync (node:internal/filesystem:1148:3)
解决方案: 最常见的原因是数据目录权限问题或挂载失败:
# 修复数据目录权限
sudo chown -R 1000:1000 ./n8n_data
# 如果用的是Docker Compose,重新创建卷
docker compose down -v
docker compose up -d
# 验证健康状态
sleep 5 && docker exec n8n curl -s http://localhost:5678/healthz
如果日志显示的是内存相关崩溃(JavaScript heap out of memory),需要增加容器内存限制或添加swap。
n8n环境变量在Docker中不生效
**现象:** 在docker-compose.yml里设置了N8N_TIMEOUT_EXECUTION或EXECUTIONS_TIMEOUT,但n8n显示"Undefined or empty"。
根因: n8n在Docker中读取环境变量的时机和方式有特殊要求,部分变量必须在容器启动前存在于宿主环境,或需要显式传递。
**正确做法:** 环境变量应该在docker-compose.yml的environment段直接定义,而不是只在宿主机的.env文件里:
version: '3.8'
services:
n8n:
image: docker.n8n.io/n8nio/n8n
restart: unless-stopped
ports:
- "5678:5678"
volumes:
- n8n_data:/home/node/.n8n
environment:
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=YOUR_STRONG_PASSWORD
# 关键:执行超时需要在environment段显式写
- N8N_TIMEOUT_EXECUTION=600000 # 10分钟
- EXECUTIONS_TIMEOUT=600000
- WEBHOOK_URL=https://your-domain.com/
- N8N_PROTOCOL=https
- N8N_PORT=5678
mem_limit: 1g # 限制容器最大内存1GB
mem_reservation: 256m
volumes:
n8n_data:
验证环境变量是否生效:
在n8n里创建一个Function节点,运行:
return {
timeout: process.env.N8N_TIMEOUT_EXECUTION,
execTimeout: process.env.EXECUTIONS_TIMEOUT
};
如果显示undefined,说明环境变量没有正确传入。
**常见错误:** 直接在docker-compose.yml的env_file里写变量名(N8N_TIMEOUT_EXECUTION=600000),但这些变量只在env_file文件存在时生效,不在environment段直接写的情况下不会自动加载。
Langfuse v3强制要求Redis/Valkey
现象: 按照Langfuse官方文档部署docker-compose.yml,容器启动后报错:
Missing Redis/Valkey - Langfuse v3 requires Redis or Valkey for queuing events and caching data
根因: Langfuse v3(2024年12月发布stable)架构变化,原来可选的Redis变成了必选依赖,用于队列事件和缓存。
完整docker-compose.yml(n8n + Langfuse + Postgres + Redis):
version: '3.8'
services:
# PostgreSQL 数据库
postgres:
image: postgres:15-alpine
restart: unless-stopped
environment:
- POSTGRES_USER=langfuse
- POSTGRES_PASSWORD=langfuse_password
- POSTGRES_DB=langfuse
volumes:
- postgres_data:/var/lib/postgresql/data
mem_limit: 512m
healthcheck:
test: ["CMD-SHELL", "pg_isready -U langfuse"]
interval: 10s
timeout: 5s
retries: 5
# Redis 消息队列(Langfuse v3 必选)
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
mem_limit: 384m
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
# Langfuse 主应用
langfuse:
image: langfuse/langfuse:latest
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
- DATABASE_URL=postgresql://langfuse:langfuse_password@postgres:5432/langfuse
- REDIS_URL=redis://redis:6379
- NEXTAUTH_SECRET=your_nextauth_secret_here
- NEXTAUTH_URL=http://your-domain.com:3000
- SALT=your_salt_here
- TELEMETRY_ENABLED=false
ports:
- "3000:3000"
mem_limit: 1g
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/public/health"]
interval: 30s
timeout: 10s
retries: 3
# n8n 工作流引擎
n8n:
image: docker.n8n.io/n8nio/n8n
restart: unless-stopped
depends_on:
- postgres # n8n可选用postgres做workflow数据存储
ports:
- "5678:5678"
volumes:
- n8n_data:/home/node/.n8n
environment:
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=CHANGE_ME
- WEBHOOK_URL=https://your-domain.com/
- N8N_PROTOCOL=https
- N8N_PERSONAL_EXCLUSIONS=owner,admin,administrator
mem_limit: 768m
volumes:
postgres_data:
redis_data:
n8n_data:
注意Langfuse v3新增的必需环境变量:
- `DATABASE_URL` — PostgreSQL连接字符串(v2就有但现在必选)
- `REDIS_URL` — Redis连接字符串(v3新增,之前可选)
- `SALT` — 密码加盐(v3新增,之前用NEXTAUTH_SECRET)
2GB内存VPS上OOM崩溃
**现象:** 容器运行几小时或几天后突然无响应,docker ps显示Exited状态,dmesg里有oom-killer记录。
根因: 2GB内存的VPS跑PostgreSQL + Redis + Langfuse + n8n,内存总量刚好踩在临界线上。任何突发流量(如workflow高并发执行)都会触发OOM Killer。
诊断命令:
# 查看内存使用
free -h
# 查看哪些进程被OOM Killer杀过
grep -i oom /var/log/syslog
# 或者
dmesg | grep -i kill
# 查看容器内存使用
docker stats --no-stream
解决方案(三步走):
Step 1: 添加swap(最简单)
# 检查是否有swap
swapon -s
# 创建2GB swap文件(如果还没有)
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# 永久启用:添加到fstab
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Step 2: 为每个容器设置内存硬限制
在docker-compose.yml里为每个服务加mem_limit(已在上面的完整配置中体现):
- postgres: 512m
- redis: 384m
- langfuse: 1g
- n8n: 768m
Step 3: 调整PostgreSQL共享缓冲区
# 在postgres容器内执行(或通过POSTGRES环境变量)
# 建议设为容器内存的25%
shared_buffers = 128MB
effective_cache_size = 256MB
work_mem = 16MB
内存使用监控脚本(加到crontab):
# 每5分钟检查一次,内存<20%时发警告
*/5 * * * * free -m | awk '{if($3/$2 < 0.2) print "Low memory: "$3"M used of "$2"M"}' | logger -t mem-alert
PostgreSQL连接池耗尽
**现象:** Langfuse或n8n突然报FATAL: remaining connection slots are reserved for non-replication superuser connections,之后所有请求都失败。
**根因:** PostgreSQL默认max_connections=100,但每个Langfuse和n8n容器内的连接池可能会用掉几十个连接。在资源受限的VPS上,PostgreSQL默认配置会快速耗尽。
解决方案:
方案A: 降低每个容器的最大连接数
# Langfuse侧:限制Prisma连接池
environment:
- DATABASE_URL=postgresql://langfuse:langfuse_password@postgres:5432/langfuse?connection_limit=5&pool_timeout=10
方案B: 修改PostgreSQL最大连接数
-- 进入postgres容器
docker exec -it postgres psql -U langfuse
-- 查看当前连接数
SELECT count(*) FROM pg_stat_activity;
-- 修改最大连接数(临时,重启失效)
ALTER SYSTEM SET max_connections = 50;
SELECT pg_reload_conf();
-- 永久生效:重启postgres容器
docker restart postgres
方案C: 使用PgBouncer连接池(生产环境推荐)
# 在docker-compose.yml中加一个pgbouncer服务
pgbouncer:
image: edoburu/pgbouncer:latest
restart: unless-stopped
depends_on:
- postgres
environment:
- POOL_MODE=transaction
- MAX_CLIENT_CONN=200
- DEFAULT_POOL_SIZE=20
- MIN_POOL_SIZE=5
-Reserve_POOL_SIZE=5
- DB_HOST=postgres
- DB_PORT=5432
- DB_NAME=langfuse
- DB_USER=langfuse
- DB_PASSWORD=langfuse_password
ports:
- "5433:5432"
Langfuse的DATABASE_URL改为指向pgbouncer:
DATABASE_URL=postgresql://langfuse:langfuse_password@pgbouncer:5432/langfuse
完整部署验证清单
部署完成后,按以下顺序验证每个组件:
# 1. 检查所有容器状态
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# 2. 验证n8n健康
curl -s http://localhost:5678/healthz
# 期望输出:{"status":"ok"}
# 3. 验证Langfuse健康
curl -s http://localhost:3000/api/public/health
# 期望输出包含 "status":"ok"
# 4. 验证Redis
docker exec redis redis-cli ping
# 期望输出:PONG
# 5. 验证PostgreSQL连接
docker exec postgres pg_isready -U langfuse
# 期望输出:accepting connections
# 6. 从外部访问(防火墙检查)
# 确保以下端口在VPS防火墙开放:
# 5678 (n8n), 3000 (Langfuse), 5432 (postgres对外暴露危险,限制源IP)
sudo ufw allow 5678/tcp
sudo ufw allow 3000/tcp
踩坑总结
| 问题 | 根因 | 解决方案 |
|---|---|---|
| n8n 502但容器Running | n8n进程崩溃,容器活着但服务死了 | 检查`docker logs`、修复数据目录权限、加内存限制 |
| 环境变量不生效 | 没写在environment段,或变量名写错 | 显式在docker-compose.yml environment段定义 |
| Langfuse v3 Redis必选 | v3架构变更,Redis从可选变必选 | 添加Redis服务,更新docker-compose.yml |
| 2GB VPS OOM | 总内存不足,突发流量触发OOM Killer | 加swap、为各容器设mem_limit、调PostgreSQL内存 |
| PostgreSQL连接池耗尽 | max_connections默认100不够用 | 限制各容器连接数或加PgBouncer |
硬件建议: 如果预算允许,将VPS升级到2核4GB(约50元/月),可以避免大部分内存相关问题。2GB适合单跑n8n或Langfuse,但同时跑两者需要swap+内存限制的组合配置。
👉 体验AI编程工具的实际效果,推荐尝试MiniMax的Token Plan,高性价比适合个人开发者实验各类AI工作流:立即参与
📌 This article was AI-assisted generated and human-reviewed | TechPassive — An AI-driven content testing site focused on real tool reviews
🔗 Recommended Tools
These are carefully selected tools. Using our affiliate links supports us to keep producing quality content: