Skip to content
Get Started

Runtime & Deployment

The primary way to run Zebric in development:

Terminal window
zebric dev --blueprint blueprint.toml --port 3000 --seed

This starts a Hono HTTP server with:

  • Hot reload via WebSocket (blueprint changes apply instantly)
  • SQLite database (auto-created from entity definitions)
  • Server-side HTML rendering with Tailwind CSS
  • CSRF protection, security headers, and session management

When using the engine programmatically:

import { ZebricEngine } from '@zebric/runtime-node'
const engine = new ZebricEngine({
blueprintPath: './blueprint.toml',
port: 3000,
host: 'localhost',
database: {
type: 'sqlite', // or 'postgres'
filename: './data.db', // SQLite path
},
dev: {
hotReload: true,
seed: true,
logLevel: 'info', // debug | info | warn | error
logQueries: false,
},
})
await engine.start()

SQLite (default):

database: {
type: 'sqlite',
filename: './data.db'
}

PostgreSQL:

database: {
type: 'postgres',
url: 'postgres://user:pass@localhost:5432/mydb'
}

Deploy to Cloudflare Workers using @zebric/runtime-worker — same blueprint format, running on Cloudflare’s edge network. The fastest way to start is copying starters/cloudflare-workers, which is a single-file entry point:

worker.ts
import { ZebricWorkersEngine } from '@zebric/runtime-worker'
import type { WorkersEnv } from '@zebric/runtime-worker'
import blueprintToml from './blueprint.toml'
export default {
async fetch(request: Request, env: WorkersEnv, ctx: ExecutionContext): Promise<Response> {
const engine = new ZebricWorkersEngine({
env,
blueprintContent: blueprintToml,
blueprintFormat: 'toml',
})
return engine.fetch(request)
},
}

WorkersEnv bindings: DB (D1, required), CACHE_KV, FILES_R2, SESSION_KV (all optional — enabling SESSION_KV turns on session-backed forms with CSRF protection).

wrangler.toml
name = "my-zebric-app"
main = "worker.ts"
compatibility_date = "2025-01-10"
compatibility_flags = ["nodejs_compat"]
[[d1_databases]]
binding = "DB"
database_name = "my-zebric-db"
database_id = "your-database-id" # from: wrangler d1 create my-zebric-db
# Optional
[[kv_namespaces]]
binding = "SESSION_KV"
id = "your-session-kv-id" # from: wrangler kv:namespace create SESSION_KV
[[r2_buckets]]
binding = "FILES_R2"
bucket_name = "my-zebric-files"

Create the D1 database (tables are created automatically from your entities on first request), then run locally or deploy:

Terminal window
wrangler d1 create my-zebric-db # copy the database_id into wrangler.toml
wrangler dev # local dev at http://localhost:8787, with D1/KV/R2 emulation
wrangler deploy # deploy to your-app.your-subdomain.workers.dev

For a working reference with all three bindings wired up, see examples/workers-blog.

FROM node:22-slim
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --prod
COPY blueprint.toml ./
EXPOSE 3000
CMD ["npx", "zebric", "dev", "--blueprint", "blueprint.toml"]
Terminal window
wrangler deploy

Security events are written as JSON to ./data/audit.log (configurable). Events include authentication attempts, access control denials, CSRF violations, data access, and suspicious activity. Each entry includes timestamp, event type, severity, user context, and sanitized metadata.

Sensitive fields (passwords, tokens, API keys) are automatically redacted.

The engine exposes Prometheus-format metrics with zbl_* prefixed counters and histograms, registered on both the main app port and the admin port:

GET http://localhost:3000/metrics # main app port
GET http://localhost:3030/metrics # admin port (127.0.0.1 only)

Available metrics:

  • zbl_requests_total — total HTTP requests
  • zbl_request_duration_ms — request duration histogram (buckets: 25, 50, 100, 250, 500, 1000, 2000ms)
  • zbl_requests_by_route_total — requests per route
  • zbl_requests_by_status_total — requests by status code family (2xx, 4xx, etc.)
  • zbl_query_duration_ms — database query duration histogram per entity
  • zbl_route_cache_hits / zbl_route_cache_misses — route cache performance
GET http://localhost:3000/health # main app port
GET http://localhost:3030/health # admin port

Returns health status with a 200 (healthy) or 503 (unhealthy) status code.

docker-compose.dev.yml includes Prometheus and Grafana services pre-configured to scrape your local Zebric instance.

Terminal window
# Prometheus only
docker compose -f docker-compose.dev.yml up zebric-dev prometheus-dev
# Prometheus + Grafana
docker compose -f docker-compose.dev.yml up zebric-dev prometheus-dev grafana-dev
  • Prometheus UI: http://localhost:9091 — check Status → Targets to confirm zebric-engine shows UP, or query directly at /graph?g0.expr=zbl_requests_total
  • Grafana: http://localhost:3001 (default credentials admin / admin). It has no Prometheus data source pre-configured — add one manually under Connections → Data sources, type Prometheus, URL http://prometheus-dev:9090 (the Docker service name, not localhost)

The scrape config lives at monitoring/prometheus-dev.yml (10s interval, targets zebric-dev:3000). Update the targets field there if you change the app port or container name.

Prometheus data persists in the prometheus_dev_data volume between runs; docker compose -f docker-compose.dev.yml down -v resets it.

The admin server runs on a separate port (default: 3030, bound to 127.0.0.1 only) and exposes:

EndpointDescription
/healthEngine health status
/metricsPrometheus-format metrics
/blueprintCurrent parsed blueprint
/stateEngine state
/entitiesEntity summary
/pagesPage routes and layouts
/workflowsWorkflow definitions, job counts, and stats
/workflows/visualizationHTML workflow graph visualization
/tracesRequest traces (with ?limit=N)
/traces/errorsError traces
/traces/slowSlow traces (with ?threshold=1000)
/traces/statsTrace statistics
/schema-diffPending database schema changes
/pluginsLoaded plugin list