Self-hosted Inngest — a durable execution engine for reliable background jobs and AI workflows. Includes PostgreSQL and Redis.
Zeabur
Zeabur
ZeaburInngest is an open-source durable execution engine that lets you write reliable background jobs, scheduled functions, and AI workflows with built-in retries, concurrency control, and observability.
This template deploys a self-hosted Inngest instance with PostgreSQL (state persistence) and Redis (queue and cache) in a single click.
| Service | Port | Purpose |
|---|---|---|
| Inngest Dashboard & API | 8288 (HTTP) | UI, event ingestion, REST API |
| Inngest Connect Gateway | 8289 (HTTP/WSS) | WebSocket gateway for SDK workers |
| PostgreSQL | 5432 | Durable state storage |
| Redis | 6379 | Queue and cache |
You need two hex strings (even number of characters, using 0-9 and a-f only).
Generate on macOS/Linux:
openssl rand -hex 16
Use one value for Event Key and another for Signing Key.
By design, Inngest self-hosted does not protect the dashboard UI or GraphQL API with authentication. The source code only applies the signing key middleware to SDK-facing endpoints:
| Endpoint | Protected |
|---|---|
| Dashboard UI | ❌ Public |
GraphQL API (/v0/gql) | ❌ Public — anyone can query all apps, functions, and events |
Event ingestion (/e/*) | ✅ Requires Event Key |
SDK sync (/fn/register) | ✅ Requires Signing Key |
| Connect WebSocket | ✅ Requires Signing Key |
Recommended: do not expose the Dashboard domain publicly. Place it behind a VPN, IP allowlist, or HTTP Basic Auth proxy. The Connect Gateway domain (port 8289) must remain publicly accessible for SDK workers to connect.
After deployment, configure your Inngest SDK to point to your self-hosted instance:
const inngest = new Inngest({
id: "my-app",
baseUrl: "https://YOUR_DOMAIN", // Dashboard domain (port 8288)
eventKey: "YOUR_EVENT_KEY",
signingKey: "YOUR_SIGNING_KEY",
});
For Connect (persistent WebSocket workers), override the gateway URL with your Connect domain:
import { connect } from "inngest/connect";
// ⚠️ Use `triggers` (plural array), NOT `trigger` (singular)
const myFn = inngest.createFunction(
{ id: "my-fn", triggers: [{ event: "my/event" }] },
async ({ event, step }) => { /* ... */ }
);
await connect({
apps: [{ client: inngest, functions: [myFn] }],
gatewayUrl: "wss://YOUR_CONNECT_DOMAIN/v0/connect",
});
Note: The Connect gateway WebSocket path is
/v0/connect. Always usetriggers: [...](plural array) when defining functions — usingtrigger:(singular) will result in empty triggers and functions will never execute.
Inngest is open-source under the Apache 2.0 License.
Zeabur