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
Source: service-topology.mmd
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.
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.
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
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.
| Runtime | Role | Default port / mapping | Notes |
|---|---|---|---|
traefik | Reverse proxy | 80, 8080 | Optional in dev profile, primary routed entrypoint |
surrealdb | Database | 8010:8000 | Main operational store |
database-migrations | One-shot job | n/a | Applies SurrealQL migrations before dependent services start |
auth-service | Auth / session / JWKS | 3000 | Central auth provider |
records-service | Forms API | 3095 | CRUD, stats, locks, exports |
entity-service | Person API | 3003 | Person records, live stats, matching |
audit-service | Audit / FGAC | 5510 | Access logging and audit APIs |
analytics-service | Analytics API | 3006 | Reports, snapshots, live dashboards |
user-activity-service | Activity API | 3097 | Activity feeds and user-facing stats |
cms-service | Content API | 3098 | Announcements, nav links, pages |
support-service | Support / notifications | 3099 | Tickets, messages, notifications |
nela-service | Lookup service | 3018 | NELA clinician lookup |
risk-score-service | Risk scoring | 8000 | R/plumber-based API |
auth-ui-app | Frontend | 5175:80 | Authentication UI |
audits-app | Frontend | 5173:80 | Main audit UI |
application-list-app | Frontend | 5174:80 | App selection and navigation |
schema-designer-app | Frontend | 5176:80 | Schema design tooling |
admin-ui-app | Frontend | 5177:80 | Admin and operational tooling |
nela-risk-calculator-app | Frontend | 5178:80 | Risk calculator UI |
analytics-ui-app | Frontend | 5180:80 | Analytics dashboards |
Environment shapes
Development
Development uses the main docker-compose.yml file:
- ports are exposed directly on localhost
- Traefik can route
.localhostdomains 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-testscontainer 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 datasupport-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.docsif the public docs container behaviour changes- rsbuild env mappings for affected frontends
.env.example/ environment references where relevant- regenerate docs from
apps/docs-site/viabun run generate