Files
vercel__workflow/docs/content/worlds/v4/postgres.mdx
Nathan Rajlich e1e64e3de3 docs: apply Vercel technical writing standards (#3704)
* docs: apply Vercel technical writing standards

Audit the complete documentation corpus, package READMEs, skills, and
source TSDoc/comments against the vercel-technical-writing skill and
style-rules.md. Normalize sentence-case headings without changing
published anchors, remove prose em dashes and filler wording, improve
active voice and self-contained phrasing, standardize product/brand
capitalization, American English, list punctuation, units, and code
fence languages, and preserve exact runtime strings/table placeholders.

All executable code is unchanged. Modified skills have their metadata
versions bumped.

* docs: extend writing audit to repository Markdown

Apply the same technical-writing rules to design documents, compiler
specifications, workbench guides, package changelogs, and the remaining
tracked Markdown outside the deployed docs corpus. Preserve historical
meaning, commands, output literals, table placeholders, and heading
anchors.

* docs: exclude generated package changelogs from audit
2026-08-21 14:24:31 -07:00

265 lines
8.1 KiB
Plaintext

---
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:
- /worlds/local
- /worlds/vercel
---
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 to deploy workflows on your own infrastructure outside Vercel, such as a Docker container, Kubernetes cluster, or any cloud that supports long-running servers.
## Installation
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:
<Tabs items={["npm", "pnpm", "Yarn", "Bun"]}>
<Tab value="npm">
```bash
npx --package=@workflow/world-postgres bootstrap
```
</Tab>
<Tab value="pnpm">
```bash
pnpm dlx --package @workflow/world-postgres bootstrap
```
</Tab>
<Tab value="Yarn">
```bash
yarn dlx --package @workflow/world-postgres bootstrap
```
</Tab>
<Tab value="Bun">
```bash
bunx --package @workflow/world-postgres bootstrap
```
</Tab>
</Tabs>
<Callout type="info">
The migration is idempotent and can safely be run as a post-deployment lifecycle script.
</Callout>
## Starting the World
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:
<Callout type="warn">
This step is specific to worlds that run a background worker, such as Postgres
World. Worlds whose queue delivers work over HTTP, including the Vercel World,
have no worker to subscribe, so `start()` does nothing there while the import
still adds overhead. Pulling in `workflow/runtime` from a server-startup hook puts
the whole runtime into the cold-start path before the first request is served.
</Callout>
<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");
const world = await getWorld();
await world.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");
const world = await getWorld();
await world.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");
const world = await getWorld();
await world.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>
## Observability
Use the Workflow CLI to inspect workflows stored in PostgreSQL:
```bash
# Set your database URL
export WORKFLOW_POSTGRES_URL="postgres://user:password@host:5432/database"
# List workflow runs
npx workflow inspect runs --backend @workflow/world-postgres
# Launch the web UI
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.
## Testing & compatibility
<WorldTestingPerformance worldId="postgres" />
## Configuration
You can set all configuration options through environment variables or programmatically through `createWorld()`.
### `WORKFLOW_POSTGRES_URL` (required)
PostgreSQL connection string. Falls back to `DATABASE_URL` if not set.
Default: `postgres://world:world@localhost:5432/world`
### `WORKFLOW_POSTGRES_JOB_PREFIX`
Prefix for graphile-worker queue job names. Useful when sharing a database between multiple applications.
### `WORKFLOW_POSTGRES_WORKER_CONCURRENCY`
Number of concurrent workers polling for jobs. Default: `50`.
This value also bounds how many parent→child workflow polls can be in flight simultaneously. Every `await childRun.returnValue` inside a workflow holds a worker slot until the child run terminates. If you expect recursive or highly-fanned-out parent/child workflows, raise this ceiling above the peak number of concurrent polls. With the default of 50, the included `fibonacciWorkflow` e2e test (fib(6), ~24 concurrent polls at peak) passes; deeper recursion or larger fanouts need a correspondingly larger setting.
### `WORKFLOW_POSTGRES_MAX_POOL_SIZE`
Maximum size of the internal `pg.Pool` used when `createWorld()` constructs the pool. Default: `10`.
For higher worker concurrency, Graphile Worker recommends setting `maxPoolSize` to `10` or `queueConcurrency + 2`, whichever is larger.
### Programmatic configuration
{/*@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: 50,
maxPoolSize: 52, // overrides WORKFLOW_POSTGRES_MAX_POOL_SIZE
});
```
## How it works
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).
## Deployment
Deploy your application to any cloud that supports long-running servers:
- Docker containers
- Kubernetes clusters
- Virtual machines
- Platform-as-a-service (PaaS) providers, such as Railway, Render, and Fly.io
Ensure your deployment has:
- Network access to your PostgreSQL database
- Environment variables configured correctly
- 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>
## Limitations
- **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.