← Kembali ke Blog

Self-Host n8n Queue Mode: Postgres, Redis, dan AI Agent yang Tahan Traffic Nyata

Webhook masuk, agent jalan, lalu model memutuskan butuh empat tool call. Sembilan puluh detik kemudian workflow baru selesai. Di instalasi n8n default, proses yang sama juga melayani editor, API, dan semua trigger lain yang kamu punya. Buka tab kedua, UI-nya macet menunggu.

Queue mode memisahkan dua pekerjaan itu. Satu proses menerima webhook dan timer, worker mengambil eksekusi dari antrean Redis dan menjalankannya, Postgres menyimpan state yang dibutuhkan keduanya. Tambah worker kedua nanti, throughput naik tanpa mengubah workflow.

Yang saya tulis di bawah ini adalah setup yang akan saya pakai hari ini: n8n 2.45.7, Postgres 18, Redis, satu worker, plus sidecar task runner. Setelah itu workflow AI Agent yang memanggil tool dari MCP server eksternal, lalu kebalikannya: Claude Desktop memanggil workflow n8n sebagai tool MCP.

Yang perlu disiapkan

  • Docker Engine dengan Compose v2. Cek pakai docker compose version.
  • Host minimal 4 GB RAM dan 2 vCPU. Tiap proses n8n punya sidecar task runner sendiri, jadi hitung juga kebutuhan itu.
  • API key LLM untuk bagian agent.
  • Domain dan TLS kalau webhook atau endpoint MCP harus bisa diakses dari luar.

Langkah 1: bikin secret dulu

mkdir -p ~/n8n && cd ~/n8n
openssl rand -hex 32   # N8N_ENCRYPTION_KEY
openssl rand -hex 24   # N8N_RUNNERS_AUTH_TOKEN

Isi .env:

N8N_VERSION=2.45.7
POSTGRES_USER=n8n
POSTGRES_PASSWORD=ganti-ini
POSTGRES_DB=n8n
N8N_ENCRYPTION_KEY=ganti-dengan-64-karakter-hex
N8N_RUNNERS_AUTH_TOKEN=ganti-dengan-48-karakter-hex
N8N_WEBHOOK_URL=https://n8n.example.com
TZ=Asia/Jakarta

Encryption key sering dianggap remeh. Saat boot pertama n8n membuat satu key dan menyimpannya di /home/node/.n8n. Semua worker dan proses webhook harus memakai key yang sama persis, kalau tidak, credential yang dipakai node tidak bisa didekripsi. Set eksplisit dari awal, simpan di secret manager, dan anggap hilang berarti harus input ulang semua credential.

Langkah 2: compose.yml

x-n8n-env: &n8n-env
  DB_TYPE: postgresdb
  DB_POSTGRESDB_HOST: postgres
  DB_POSTGRESDB_PORT: "5432"
  DB_POSTGRESDB_DATABASE: ${POSTGRES_DB}
  DB_POSTGRESDB_USER: ${POSTGRES_USER}
  DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
  EXECUTIONS_MODE: queue
  QUEUE_BULL_REDIS_HOST: redis
  QUEUE_BULL_REDIS_PORT: "6379"
  N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
  N8N_WEBHOOK_URL: ${N8N_WEBHOOK_URL}
  GENERIC_TIMEZONE: ${TZ}
  TZ: ${TZ}
  N8N_RUNNERS_MODE: external
  N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
  N8N_RUNNERS_BROKER_LISTEN_ADDRESS: 0.0.0.0

services:
  postgres:
    image: postgres:18
    restart: always
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
      PGDATA: /var/lib/postgresql/data
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s
      retries: 10

  redis:
    image: redis:7-alpine
    restart: always
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      retries: 10

  n8n-main:
    image: docker.n8n.io/n8nio/n8n:${N8N_VERSION}
    restart: always
    ports:
      - "5678:5678"
    environment:
      <<: *n8n-env
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  n8n-main-runner:
    image: n8nio/runners:${N8N_VERSION}
    restart: always
    environment:
      N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
      N8N_RUNNERS_TASK_BROKER_URI: http://n8n-main:5679
    depends_on:
      - n8n-main

  n8n-worker:
    image: docker.n8n.io/n8nio/n8n:${N8N_VERSION}
    restart: always
    command: ["worker", "--concurrency=10"]
    environment:
      <<: *n8n-env
      QUEUE_HEALTH_CHECK_ACTIVE: "true"
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  n8n-worker-runner:
    image: n8nio/runners:${N8N_VERSION}
    restart: always
    environment:
      N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
      N8N_RUNNERS_TASK_BROKER_URI: http://n8n-worker:5679
    depends_on:
      - n8n-worker

volumes:
  db_data:
  redis_data:
  n8n_data:

Ada tiga detail di file itu yang gampang salah.

PGDATA di Postgres 18. Image resminya memindahkan direktori data default di v18. Tanpa variabel ini, volume tidak menutupi lokasi tulis Postgres yang sebenarnya, dan database terlihat kosong setiap kali container restart.

Sidecar runner per proses. Di mode external, image n8nio/runners berisi task runner JS dan Python, dan tiap proses n8n butuh sidecar sendiri. Queue mode dengan satu worker berarti dua sidecar: satu untuk main, satu untuk worker. Ini lapisan isolasi antara kode dari node Code dan sisa sistem, jadi container tambahannya sepadan.

Volume n8n_data dipakai bersama. Kedua proses mount volume yang sama karena keduanya perlu encryption key dan config instance. Workflow dan eksekusi ada di database, sedangkan key ada di volume.

Langkah 3: nyalakan dan buktikan

docker compose up -d
docker compose ps
curl -sf localhost:5678/healthz && echo " main ok"
docker compose logs -f n8n-worker

Buka http://localhost:5678, buat akun owner, lalu masuk ke Settings dan pilih Workers. Worker kamu harus muncul lengkap dengan concurrency dan jumlah job yang sedang jalan. Kalau daftarnya kosong, worker gagal konek ke Redis atau start dengan encryption key berbeda dari main. Dua kegagalan itu tampil sama di UI, jadi cek docker compose logs n8n-worker dulu sebelum mulai mengubah env sana-sini.

Worker hanya membuka /healthz dan /healthz/readiness kalau QUEUE_HEALTH_CHECK_ACTIVE=true. Kalau load balancer perlu probe, publish port-nya dan arahkan health check ke situ.

Langkah 4: agent yang memanggil MCP server eksternal

Bikin workflow dengan Webhook trigger dan node AI Agent. Pasang dua sub-node: chat model (OpenAI Chat Model, Anthropic Chat Model, mana yang key-nya ada) dan MCP Client Tool.

Di node MCP Client Tool:

  • SSE Endpoint: URL MCP server, misalnya https://mcp.example.com/sse.
  • Authentication: None, Bearer, Header, Multiple Headers, atau OAuth2. Pakai Bearer dan tempel token untuk server yang tidak sepenuhnya kamu percaya.
  • Tools to Include: All, Selected, atau All Except.

Pilih Selected dan daftarkan hanya tool yang dibutuhkan agent. Setiap tool yang diekspos dideskripsikan ke context model di tiap turn, jadi server dengan empat puluh tool membakar token untuk deskripsi dan bikin pemilihan tool lebih buruk. Filter di node jauh lebih murah daripada memperbaikinya lewat prompt.

Lalu batasi perilakunya di system message:

You are an operations assistant. Use the MCP tools for anything factual.
Rules:
1. Never guess a value a tool can return.
2. If a tool fails twice, stop and report the error text verbatim.
3. Answer in at most five sentences unless asked for detail.

Satu catatan versi: setting agent type di node AI Agent sudah deprecated sejak n8n 1.82.0, dan semua node AI Agent sekarang berjalan sebagai Tools Agent. Versi 1 node itu dihapus di n8n 3.0, jadi upgrade node lama sebelum migrasi. Kalau ada tutorial yang menyuruh memilih "SQL Agent" atau "Conversational Agent", tutorialnya lebih tua dari perubahan itu. Pakai tool sub-node database dengan Agent node versi sekarang.

Langkah 5: jadikan n8n sebagai MCP server

Ini arah yang sering terlewat. Taruh MCP Server Trigger di workflow kosong, sambungkan Custom n8n Workflow Tool ke workflow yang mau diekspos, dan set authentication ke Bearer. n8n memberi dua URL: test URL yang menampilkan data ke editor saat kamu membangun, dan production URL yang aktif setelah workflow di-publish.

n8n tidak mendukung stdio, hanya SSE dan streamable HTTP. Untuk Claude Desktop, jembatani pakai mcp-remote:

{
  "mcpServers": {
    "n8n": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://n8n.example.com/mcp/your-path",
        "--header",
        "Authorization: Bearer ${AUTH_TOKEN}"
      ],
      "env": {
        "AUTH_TOKEN": "your-bearer-token"
      }
    }
  }
}

Kalau ada nginx di depan, location MCP harus mematikan buffering. SSE mempertahankan koneksi tetap terbuka, dan proxy yang buffering akan menahan event sampai response selesai, yang artinya tidak pernah.

location /mcp/ {
    proxy_http_version          1.1;
    proxy_buffering             off;
    gzip                        off;
    chunked_transfer_encoding   off;
    proxy_set_header            Connection '';
}

Bagian yang bikin queue mode bermasalah

  • SQLite tidak didukung di queue mode. Pakai Postgres atau MySQL, tanpa pengecualian.
  • Response webhook dikirim dari worker ke main lewat pesan Redis, dibatasi 64 MiB lewat N8N_WEBHOOK_RESPONSE_RELAY_SIZE_MAX. Batas yang sama berlaku untuk hasil tool dari workflow MCP Trigger, dan hasil tool tidak bisa di-offload.
  • N8N_WEBHOOK_URL menggantikan WEBHOOK_URL mulai n8n 2.30.0. Versi lama mengabaikan nama baru.
  • n8n 2.0 menghapus QUEUE_WORKER_MAX_STALLED_COUNT dan membuat N8N_RUNNERS_ENABLED tidak lagi perlu.
  • Kalau ada beberapa webhook replica, arahkan semua request /mcp* ke satu replica khusus. Koneksi persisten dan round-robin tidak cocok.
  • Concurrency default 10 per worker. Nilai sangat kecil yang disebar ke banyak worker menghabiskan connection pool Postgres, dan gejalanya berupa delay acak, bukan error yang jelas.

Kapan pakai ini, kapan pilih yang lain

Pakai n8n kalau pekerjaannya didominasi integrasi plus beberapa langkah AI, dan orang yang tidak menulis kode perlu ikut mengedit workflow. Di situ kekuatannya, dan queue mode membuatnya tahan beban.

Pakai Dify atau Flowise kalau produknya aplikasi chat atau RAG, dan UI-nya memang seharusnya soal prompt, knowledge base, dan setting model, bukan node HTTP.

Pakai Temporal atau LangGraph kalau workflow-nya logika bisnis inti yang harus masuk version control, punya test, lewat code review, dan tidak butuh editor visual. Kamu tukar library node dengan kontrol penuh.

Soal lisensi: kode n8n source-available di bawah Sustainable Use License, dan n8n sendiri tidak menyebutnya open source karena lisensinya membatasi penggunaan komersial. Pemakaian internal bisnis gratis. White-label, menjual ulang sebagai produk otomasi, atau membiarkan pelanggan membangun workflow lewat UI kamu, tidak.

Kalau cuma ingat satu hal

Masalah di n8n self-hosted jarang ada di workflow-nya. Biasanya di satu proses yang mencoba melayani editor, menahan koneksi webhook, dan menjalankan rantai agent panjang sekaligus. Pisahkan eksekusi ke worker, bagikan encryption key dengan benar, dan jalankan sidecar runner di mode external. Editor tetap responsif dan tool call yang lama berhenti jadi masalah orang lain.

Referensi

Butuh Bantuan Implementasi?

Saya membantu tim mendesain dan membangun infrastruktur cloud scalable, pipeline DevOps, dan sistem production-grade.

Konsultasi Gratis