Reference Dockerfile
QiForge has no runtime dependency on itself — anywhere Node 22+ runs, an oracle runs. This Dockerfile is the shortest path frompnpm dev to a container you can ship.
apps/qiforge-example) builds with pnpm build and runs with node dist/main.js — see apps/qiforge-example/src/main.ts.
Prerequisites
- Node.js 22+
- A reachable Matrix homeserver
- A persistent volume for the Matrix store and SQLite checkpointer
- Core env vars (per
baseEnvSchema) - Optional: Redis (only if
creditsortasksplugins are loaded)
Build and ship
1
Build the production bundle
tsc -p tsconfig.build.json and outputs dist/. Your package.json’s start script should be node dist/main.js.2
Pin every required env var
Production Per-plugin vars come on top — see environment variables reference for the full list. Boot validates every var declared by every loaded plugin’s
.env (or your platform’s secret manager) needs every core var plus the vars for each plugin you’ve kept.configSchema; missing required vars fail loudly.3
Run the entity setup once per deployment
Identity and Matrix keys are provisioned via the CLI — see identity and auth for the full flow.
Persistent volumes
1
Mount /data as a persistent volume
Two things must survive restarts. Lose either and you lose state.On Railway / Fly.io: attach a persistent volume mounted at
In Docker Compose:
/data.2
Add Redis only when needed
Redis is required only if you load the Pass the Redis client to
credits plugin (or the tasks plugin once it ships). Without those, skip it entirely.CreditsPlugin explicitly — see enable bundled plugins.Health probes
1
Use /health as the liveness probe
The framework exposes Kubernetes:
GET /health, always public, never goes through AuthHeaderMiddleware. Returns 200 once Nest is up. The built-in auth-excluded routes are / (a JSON landing payload), /health, /docs, and /docs/(.*) — everything else requires auth.Docker:2
There is no separate readiness probe
Matrix init runs in the background. The process accepts requests immediately after Nest boots. If you need to delay traffic until Matrix is up, subscribe via
app.onPluginStatusChange and signal your platform when matrix transitions to loaded — see observability.3
The /docs Swagger UI is also auth-excluded
Useful for debugging in production behind a private network. To remove it in public deployments, host-supply your own Nest module that re-mounts
/docs behind auth, or block it at your reverse proxy.CORS
CORS_ORIGIN controls who can call your oracle from a browser.
The framework allows these headers on every request:
Authorization and x-auth-type carry the primary invocation-auth path (Authorization: Bearer <invocation> + X-Auth-Type: ucan); x-ucan-delegation carries the downstream-authorization delegation. If you replicate CORS at a reverse proxy, mirror this full list — dropping x-auth-type breaks browser clients on the primary auth path.
Logging
The runtime uses NestJS’s defaultLogger. Pipe stdout/stderr to your aggregator. Override the bootstrap logger with createOracleApp({ logger }) if you need a custom format — see createOracleApp reference.
For structured tracing instead of free-form logs, set the LangSmith env vars — see observability.
Graceful shutdown
The runtime registers aSIGTERM / SIGINT handler that drains in order: (1) flushes the per-user checkpoint to Matrix, (2) closes the Nest app, (3) shuts down the Matrix client, (4) runs any plugin teardowns. Each step is isolated — a failure in one is logged and the rest still run.
That first step is why a clean SIGTERM matters: an abrupt SIGKILL skips the checkpoint upload and loses recent thread state. See graceful-shutdown.ts.
Where to read next
Environment variables
Every var per plugin, exhaustively.
Observability
LangSmith, plugin lifecycle, logger.
Identity and auth
Entity setup and per-request UCAN auth.
Troubleshooting
Boot errors, Matrix issues, common runtime errors.