Skip to content

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/v1alpha1
kind: Performance
metadata:
name: orders
namespace: apps
spec:
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 permanent

A 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.

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.

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.

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.

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.versions gets an entry with outcome RolledOut and status.runningVersion is the new version.
  • When it is not ready within rolloutTimeoutSeconds (300), status.versions gets an entry for it with outcome Failed and a reason, another for the previous release with outcome RolledBack, and the previous image and package are deployed again. The condition RolloutSucceeded is False, runningVersion is the old version and the phase can be Ready.
  • 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 Failed and 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 is either permanent: true or until, and applies to any Performance. When until passes:

  1. The Ingress is deleted, so the public route is gone.
  2. Every Deployment is scaled to zero.
  3. The phase is Expired, the condition Expired is True, publicUrl is empty and status.dataRetainedUntil is until plus dataRetentionDays (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, with cpuCoreSeconds, peakMemoryBytes, averageMemoryBytes, windowStart, windowEnd, sampleCount and measured.
  • currentPeriodUsage: the same for the UTC day under way.
  • lastPeriodUsage: the last completed UTC day, with day (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.

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.