Deployment
Callora ships as a self-contained app: one application container (the composed host built from these framework packages) plus a PostgreSQL database. It runs on docker-compose or a single VPS. This page covers the operational shape; the composition itself (image build, package assembly) lives in the separate callora-production repository.
Status: This repository provides the framework libraries and the dev compose stack below. The production composition (
callora-production), the production container image, and the production NuGet feed live outside this repo. Nothing here is a shipped production artifact.
Topology
A single origin per workspace domain routes by path (the docs/CALLORA_PLATFORM_BETRIEBSMODELL.md model), fronted by a reverse proxy (Caddy in the local stacks):
| Path | Target |
|---|---|
/admin/* | Admin UI (the colocated Vue shell, static web asset from Callora.Administration) |
/api/* | Operator / control-plane API (plugin lifecycle, entitlements, policies, audit) |
/workspace/* | Workspace API |
/surface-app/* | Surface runtime (static web asset from Callora.Surface.Rendering) |
/* | Public workspace surfaces (SSR) |
DNS for all workspace domains points at the same frontdoor IP; the workspace is resolved from Host + Path.
Compose stacks in this repo
| File | Brings up | Use |
|---|---|---|
docker-compose.yml | callora-backend (built from the dev stage, dotnet watch on src/Host/Dev, port 5000), postgres:16, and coturn for a real ICE relay. Repo mounted. | Working on it. |
docker-compose.standalone.yml | The same host, but built into the image — host, both frontends and every cloned plugin — plus postgres:16. No mount, no dotnet watch. | Looking at it: clone and run, nothing installed but Docker. |
docker-compose.frontdoor.yml | Overlay adding a caddy:2.8 frontdoor on port 8080 over the dev stack (single-origin path routing). | Layer with -f docker-compose.yml -f docker-compose.frontdoor.yml. |
Both backends build from the repository Dockerfile, which carries .NET and Node — the two Vue suites hang off their project's MSBuild target and call npm, so a full build in a container without Node ends in npm: not found.
The standalone stack is the shortest path from nothing to a running system:
git clone https://github.com/BechsteinDigital/callora.git
cd callora
docker compose -f docker-compose.standalone.yml up --buildPlugins cloned into custom/plugins/ or custom/static-plugins/ are built and loaded without being listed anywhere — the build finds every registry.json, the same search the host performs at startup.
MinVer must be set at build time, not at start
Without a Git tag MinVer falls back to 0.1.0-preview.0.<height>, and the host then rejects every plugin it finds: "Plugin dependency 'Callora.Core' requires '>=0.9.0-0', but the host provides 0.1.0-preview.0". The standalone stack passes CALLORA_VERSION as a build argument for that reason. Supplied as a runtime environment variable it arrives too late — the build stays green and the only evidence is a handful of rejections in the startup log.
There was a second stack here, docker-compose.prod-like.yml, built from src/Core/Dockerfile. Both are gone. The Dockerfile set ENTRYPOINT ["dotnet", "Callora.Core.dll"], and Core has been OutputType=Library since the module split — the image it produced had no entry point and could not start. Rehearsing the production shape means building the callora-production distribution, which is where the runnable host actually lives.
Start the dev stack:
cp .env.example .env # edit at least the DB connection + API key
set -a; source .env; set +a
docker compose up -d
curl http://localhost:5000/healthWith the frontdoor, the app is reachable at http://localhost:8080.
PostgreSQL
- One PostgreSQL database backs the whole app. The host schema and every plugin's
plugin_<id>schema live on the same database. - Migrations are applied automatically at startup under a Postgres advisory lock, so multiple app instances can start safely against one database. See Migration & Rollback.
- Default local credentials (from
.env.example): databasecallora_host, usercallora. Change these for anything beyond local dev.
Connection string (config key BackendHost:DatabaseConnectionString, env BackendHost__DatabaseConnectionString):
Host=postgres;Port=5432;Database=callora_host;Username=callora;Password=<secret>Configuration hygiene
ASP.NET configuration keys map from environment variables with double-underscore (BackendHost__DatabaseConnectionString → BackendHost:DatabaseConnectionString); arrays use __0, __1, object arrays __0__Field. Template: .env.example; local .env is gitignored.
Keys a deployment operator must review:
| Key | Meaning |
|---|---|
BackendHost__DatabaseConnectionString | PostgreSQL connection. |
BackendHost__EnableBootstrapApiKeys / BackendHost__ApiKeys__0 / BackendHost__BootstrapApiKeysExpireAtUtc | Break-glass control-plane key (header X-Callora-Api-Key). Rotate the default key callora-local-dev-key-change-me and retire it after onboarding — see the bootstrap credential lifecycle. |
BackendHost__AllowUnsignedPlugins | Dev sets true; production must be false so every plugin (including bundled system plugins) must be signed and trusted. |
BackendHost__TrustedSigners__* | Trusted signer public keys (see Security). |
BackendHost__RevokedSignerFingerprints__* / BackendHost__RevokedContentHashes__* | Revocation lists, enforced at install and at runtime rehydration. |
BackendHost__AdminShellBaseUrl (/admin/), WorkspaceShellBaseUrl (/), PluginAssetBaseUrl (/plugin-assets) | Routing base URLs. |
CalloraHosting__AutoLoadPlugins, AutoActivateInstalledPlugins, PluginDirectory | Plugin auto-load behaviour on startup. |
Observability__OtlpEndpoint (a.k.a. Observability:OtlpEndpoint) | OTLP exporter target; empty = telemetry off. |
Retention__* | Retention sweep configuration (see below). |
Hardening notes from docs/QUALITY_STANDARDS.md:
- The default
JwtSigningKeythrows outside Development — you must supply a real signing key in production. - Secret config and webhook secrets are encrypted in the database (the data-protection keyring lives in the DB) and redacted in API responses; PII is masked in logs.
- Configure
AllowedHostsand any CORS/forwarded-headers settings for the real deployment origin.
TLS
Terminate TLS at the reverse proxy / frontdoor. The local stacks use Caddy on port 8080 without TLS; in production, put the app behind a TLS-terminating proxy (Caddy with automatic HTTPS, or your existing ingress).
Status: The local frontdoor config (
ops/local-frontdoor/Caddyfile*) is dev-only and does not terminate TLS. Production TLS and the hardened frontdoor config are an operations task in thecallora-productioncomposition, not shipped here.
Health and readiness
GET /health— liveness, no dependency check.GET /ready— readiness, checks the database.
Wire /ready into your orchestrator's readiness probe and /health into the liveness probe.
Admin and public surfaces
- The admin shell is served at
/adminfrom the static web assets bundled inCallora.Administration. Plugin admin UIs are loaded at runtime by the micro-frontend loader (backend manifest →/plugin-assets), so installing and activating a plugin surfaces its UI on a browser refresh without a restart. - Public surfaces are server-side rendered by
Callora.Surface.Rendering(Nunjucks on a hardened Jint sandbox), with the surface runtime served at/surface-app. Untrusted surface templates run inside the sandbox — see Security.