MSO Cloud · Documentation

Cloud Architecture

Source: docs/architecture/cloud-architecture.md Updated 2026-09-21
On this page

MSO Cloud runs as a containerized, self-hosted stack: stateless compute in front, stateful data services behind it, and tenant isolation enforced in the database rather than the network.

Deployment model today#

The stack is a set of containers behind a reverse-proxy ingress that terminates TLS. The ingress routes incoming traffic to the application layer and does not route to the data layer at all: the database, cache, and object storage are unreachable from outside the deployment.

The application layer is split into stateless web replicas and background workers:

  • Web serves the dashboard and API traffic. It holds no local state, so replicas can be added or replaced without coordination between them. A deploy performs a rolling replace: a new replica starts alongside the running one(s), is added to rotation only once its own health check passes, and the previous replica is then drained of in-flight requests and removed. Traffic sees no gap in service during a normal deploy.
  • Worker processes background jobs: connector sync, AI-assisted tasks, alerting, scheduled reports, and metric materialization.
  • Archive-worker is a separate worker process scoped to backup and restore work only. It runs under its own database credential, distinct from the general worker's, so the capability to read and stage a full data export is isolated to one narrow process rather than shared across all background jobs.

The data layer is Postgres 16, Redis 7 configured with append-only-file persistence (so queued jobs and cached state survive a restart), and S3-compatible object storage for uploads and generated exports.

Deploys are driven from a versioned compose template plus an environment file that is the single source of truth for a given deployment; nothing in the running configuration is left to ambient shell state or undeclared defaults. Deploying a schema change runs a one-shot migration step before any application container starts or is replaced. That step also applies the platform's database access-control manifest and checks the live schema against the inventory the application code expects; if the two have drifted apart, the step fails and the deploy stops before touching the running application, rather than starting containers against a database they disagree with.

Startup is health-gated end to end: application containers wait for the database, cache, and object storage to report healthy, and for the migration step to complete successfully, before they are allowed to start or receive traffic.

flowchart LR
    Ingress -->|443| Web["Web (N replicas)"]
    Web --> Postgres[(Postgres)]
    Web --> Redis[(Redis)]
    Web --> Storage[(Object storage)]
    Worker --> Postgres
    Worker --> Redis
    Worker --> Storage

Isolation and state#

Compute and data are already separated in principle: web and worker hold no durable state of their own and can be stopped, replaced, or restarted without data loss, while Postgres, Redis, and object storage are the only places state persists.

Tenant isolation is enforced at the database, not at the network. Every tenant's data lives in the same database and the same tables; a row is visible only when it matches the request's tenant context, checked by the database itself on every query, not by giving each tenant a separate network segment or a separate database instance. This means isolation does not depend on infrastructure topology and holds the same way whether the stack is one node or many.

Backups and data exports follow a "secrets never leave" rule: a portable archive of a tenant's data excludes envelope-encrypted secrets (API credentials, connector tokens) by construction, so an archive handed to or restored for a tenant cannot also leak platform-level key material. Staging for a large export or restore writes to real disk rather than memory-backed storage, because a single tenant's data volume has no fixed upper bound and an in-memory staging area would fail unpredictably as that volume grows.

Scaling roadmap#

The items below are a considered direction, not the current deployment. None of them are live; each is scoped to be deployed and, if needed, rolled back on its own, without requiring the others.

  1. Automated backups and verified restore. Scheduled backups plus periodic restore drills, ahead of every other step, because it is the single largest risk reduction and does not depend on anything else changing first.
  2. Read replica and connection pooling. A read replica for reporting-style queries, so heavy analytical reads do not compete with the write path, paired with connection pooling in front of the database so that adding compute nodes later does not exhaust the database's connection budget.
  3. Separate compute and data onto independent nodes. Today's stack co-locates application and data containers; this step moves the data layer onto infrastructure of its own, with the application layer reduced to pure compute configured against a database endpoint rather than a co-located instance.
  4. Multi-node compute behind shared object storage. Multiple web and worker nodes running concurrently against the same shared object storage and data layer, so compute capacity can scale independently of any single node's limits.

Self-hosted posture#

MSO Cloud is deployed as a single self-hosted stack: the application, its database, cache, and object storage all run inside infrastructure the operator controls. There is no dependency on a third-party application platform to serve the product.

The stack makes a small, declared set of outbound calls:

  • Transactional email, for sign-in links and notifications.
  • Each org's chosen LLM provider. The AI provider is a per-org setting, not a single fixed platform-wide dependency; an org can point its own workloads at the provider it has chosen.
  • Connected data-source APIs, for the marketplace, creator, and inventory platforms an org has connected.

No third-party analytics or telemetry is embedded in the product itself.