Skip to content

Self-Hosting & Operations

Invariant is designed to be self-hosted on your own cloud infrastructure (AWS RDS, GCP Cloud SQL, Supabase, Azure Database for PostgreSQL, or bare-metal Linux servers) with PostgreSQL.


1. Prerequisites

  • Node.js: v18.x, v20.x, or v22.x+
  • PostgreSQL: v14+ (Postgres 16 recommended)

2. Quick Setup with Docker Compose

Spin up a PostgreSQL 16 container with a basic local healthcheck. Production credentials, TLS, backups, upgrades, monitoring, and network policy remain deployment responsibilities:

yaml
# docker-compose.yml
version: "3.8"

services:
  postgres:
    image: postgres:16-alpine
    container_name: invariant-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgrespassword
      POSTGRES_DB: invariant
    ports:
      - "5432:5432"
    volumes:
      - invariant_pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d invariant"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  invariant_pgdata:

Start the database:

bash
docker compose up -d

3. Database Schema Initialization

Invariant provides two standard patterns for provisioning the required database tables:

Initialize tables automatically when your application or worker boots up:

ts
import { invariant } from "@invariant-tech/sdk";
import { PostgresRuntimeStore } from "@invariant-tech/postgres";
import { Pool } from "pg";

const pool = new Pool({
  connectionString: process.env.DATABASE_URL || "postgres://postgres:postgrespassword@localhost:5432/invariant",
});

export const store = new PostgresRuntimeStore({ pool });

// Initialize DDL tables and indexes if they do not already exist
await store.initializeSchema();

export const app = invariant({
  storage: store,
});

Pattern B: Versioned SQL for DBA Pipelines

For environments where Flyway, Liquibase, Terraform, or another operator-owned pipeline controls DDL, execute the versioned migration shipped in the installed package:

bash
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 \
  -f node_modules/@invariant-tech/postgres/src/schema.sql

The script creates and records invariant_schema_migrations in the same transaction as the schema. Pin the package version in the migration artifact, retain the applied SQL with deployment records, and use only one schema owner per environment. Do not copy a DDL block from this page into an independent migration because that copy can drift from the runtime.

See Database Schema & Data Lifecycle for the canonical table contract, migration rules, backup/restore procedure, and retention behavior.


4. Multi-Worker Primitives & Distributed Leases

The store exposes OCC and lease primitives for future horizontal coordination. The current public Beta lacks command claim/read and arbitrary-runId runnable-work attachment APIs, so the diagram below is a target architecture rather than an implementable portable worker loop. Request-driven restoration at a registered .wait() boundary is already supported through the owning Session.

text
               ┌────────────────────────────────────────────────────────┐
               │              Shared PostgreSQL Database                │
               │   (workflow_executions, outbox_commands, leases)      │
               └───────────▲──────────────────────────────▲─────────────┘
                           │                              │
              Lease: worker_1 (active)       Lease: rejected (run locked)
                           │                              │
                 ┌─────────┴─────────┐          ┌─────────┴─────────┐
                 │   Host Worker 1   │          │   Host Worker 2   │
                 │ (Processes Run A) │          │ (Processes Run B) │
                 └───────────────────┘          └───────────────────┘

Storage-Layer Coordination Mechanics

  1. Single-Worker Run Authority: When a worker starts processing a runId, it calls store.acquireLease(runId, workerId, ttlMs). PostgreSQL enforces row-level locks on execution_leases, preventing peer workers from acquiring the same run concurrently.
  2. Incomplete Public Work-Recovery Contract: A future host can use lease expiry and runnable discovery, but portable pending-command claim and arbitrary-runId runnable-work attachment are not yet exposed. This does not remove the existing Session-owned .wait() restoration path.
  3. Atomic Commit Protection: Even if a network split causes two workers to attempt writes, Optimistic Concurrency Control (expectedRevision) rejects the stale worker with STALE_TRANSITION.

Do not advertise cross-process recovery with the current public Beta. The adapter persists required facts, but the portable orchestration contract is incomplete. Use the canonical Recovery Contract Matrix as the release boundary.


5. Next Steps

Invariant Durable Execution Engine.