Skip to Content
InfrastructureOverview

Infrastructure

The Orbit platform runs in exactly two environments, both defined in the infrastructure/ directory of the platform repo:

  • Local development (infrastructure/local/) — a single docker-compose stack that runs every application on one machine: the three per-solution Orbit apps against a bind-mounted reference solution, the Aurora portfolio webapp, and the portfolio registry (Postgres plus the three Hono services).
  • Azure Container Apps (infrastructure/azure/) — the sole production target (the Vercel path was retired on 2026-07-02). A set of idempotent, reconciling bash scripts provisions the singleton aurora control-plane app, the registry database and services, and — on demand or automatically at solution creation — a live per-solution Orbit stack of three apps.

Between the two sits the release registry (releases/ at the repo root): the per-component record of what was built, from which git tag, into which container image. Local development builds dev images ad hoc from the working tree; Azure only ever runs images that were built from an immutable git tag and recorded in the registry.

The two environments

Local: one compose stack for everything

infrastructure/local/docker-compose.yml (project name orbit-local) defines nine services on a private orbit-net bridge network:

ServiceHost port (default)Role
orbit-webapp3000Read-only solution viewer; fetches solution data from the agent over HTTP
orbit-agent3010The authoring agent (sole writer); mounts the solution repos
orbit-agent-webapp3001Mission Control UI for the agent
aurora-webapp3040Portfolio control plane; reads the fleet live from GitHub
postgres5432Postgres 17 (portfolio database) for the registry
customer-service3021Portfolio registry — customers
tenant-service3022Portfolio registry — tenants
solution-service3023Portfolio registry — solutions

(The ninth “service” is the network itself plus two named volumes: orbit-claude-sessions for the agent’s session transcripts and orbit-pg-data for Postgres.)

Each codebase is bind-mounted into its container for hot reload (next dev / tsx watch), with anonymous volumes protecting node_modules and .next. Configuration comes from infrastructure/local/.env (copied from .env.example): host ports, the SOLUTION_REPOSITORY_PATH / SOLUTION_INTERNAL_REPOSITORY_PATH / TRAJECTORY_METHODOLOGY_PATH host paths the agent mounts at /repo, /repo-internal, and /methodology, GitHub App credentials for Aurora, and the Anthropic credential for the agent.

Azure: singleton control plane + per-solution stacks

The Azure scenario deploys the same applications as Azure Container Apps (ACA) in one environment, pulling images from the acrorbit container registry via a user-assigned managed identity (id-aurora) — no service-principal secret is stored anywhere. It consists of:

  • The singleton aurora app behind a Cloudflare front door, plus the portfolio registry: a Postgres Flexible Server (Entra-only auth) and the three registry services (svc-customer / svc-tenant / svc-solution, internal ingress only).
  • One Orbit stack per solution, provisioned by provision-solution.sh (or by Aurora itself in-product via deploy-azure.ts): reader orbit-<h>, writer agent-<h>, and Mission Control mc-<h> (where h is a short hash of the solution id), backed by two Azure Files shares holding the cloned customer and internal repos. The reader and Mission Control are external, each with its own Cloudflare subdomain; the agent has internal ingress only (since 2026-07-03) and is reached in-env as http://agent-<h>.

Everything is driven by a single git-ignored desired-state file, infrastructure/azure/config.local.yaml (template: config.local.example.yaml), and every script is idempotent and reconciling — GET first, create if absent, update on drift, never error on “already exists”. Re-running a provisioner converges the environment on the config.

What lives where

infrastructure/local/

FilePurpose
docker-compose.ymlThe one dev stack: all apps + registry services + Postgres on orbit-net
.env.exampleDocumented template for the stack’s configuration
.envYour local values (ports, solution repo paths, credentials) — git-ignored

infrastructure/azure/

FilePurpose
lib.shSourced foundation: config readers over config.local.yaml, Azure coordinate accessors, the deterministic azure_names helper, Cloudflare/ACA custom-domain helpers
config.local.example.yamlTemplate for the desired-state config (azure / images / aurora / cloudflare / edge / github / agent blocks)
config.local.yamlThe live desired state — git-ignored, no secrets beyond what config requires
deploy.shOps entrypoint / dispatcher: bootstrap, aurora, postgres, services, provision <id>, verify [<id>], delete <id> [--yes]
bootstrap.shOne-time identity bootstrap: id-aurora UAMI + role assignments (AcrPull, Contributor)
build-images.shThe release step: builds a component image from its git tag via az acr build and records it in releases/
provision-aurora.shProvisions the singleton aurora ACA app + secrets + Cloudflare front door
provision-postgres.shProvisions the portfolio-registry Postgres Flexible Server (Entra-only auth, portfolio db)
provision-services.shProvisions the three registry services from one template, internal ingress, scale-to-zero
provision-solution.shPer-solution worker: 2 Azure Files shares → env-storage links → seed job → the three apps (agent internal-only) + the two public Cloudflare subdomains
app-aurora.yaml.tmplACA app template for the singleton Aurora app
app-orbit.yaml.tmplACA app template for the per-solution reader (orbit-<h>)
app-agent.yaml.tmplACA app template for the per-solution agent (agent-<h>)
app-agent-webapp.yaml.tmplACA app template for Mission Control (mc-<h>)
app-service.yaml.tmplShared ACA template rendered three times for the registry services
seed-job.yaml.tmplACA job that clones the two solution repos into the Azure Files shares
README.mdThe operational guide for the whole scenario

The Azure scenario also has a TypeScript twin inside the Aurora webapp (src/lib/solutions/deploy-azure.ts, src/lib/solutions/azure-names.ts, src/lib/portfolio/azure.ts): the in-product deploy path that Aurora runs at solution creation, using the same naming scheme and the container’s managed identity. Bash and TS implement one contract — the naming helpers are kept byte-identical.

The release registry: connecting builds to deployments

releases/ (repo root) is the per-component registry of release records. Each record is a small release.yaml under releases/<component>/v<semver>/, validated by releases/release.schema.json, that pins the immutable source (git tag <component>/v<semver> + commit) and a reference to the built artifact — a container entry with registry / repository / tag (and optionally digest) in ACR. Records reference builds; they never contain build bytes.

Components version independently — there is deliberately no lockstep all release. The registry currently holds records for aurora-webapp, orbit-webapp, maxq-orbit-agent, maxq-orbit-agent-webapp, and the three registry services (customer-service, tenant-service, solution-service). Historical aurora-webapp records with target: vercel remain valid as history.

Image flow: from git tag to running app

commit → git tag <component>/v<version> → build-images.sh <target>[@<version>] → az acr build (server-side, from a throwaway worktree of the tag) → acrorbit.azurecr.io/<component>:<version> (+ :latest) → releases/<component>/v<version>/release.yaml written/reconciled → bump images.*_tag in config.local.yaml → deploy.sh aurora / provision <id>

Key properties, in order:

  1. Builds come from the immutable tag, not the working tree — the image, the recorded commit, and the git_tag can never drift.
  2. Builds run server-side via az acr build (ACR Tasks); no local Docker is needed.
  3. Images are shared per release, not per solution. Every per-solution stack pins a tag of the same shared reader / agent / Mission Control images; per-solution state lives in ACA app config, env vars, and the Azure Files shares — never baked into an image.
  4. Deploy picks up the tag from config: bumping images.*_tag in config.local.yaml and re-running the relevant provisioner rolls the apps forward; new solution stacks are provisioned at the configured tags.

Environment topology

The same application code runs in both environments; what differs is how it is built (working-tree dev images vs. tagged release images), how solution repos reach the agent (host bind mounts vs. Azure Files shares seeded from GitHub), and how requests arrive (published host ports vs. Cloudflare-fronted ACA ingress).

In this section

  • Local Development — the docker-compose stack in detail: services, ports, mounts, .env configuration, and day-to-day workflows.
  • Azure Deployment — the ACA scenario: bootstrap, the singleton Aurora app, the registry database and services, and the per-solution provisioning lifecycle.
  • Release Registry — the record format, the schema, and the tag-driven release workflow.