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 singletonauroracontrol-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:
| Service | Host port (default) | Role |
|---|---|---|
orbit-webapp | 3000 | Read-only solution viewer; fetches solution data from the agent over HTTP |
orbit-agent | 3010 | The authoring agent (sole writer); mounts the solution repos |
orbit-agent-webapp | 3001 | Mission Control UI for the agent |
aurora-webapp | 3040 | Portfolio control plane; reads the fleet live from GitHub |
postgres | 5432 | Postgres 17 (portfolio database) for the registry |
customer-service | 3021 | Portfolio registry — customers |
tenant-service | 3022 | Portfolio registry — tenants |
solution-service | 3023 | Portfolio 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
auroraapp 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 viadeploy-azure.ts): readerorbit-<h>, writeragent-<h>, and Mission Controlmc-<h>(wherehis 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 ashttp://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/
| File | Purpose |
|---|---|
docker-compose.yml | The one dev stack: all apps + registry services + Postgres on orbit-net |
.env.example | Documented template for the stack’s configuration |
.env | Your local values (ports, solution repo paths, credentials) — git-ignored |
infrastructure/azure/
| File | Purpose |
|---|---|
lib.sh | Sourced foundation: config readers over config.local.yaml, Azure coordinate accessors, the deterministic azure_names helper, Cloudflare/ACA custom-domain helpers |
config.local.example.yaml | Template for the desired-state config (azure / images / aurora / cloudflare / edge / github / agent blocks) |
config.local.yaml | The live desired state — git-ignored, no secrets beyond what config requires |
deploy.sh | Ops entrypoint / dispatcher: bootstrap, aurora, postgres, services, provision <id>, verify [<id>], delete <id> [--yes] |
bootstrap.sh | One-time identity bootstrap: id-aurora UAMI + role assignments (AcrPull, Contributor) |
build-images.sh | The release step: builds a component image from its git tag via az acr build and records it in releases/ |
provision-aurora.sh | Provisions the singleton aurora ACA app + secrets + Cloudflare front door |
provision-postgres.sh | Provisions the portfolio-registry Postgres Flexible Server (Entra-only auth, portfolio db) |
provision-services.sh | Provisions the three registry services from one template, internal ingress, scale-to-zero |
provision-solution.sh | Per-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.tmpl | ACA app template for the singleton Aurora app |
app-orbit.yaml.tmpl | ACA app template for the per-solution reader (orbit-<h>) |
app-agent.yaml.tmpl | ACA app template for the per-solution agent (agent-<h>) |
app-agent-webapp.yaml.tmpl | ACA app template for Mission Control (mc-<h>) |
app-service.yaml.tmpl | Shared ACA template rendered three times for the registry services |
seed-job.yaml.tmpl | ACA job that clones the two solution repos into the Azure Files shares |
README.md | The 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:
- Builds come from the immutable tag, not the working tree — the image,
the recorded commit, and the
git_tagcan never drift. - Builds run server-side via
az acr build(ACR Tasks); no local Docker is needed. - 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.
- Deploy picks up the tag from config: bumping
images.*_taginconfig.local.yamland 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,
.envconfiguration, 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.