Deployment

Deployment Architecture

The cross-cutting deployment reference that ties the single-host, hosted Coolify, and multi-node models together.

DeploymentReference

Crown ships as a Docker Compose-based system with three repo-defined runtime shapes, plus several curated production reference patterns for Ubuntu / Coolify deployments.

The preferred small-production API shape is now docker-compose.coolify-core.yml: one crown-core-api container hosts Auth, Forms, Person, User Activity, CMS, Audit, Support, and Analytics while keeping their existing hostnames. The legacy split-service Compose shape is retained for rollback and for modules that later need independent scaling. Frontend apps, NELA, risk scoring, AI, and ML training are not folded into the core runtime.

Those curated references now explicitly cover a more realistic delivery plane too: self-hosted Forgejo as the Git provider and self-hosted OCI registry flows where source, build, and deploy are kept inside your own infrastructure.

  • development — full local stack with Traefik and exposed ports
  • production — internal container networking with domain routing
  • E2E testing — isolated test stack with in-memory database behaviour
  • single-server Coolify — one Ubuntu host running Crown, Coolify, and a single-node SurrealDB runtime
  • split build/runtime on Ubuntu + Coolify — one runtime host managed by Coolify, with images built on a dedicated Coolify build server and pushed through a registry
  • multi-node Coolify + SurrealDB cluster — multiple workload servers using per-server Coolify proxies and domain splits, plus a TiKV-backed SurrealDB data tier

Deployment topology

Deployment topology
Deployment topology

Source: service-topology.mmd

Build and deploy order

Build and deploy order
Build and deploy order

Source: deployment-order.mmd

Single-server Coolify reference

This is the smallest production-shaped deployment that still keeps Crown coherent: one Ubuntu server, one Coolify installation, one proxy layer, self-hosted Forgejo and registry services, and one single-node SurrealDB runtime.

Single-server Coolify deployment
Single-server Coolify deployment

Source: single-server-coolify-deployment.mmd

See Single-server Coolify deployment for the operating model, when to use it, and when to move beyond it.

Multi-node Coolify + SurrealDB reference

This is the scale-out reference for Crown when one machine is no longer enough: a self-hosted delivery plane feeds Coolify-managed workload hosts, each with its own proxy and domain set, while SurrealDB moves to its documented TiKV-backed cluster mode.

Multi-node Coolify + SurrealDB deployment
Multi-node Coolify + SurrealDB deployment

Source: multi-node-coolify-surrealdb-deployment.mmd

See Multi-node Coolify + SurrealDB deployment for the per-server domain-routing model, control-plane split, data-tier notes, and scaling guidance.

Ubuntu VPS / dedicated server with Coolify

When Crown is hosted on a single Ubuntu VPS or a larger dedicated Ubuntu server, a practical production pattern is:

  • Coolify runs on the target host and manages the deployed services
  • Forgejo acts as the self-hosted Git provider
  • a self-hosted registry stores private images
  • a dedicated Coolify build server performs the remote image build and pushes the result to the registry
  • the Ubuntu host pulls prebuilt images rather than compiling on the production box
Ubuntu / Coolify hosted deployment
Ubuntu / Coolify hosted deployment

Source: ubuntu-coolify-deployment.mmd

See Ubuntu / Coolify hosted deployment for the full reference flow, host responsibilities, and rollout sequence.

Public docs site container

The docs themselves can now be published independently as a small Bun-served container.

Use Publishing the docs site together with docker/Dockerfile.docs when you want a public architecture portal separate from the main Crown runtime.

Main runtime containers

The table below lists logical runtime responsibilities. In the combined Coolify shape, the eight ordinary Bun APIs share the crown-core-api container and port 8080; in the legacy split shape they retain the individual containers and ports shown below.

RuntimeRoleDefault port / mappingNotes
traefikReverse proxy80, 8080Optional in dev profile, primary routed entrypoint
surrealdbDatabase8010:8000Main operational store
database-migrationsOne-shot jobn/aApplies SurrealQL migrations before dependent services start
auth-serviceAuth / session / JWKS3000Central auth provider
records-serviceForms API3095CRUD, stats, locks, exports
entity-servicePerson API3003Person records, live stats, matching
audit-serviceAudit / FGAC5510Access logging and audit APIs
analytics-serviceAnalytics API3006Reports, snapshots, live dashboards
user-activity-serviceActivity API3097Activity feeds and user-facing stats
cms-serviceContent API3098Announcements, nav links, pages
support-serviceSupport / notifications3099Tickets, messages, notifications
nela-serviceLookup service3018NELA clinician lookup
risk-score-serviceRisk scoring8000R/plumber-based API
auth-ui-appFrontend5175:80Authentication UI
audits-appFrontend5173:80Main audit UI
application-list-appFrontend5174:80App selection and navigation
schema-designer-appFrontend5176:80Schema design tooling
admin-ui-appFrontend5177:80Admin and operational tooling
nela-risk-calculator-appFrontend5178:80Risk calculator UI
analytics-ui-appFrontend5180:80Analytics dashboards

Environment shapes

Development

Development uses the main docker-compose.yml file:

  • ports are exposed directly on localhost
  • Traefik can route .localhost domains to services and frontends
  • frontend apps are built with host-accessible URLs
  • backend services still use container-internal DNS names for service-to-service calls

Production

Production uses docker-compose.prod.yml as the runtime-oriented override:

  • SurrealDB stays internal to the network
  • services use container DNS names such as http://auth-service:3000
  • frontends are built with public external URLs
  • Traefik handles routed HTTPS access
  • persistent volumes remain attached for SurrealDB and support attachments

E2E

E2E uses docker-compose.e2e.yml as an override:

  • SurrealDB switches to in-memory storage for clean test runs
  • frontend ports are pinned to expected Playwright values
  • build args are overridden so in-stack URLs work correctly during tests
  • an optional e2e-tests container can execute Playwright inside Docker

Routing model

docker/nginx.conf.template shows the frontend deployment pattern:

  • SPAs are served from nginx
  • all unknown routes fall back to index.html
  • assets are aggressively cached

This means the frontend containers are static web deployments, while the API logic remains in the Hono services.

Volumes and state

Persistent state is currently concentrated in two places:

  • surrealdb-data — primary application data
  • support-attachments — uploaded support attachments

That makes operational backup planning fairly clean: protect the database volume first, then the support attachment volume.

Deployment maintenance checklist

When changing the deployment architecture, update at least these areas:

  • compose files (docker-compose*.yml)
  • Dockerfiles for affected apps/services
  • docker/Dockerfile.docs if the public docs container behaviour changes
  • rsbuild env mappings for affected frontends
  • .env.example / environment references where relevant
  • regenerate docs from apps/docs-site/ via bun run generate