Run a published Stage application
A published Stage application is the generic Stage runtime image (cratis/stage:<runtime version>) in published mode, with an application package mounted into it. A Performance runs one when application.published is set. Nothing is built per application: a new version is a new package ConfigMap, and the image changes only when the runtime does.
apiVersion: showrunner.cratis.io/v1alpha1kind: Performancemetadata: name: orders namespace: appsspec: environment: name: production configMapRef: name: orders-environment # the sign-in providers' values application: image: cratis/stage:5.3.0 # the Stage runtime, not an image per application published: version: 1.4.0 packageConfigMapRef: name: orders-package-1-4-0 # immutable; binaryData key package.tar.gz storageConnectionSecretRef: name: orders-storage # key connectionString trustedProxies: ["10.244.0.0/16"] notes: collectorUrl: https://collector.example.invalid:8443 collectorTokenSecretRef: name: orders-notes-token # key token publicationId: pub-7c1d collectorTenantId: acme-corp applicationName: Orders authProxy: enabled: true providers: - kind: GitHub identity: GitHub ingress: enabled: true hosts: [orders.cratis.app] tlsIssuer: name: letsencrypt lifetime: until: 2026-12-31T00:00:00Z # or permanent: true; omit for permanentA published application needs authProxy.enabled and ingress.hosts (Stage answers only the hosts it is published under) and runs one replica. It does not take a chronicle or productionRef: Stage hosts its own Chronicle kernel in the container. onboarding is not supported for it. Like every Performance it is never half applied: when the package, the storage Secret, the Notes token Secret or the environment is not there, the phase is Blocked and the conditions PackageAvailable, StorageConnection, NotesCollectorToken and EnvironmentComplete say which.
The package
Section titled “The package”packageConfigMapRef names a ConfigMap in the namespace of the Performance whose binaryData key package.tar.gz (or key) holds the archive that Stage exports. It must be immutable: true; a version never changes in place, which is what makes a rollback to it safe. The ConfigMap is mounted read-only at /stage/package-archive/ and Stage__Published__PackagePath points at the file. Name one ConfigMap per version, for example orders-package-1-4-0. A ConfigMap holds up to 1 MiB.
Storage
Section titled “Storage”The kernel inside Stage needs MongoDB. storageConnectionSecretRef names a Secret with the connection string (key connectionString unless set). It is projected into the container as a file and Stage reads it through Stage__Published__Storage__ConnectionStringFile; it is never copied, never an environment variable and never in the status. Give each application a database of its own in the connection string.
The operator does not run MongoDB for a published application. Stage Manager takes a connection from a Secret for the Chronicle of an ordinary Performance in the same way, and managed storage is the job of a Production, which is not reconciled yet. The publisher provisions the database and owns it, including when to remove it after the application expired.
Sign-in and trust
Section titled “Sign-in and trust”AuthProxy is the only public entry point, as for every Performance. It signs users in and forwards each request to the application with the x-ms-client-principal* headers. Stage believes those headers only from the addresses in trustedProxies and strips them from any other caller.
AuthProxy runs as its own Deployment, so Stage sees the address of an AuthProxy pod: set trustedProxies to the pod network of the cluster (for example 10.244.0.0/16 on kind). That is safe because the operator also creates the NetworkPolicy <name>-app, which lets only the AuthProxy pods of the same Performance connect to Stage’s port 9090. Nothing else in the cluster can reach Stage, and Chronicle’s port 35000 is never exposed by a Service or the Ingress. The cluster needs a network plugin that enforces NetworkPolicy.
The Ingress routes every host to AuthProxy and nothing else, so /scalar, /openapi, /workbench, /read-models, /stage/load and /stage/semantic are not reachable from outside, and published mode does not map them in the first place.
The application’s Service is <name>-app on port 9090. Stage Manager points AuthProxy at it with Cratis__AuthProxy__Services__application__Backend__BaseUrl and …Frontend__BaseUrl, both http://<name>-app:9090/.
With notes set, the application hosts the Notes backend and sends notes over HTTP to the collecting host: Notes__Transport=Http, Notes__CollectorUrl, Notes__CollectorTokenFile (the Secret mounted at /run/secrets/notes/token), Notes__PublicationId, Notes__CollectorTenantId and Notes__ApplicationName. See Transports. Leave notes out, or set enabled: false, for an application without Notes.
Rollouts
Section titled “Rollouts”A release is the application version, the runtime image and the package ConfigMap. Changing any of them rolls out. The application Deployment stops the old pod before it starts the new one (strategy: Recreate), and readiness is checked on /stage/status.
- When the new release is ready,
status.versionsgets an entry with outcomeRolledOutandstatus.runningVersionis the new version. - When it is not ready within
rolloutTimeoutSeconds(300),status.versionsgets an entry for it with outcomeFailedand areason, another for the previous release with outcomeRolledBack, and the previous image and package are deployed again. The conditionRolloutSucceededisFalse,runningVersionis the old version and the phase can beReady. - A release that failed is not tried again while the spec asks for it. Ask for something else, such as a new package ConfigMap, to try again.
- A first release that never becomes ready is recorded as
Failedand stays deployed, because there is nothing to roll back to.
status.versions keeps the last twenty entries, the newest last. Stage’s own data stays in the storage the Secret names.
Stage runs its Chronicle kernel in-process against the application’s database, so two pods must never run at once: a rollout stops the application briefly, until the new pod is ready. A rollback is a rollout too.
Lifetime
Section titled “Lifetime”lifetime is either permanent: true or until, and applies to any Performance. When until passes:
- The Ingress is deleted, so the public route is gone.
- Every Deployment is scaled to zero.
- The phase is
Expired, the conditionExpiredisTrue,publicUrlis empty andstatus.dataRetainedUntilisuntilplusdataRetentionDays(7).
The operator owns no storage of a published application, so it removes no data. dataRetainedUntil tells the publisher when it may drop the database it provided. Moving until into the future runs the Performance again. Deleting the Performance removes everything it created through owner references.
On every reconciliation (every 30 seconds for a published application) the operator reads PodMetrics for all pods of the Performance from metrics.k8s.io and adds them to the status, summed over all containers:
resourceUsage: the total since measuring began, withcpuCoreSeconds,peakMemoryBytes,averageMemoryBytes,windowStart,windowEnd,sampleCountandmeasured.currentPeriodUsage: the same for the UTC day under way.lastPeriodUsage: the last completed UTC day, withday(yyyy-MM-dd). It is replaced when the next day completes, so a publisher that reports daily reads it once a day.
CPU is the rate each sample reports multiplied by the time since the previous sample, at most two minutes, so a gap while the operator was down is not billed. measured is false when the cluster has no metrics API; install metrics-server.
Resources
Section titled “Resources”The Stage container requests 250m CPU and 512Mi memory and is limited to 1 CPU and 1Gi memory. Set application.published.resources.requests and limits to change them.