workflow
Version:
Workflow DevKit - Build durable, resilient, and observable workflows
211 lines (142 loc) • 6.55 kB
text/mdx
---
title: Postgres World
description: Production-ready, self-hosted world using PostgreSQL for storage and graphile-worker for job processing.
type: integration
summary: Deploy workflows to your own infrastructure using PostgreSQL and graphile-worker.
prerequisites:
- /docs/deploying
related:
- /docs/deploying/world/local-world
- /docs/deploying/world/vercel-world
---
The Postgres World is a production-ready backend for self-hosted deployments. It uses PostgreSQL for durable storage and [graphile-worker](https://github.com/graphile/worker) for reliable job processing.
Use the Postgres World when you need to deploy workflows on your own infrastructure outside of Vercel - such as a Docker container, Kubernetes cluster, or any cloud that supports long-running servers.
Install the Postgres World package in your workflow project:
```package-install
@workflow/world-postgres
```
Configure the required environment variables to use the world and point it to your PostgreSQL database:
```bash title=".env"
WORKFLOW_TARGET_WORLD="@workflow/world-postgres"
WORKFLOW_POSTGRES_URL="postgres://user:password@host:5432/database"
```
Run the migration script to create the necessary tables in your database. Ensure `WORKFLOW_POSTGRES_URL` is set when running this command:
```bash
npx workflow-postgres-setup
```
<Callout type="info">
The migration is idempotent and can safely be run as a post-deployment lifecycle script.
</Callout>
To subscribe to the graphile-worker queue, your workflow app needs to start the world on server start. Here are examples for a few frameworks:
<Tabs items={["Next.js", "SvelteKit", "Nitro"]}>
<Tab value="Next.js">
Create an `instrumentation.ts` file in your project root:
```ts title="instrumentation.ts" lineNumbers
export async function register() {
if (process.env.NEXT_RUNTIME !== "edge") {
const { getWorld } = await import("workflow/runtime");
await getWorld().start?.();
}
}
```
<Callout type="info">
Learn more about [Next.js Instrumentation](https://nextjs.org/docs/app/guides/instrumentation).
</Callout>
</Tab>
<Tab value="SvelteKit">
Create a `src/hooks.server.ts` file:
```ts title="src/hooks.server.ts" lineNumbers
import type { ServerInit } from "@sveltejs/kit";
export const init: ServerInit = async () => {
const { getWorld } = await import("workflow/runtime");
await getWorld().start?.();
};
```
<Callout type="info">
Learn more about [SvelteKit Hooks](https://svelte.dev/docs/kit/hooks).
</Callout>
</Tab>
<Tab value="Nitro">
Create a plugin to start the world on server initialization:
```ts title="plugins/start-pg-world.ts" lineNumbers
import { defineNitroPlugin } from "nitro/~internal/runtime/plugin";
export default defineNitroPlugin(async () => {
const { getWorld } = await import("workflow/runtime");
await getWorld().start?.();
});
```
Register the plugin in your config:
```ts title="nitro.config.ts"
import { defineNitroConfig } from "nitropack";
export default defineNitroConfig({
modules: ["workflow/nitro"],
plugins: ["plugins/start-pg-world.ts"],
});
```
<Callout type="info">
Learn more about [Nitro Plugins](https://v3.nitro.build/docs/plugins).
</Callout>
</Tab>
</Tabs>
<Callout type="info">
The Postgres World requires a long-lived worker process that polls the database for jobs. This does not work on serverless environments. For Vercel deployments, use the [Vercel World](/worlds/vercel) instead.
</Callout>
Use the `workflow` CLI to inspect workflows stored in PostgreSQL:
```bash
export WORKFLOW_POSTGRES_URL="postgres://user:password@host:5432/database"
npx workflow inspect runs --backend @workflow/world-postgres
npx workflow web --backend @workflow/world-postgres
```
If `WORKFLOW_POSTGRES_URL` is not set, the CLI defaults to `postgres://world:world@localhost:5432/world`.
Learn more in the [Observability](/docs/observability) documentation.
<WorldTestingPerformance />
All configuration options can be set via environment variables or programmatically via `createWorld()`.
PostgreSQL connection string. Falls back to `DATABASE_URL` if not set.
Default: `postgres://world:world@localhost:5432/world`
Prefix for graphile-worker queue job names. Useful when sharing a database between multiple applications.
Number of concurrent workers polling for jobs. Default: `10`
{/* @skip-typecheck: incomplete code sample */}
```typescript title="workflow.config.ts" lineNumbers
import { createWorld } from "@workflow/world-postgres";
const world = createWorld({
connectionString: "postgres://user:password@host:5432/database",
jobPrefix: "myapp_",
queueConcurrency: 20,
});
```
The Postgres World uses PostgreSQL as a durable backend:
- **Storage** - Workflow runs, events, steps, and hooks are stored in PostgreSQL tables
- **Job Queue** - [graphile-worker](https://github.com/graphile/worker) handles reliable job processing with retries
- **Streaming** - PostgreSQL NOTIFY/LISTEN enables real-time event distribution
This architecture ensures workflows survive application restarts with all state reliably persisted. For implementation details, see the [source code](https://github.com/vercel/workflow/tree/main/packages/world-postgres).
Deploy your application to any cloud that supports long-running servers:
- Docker containers
- Kubernetes clusters
- Virtual machines
- Platform-as-a-Service providers (Railway, Render, Fly.io, etc.)
Ensure your deployment has:
1. Network access to your PostgreSQL database
2. Environment variables configured correctly
3. The `start()` function called on server initialization
<Callout type="info">
The Postgres World is not compatible with Vercel deployments. On Vercel, workflows automatically use the [Vercel World](/worlds/vercel) with zero configuration.
</Callout>
- **Requires long-running process** - Must call `start()` on server initialization; not compatible with serverless platforms
- **PostgreSQL infrastructure** - Requires a PostgreSQL database (self-hosted or managed)
- **Not compatible with Vercel** - Use the [Vercel World](/worlds/vercel) for Vercel deployments
For local development, use the [Local World](/worlds/local) which requires no external services.