Skip to main content

Local Mode with PGLite

Steward includes a PGLite backend — full PostgreSQL running in-process via WebAssembly. This means you can run a complete Steward instance with zero external dependencies: no Postgres server, no Redis, no Docker. This is ideal for:
  • Local development — spin up Steward in seconds, no database setup
  • Desktop apps — Steward as an embedded sidecar (e.g., Electrobun apps)
  • Testing — in-memory mode for fast, isolated tests
  • Offline use — full functionality without network connectivity

Starting Local Mode

# Persistent mode — data saved to ~/.steward/data/
bun run packages/api/src/embedded.ts

# In-memory mode — resets on restart
STEWARD_PGLITE_MEMORY=true bun run packages/api/src/embedded.ts

# Custom data directory
STEWARD_PGLITE_PATH=/path/to/data bun run packages/api/src/embedded.ts
On first start you’ll see:
╔══════════════════════════════════════════╗
║     Steward — Local / Desktop Mode       ║
╚══════════════════════════════════════════╝
Data directory: /home/user/.steward/data

[embedded] Initializing PGLite database...
[pglite] Applied migration: 0001_initial_schema.sql
[pglite] Applied migration: 0002_policies.sql
[pglite] Database ready.
[embedded] Starting API server...
Steward API listening on http://127.0.0.1:3200
Subsequent starts skip migrations that have already been applied.

Environment Variables

VariableDefaultDescription
STEWARD_DB_MODEauto-detectSet to pglite to force PGLite even if DATABASE_URL is set
STEWARD_PGLITE_PATH~/.steward/dataDirectory for persistent storage
STEWARD_PGLITE_MEMORYfalseSet to true for in-memory (no persistence)
STEWARD_MASTER_PASSWORDauto-generatedVault encryption key — auto-generated in local mode
PORT3200API port
STEWARD_BIND_HOST127.0.0.1Bind host — 127.0.0.1 in local mode for security
In local mode, STEWARD_MASTER_PASSWORD is auto-generated if not provided. This is fine for development but means vault data encrypted with the generated key cannot be decrypted after restart unless you set a fixed password.

Auto-Detection

You can also use the regular API entry point — Steward auto-selects PGLite if:
  • STEWARD_DB_MODE=pglite is set, OR
  • DATABASE_URL is not set
# These are equivalent for local mode
bun run packages/api/src/embedded.ts

# or
STEWARD_DB_MODE=pglite bun run packages/api/src/index.ts
The embedded entry point is preferred because it initializes PGLite before importing the API, ensuring the database is ready before the first request.

Using with the SDK

Local mode exposes the same REST API — point the SDK at localhost:3200:
import { StewardClient } from "@stwd/sdk";

const client = new StewardClient({
  baseUrl: "http://localhost:3200",
  apiKey: "local-dev-key",
  tenantId: "my-app",
});

// Create a local tenant first
await fetch("http://localhost:3200/platform/tenants", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Steward-Platform-Key": process.env.STEWARD_PLATFORM_KEY!,
  },
  body: JSON.stringify({ id: "my-app", name: "My App" }),
});

// Then use the SDK normally
const agent = await client.createWallet("agent-1", "Local Agent");
console.log(agent.walletAddress);

Using with React Components

import { StewardProvider, WalletOverview } from "@stwd/react";
import { StewardClient } from "@stwd/sdk";
import "@stwd/react/styles.css";

const client = new StewardClient({
  baseUrl: "http://localhost:3200",
  apiKey: "local-dev-key",
  tenantId: "my-app",
});

function App() {
  return (
    <StewardProvider client={client} agentId="agent-1">
      <WalletOverview showQR />
    </StewardProvider>
  );
}

Data Persistence

PGLite persists data as files in the data directory:
~/.steward/data/
├── PG_VERSION
├── base/
│   └── ... (Postgres data files)
└── __steward_migrations   (migration tracking table)
To reset local data:
rm -rf ~/.steward/data
To back up local data:
cp -r ~/.steward/data ~/.steward/data.bak

In-Memory Mode for Tests

For unit/integration tests, in-memory mode is fastest:
// test-setup.ts
process.env.STEWARD_PGLITE_MEMORY = "true";
process.env.STEWARD_DB_MODE = "pglite";
process.env.STEWARD_MASTER_PASSWORD = "test-password-32-characters-long!";

// Import starts a fresh in-memory database every time
const { app } = await import("@stwd/api");
Each test run starts with a clean database — no cleanup needed.

Limitations vs Production Mode

FeatureLocal (PGLite)Production (Postgres)
Full SQL support
Migrations
Concurrent writesSingle-writerMulti-writer
Redis rate limits❌ (in-memory fallback)
Webhook delivery
Performance~10ms queries~1ms queries
ScaleSingle processHorizontal
PGLite is fully functional for development, testing, and light-usage desktop scenarios. For production multi-tenant deployments, use a real Postgres instance (Neon works great).