Skip to main content
The repository ships a complete self-hosted deployment in examples/docker: one container image, two services, one database file.
  • web (port 8080) — the authoring app: the widget, theme, schema and action designers over the published authoring surface (@widgentic/mcp/authoring), plus write-only secrets, API keys, and Settings (your default themes for light and dark hosts, the resource domains your widgets’ images and stylesheets may load from directly, and the stylesheets every frame links). The widget and theme designers preview in the surface’s /api/preview-frame: the document hosts mount, under the policy the MCP endpoint declares for you, so a preview loads only what a conversation would.
  • mcp (port 8081) — the Streamable HTTP MCP endpoint at /mcp. Mint a key under Keys and connect any MCP host with an x-api-key header (or ?key= where the host cannot send headers).
An entry you save in the app is served on the MCP endpoint’s next tool call — same file, no cache, no restart. The store is createSqliteStore: SQLite compiled into the Node runtime, so the whole deployment has no runtime dependency to operate.

The trust shape

The two services mirror the hosted product’s split:

Identity without an identity provider

By default the deployment serves one principal with no sign-in. That mode belongs on localhost or a trusted network — treat the app like any admin panel without a login. Multi-user comes from the auth proxy you already run, not from widgentic:
Each verified header value (from oauth2-proxy, Cloudflare Access, Authelia, Tailscale Serve, …) becomes its own account, stable across restarts. The header is never read while the variable is unset, so a spoofed header changes nothing; once set the app fails closed — a request without the header is refused rather than served the default account. Your proxy must strip the header from inbound client requests.

Secrets and key custody

Secret values are envelope-encrypted at rest; the database file holds ciphertext and key digests only. The key-encryption key is yours to supply — generated once, handed to both services as a mounted file (WIDGENTIC_KEK_FILE, the compose file uses a docker secret) or the WIDGENTIC_KEK variable, and never generated at boot: a per-boot key would write records nothing can read back. With no KEK the Secrets section is off and everything else works. Name the trade honestly: here the KEK lives in the process, which is weaker custody than the hosted product’s, where the key sits in a managed vault and no process ever holds it. Whoever can read your KEK material can decrypt every secret this deployment stores, and losing it makes them unreadable with no recovery. If your threat model needs vault custody, the same cipher port takes createKeyVaultCipher from @widgentic/mcp/secrets/keyvault.

Configuration

Sample content at boot

Point WIDGENTIC_SEED_FILE at a JSON file of the shape { schemas?, themes?, actions?, widgets?, settings? } (entries as the app imports them, settings sections as the Settings page saves them) and the single principal gets them at boot, through the same validation as an import. Each boot skips only what the principal holds then: nothing you hold is overwritten, but a seeded entry you delete or a settings section you clear comes back on the next restart, so unset the variable once the content is yours. Refusals are logged and the service starts anyway; keys and secrets are never seeded, and multi-user mode ignores the seed. The image ships seed/demo.json (/srv/docker/seed/demo.json inside it): two shared schemas, two themes set as the light and dark defaults, three widgets (an appointment agenda, an email inbox and a project status widget), and two avatar-service resource domains.

A key that survives an empty store

Keys minted in the app live in the store, so a store that starts empty (an ephemeral volume, a fresh staging deploy) loses them. A deployment key set on the mcp service — WIDGENTIC_DEFAULT_KEY_FILE (a mounted secret, preferred) or WIDGENTIC_DEFAULT_KEY, in the shape the app mints (wgk_ and 64 hex characters) — resolves to the single principal on every boot, so hosts keep working across restarts. It is read-only unless WIDGENTIC_DEFAULT_KEY_SCOPES names execute, and it is never stored, listed in the app or logged. Treat it like any key: whoever holds it reads this deployment’s catalog.

Building from source

The image installs the published @widgentic/* packages. To run unreleased changes in a container (a staging deployment of a branch), build examples/docker/Dockerfile.source from the repository root: it compiles the packages from the checkout, packs them with npm pack and installs those tarballs in place of the registry versions.

What this deployment is not

A single node. The SQLite adapter is deliberately a one-machine design — two local processes share the file through WAL, but there is no multi-replica story, no migration path to one, and no high availability. When you outgrow it, the store port is the seam: the same deployment shape runs against the Cosmos adapter, and the hosted product is the same assembly operated for you.