Skip to content
rise.Operator docs
GitHub

Architecture

Metacontroller is a Kubernetes operator that implements the composite-controller pattern on top of a simple webhook protocol — so Rise does not need to run a watch loop or write reconciliation logic from scratch.

Sync webhook

Metacontroller calls POST /api/v1/metacontroller/sync whenever a RiseProject resource changes or the configured resync interval elapses. The request body contains:

  • parent: the RiseProject object (name equals the project slug; spec is intentionally empty — the database is the source of truth)
  • children: a snapshot of every child resource Metacontroller currently owns, grouped by kind

Rise reads the project state from the database, inspects the observed children to update deployment health/status, then returns the fully-specified set of child resources that should exist. Metacontroller creates, updates, or deletes child resources to match — including garbage-collecting anything no longer returned.

Finalize webhook

When a RiseProject is deleted, Metacontroller calls POST /api/v1/metacontroller/finalize before removing child resources. Rise marks all deployments for the project as Stopped, then returns finalized: true, at which point Metacontroller deletes the owned children.

Why Metacontroller

Using Metacontroller lets Rise express desired cluster state as a stateless function (database state → JSON list of resources) without owning the watch loop, retry logic, or garbage collection. Metacontroller handles watch/cache/retry; Rise handles business logic.

For webhook authentication details, see Webhook Security.

Resources follow consistent naming patterns:

ResourcePatternExample
Namespacerise-{project}rise-my-app
Deployment{project}-{deployment_id}my-app-20251207-143022
Service{escaped_group}default, mr--26
Ingress{escaped_group}default, mr--26
ServiceAccountenv-{environment}env-production, env-staging
Secretrise-registry-credsrise-registry-creds

Character escaping: Sequences of characters not in [A-Za-z0-9-_.] are replaced with --. For example, mr/26 becomes mr--26. Group names are lowercase alphanumeric segments joined by single - or / separators — adjacent separators (--, //, -/, /-) are rejected so no two group names normalize to the same string, and the normalized result must be at most 63 characters (Kubernetes label value limit).

Each deployment group gets its own Service and Ingress with a unique URL:

GroupURL PatternExample
defaultproduction_ingress_url_templatemy-app.apps.rise.local
Custom groupsstaging_ingress_url_templatemy-app--mr--26.preview.rise.local

Environments and URL routing: Environments layer on top of deployment groups to control which URL template applies. The environment marked as production determines which deployments receive the production URL (via production_ingress_url_template). All other deployments — regardless of their group name — use the staging URL template. Custom domains also attach to production environment deployments only. See the environments guide for how to create and configure environments.

Every URL template is a hostname pattern, so each project (and each deployment group and environment) gets a host of its own:

  • Production: {project_name}.apps.rise.local
  • Staging: {project_name}--{deployment_group}.preview.rise.local
  • Ingress paths are the app’s own routes (/ by default, Prefix type); no path rewriting
  • Requires a wildcard TLS certificate (e.g., *.apps.rise.example.com and *.preview.rise.example.com) — see Custom Domains for cert-manager setup

Path-based templates such as rise.local/{project_name} are rejected at startup: apps sharing one host would share its cookies and origin.

The controller implements blue/green deployments using Service selector updates:

  1. Deploy new Deployment: Create new Deployment with deployment-specific labels
  2. Wait for health: Wait until new Deployment pods are ready and pass health checks
  3. Switch traffic: Update Service selector to point to new deployment labels
  4. Previous deployment: Old Deployment remains but receives no traffic

This ensures zero-downtime deployments with instant rollback capability.

All resources are labeled for management and selection:

labels:
app.kubernetes.io/managed-by: "rise"
rise.dev/project: "my-app"
rise.dev/environment: "production" # present when deployment has an environment
rise.dev/deployment-group: "default"
rise.dev/deployment-id: "20251207-143022"
rise.dev/deployment-uuid: "550e8400-e29b-41d4-a716-446655440000"