Skip to content
Config Schema

Deployment Backends

Rise deploys container apps through a pluggable deployment backend (the deployment controller). Two backends ship today:

  • Kubernetes — the original, most widely used backend. Deploys apps as Deployments/Services/Ingresses on a cluster.
  • Docker — the single-host counterpart. Runs apps as plain Docker containers routed by Traefik. No cluster, no Helm.

Both are first-class. Pick Kubernetes for multi-node, autoscaling, production clusters; pick Docker for a single host, edge boxes, demos, or local development.

Rise aims for semantic feature parity and correctness across all deployment backends. Any capability that is configurable through a public Rise API surface — rise.toml, project/deployment settings, environment variables, the HTTP API — should behave the same way on every backend, and should be supported by every backend where it is technically possible.

The qualifier matters: some features depend on capabilities a single-host Docker daemon simply does not have (horizontal scale-out across nodes, per-workload network policies). Those gaps are legitimate and are recorded in the matrix below with a note. A gap that is merely unimplemented — not a fundamental limitation — is a parity bug to be tracked and closed, not an accepted difference.

When a feature lands on one backend, the other backend’s support is never assumed to follow implicitly. The parity question must be raised explicitly during planning and review (see the contributor guideline in CLAUDE.md), and this matrix kept up to date as the source of truth for what each backend supports.

Legend: ✅ supported · ⚠️ partial / with caveats · ❌ not supported (see note).

FeaturePublic API surfaceKubernetesDockerNotes
HTTP ingress routing ({project}.<domain>)implicitIngress vs. Traefik router.
Path-based routes[routes]Longest-prefix match on both.
Custom domainsproject custom domainRegistration emits the route + auth wiring.
TLS terminationcustom domain / ingresscert-manager (K8s) vs. Traefik ACME / Let’s Encrypt HTTP-01 (Docker).
Access classes (None / Authenticated / Member)access_requirementnginx auth-url (K8s) vs. Traefik forwardAuth (Docker).
Per-route access requirement[routes].accessA route can loosen (public) or tighten (member) the project’s requirement. Enforced by proxy-native routing — one Ingress per requirement group sharing the host (K8s) vs. per-router forwardAuth (Docker) — plus a server-stamped &access=<req> the shared ingress_auth handler enforces. Only the auth gate varies per route; ingress_class and custom_annotations stay per-project (a per-host limitation).
/.rise auth endpoints on the app hostimplicitHigh-priority route to the backend on both.
Multi-container deployments[containers]Separate Deployments (K8s) vs. one container per spec (Docker).
Cross-container service discoveryRISE_CONTAINER_HOST__*Service DNS (K8s) vs. container-name DNS on rise_default (Docker).
Auto-injected env vars (RISE_APP_URL, RISE_CONTAINER, PORT, …)implicitSame variable contract on both.
CPU / memory limitscpu, memoryPod resources (K8s) vs. nano_cpus/memory (Docker). Docker applies the limit half of a request-limit range to nano_cpus/memory (hard cap), matching the K8s pod limit.
HTTP health checkshealth_check⚠️Readiness/liveness probes (K8s) vs. Traefik per-server health check (Docker). The Docker backend offloads checking to Traefik, honoring path, period_seconds (→ Traefik interval) and timeout_seconds (→ Traefik timeout); readiness is read back from Traefik’s serverStatus and is authoritative with no fallback, so a health_check on Docker requires a reachable traefik_api_url (without one a health-checked deployment never becomes Healthy). The remaining knobs — initial_delay_seconds, failure_threshold and the separate liveness_enabled/readiness_enabled toggles — are K8s-only.
Deployment observability (Pods tab)controller_metadata.pod_statusSame pod_status JSON shape rendered by the frontend.
Rollbackrise deploy rollbackRe-resolves the prior deployment’s image on both.
Private image pullregistry configimagePullSecret (K8s) vs. host-daemon docker login (Docker).
Workload identity tokenstoken-exchange API + [identity].audiencesBoth backends deliver the bootstrap credential + per-audience token files to /var/run/secrets/rise/identity/ and refresh the tokens before they expire. K8s mounts a per-deployment Secret as a volume and a leader-elected controller loop resyncs a project when one of its deployments is due (~2/3 of identity_token_ttl_seconds after each mint) so the sync webhook re-mints the token before it expires — Metacontroller does not resync a steady project on its own; Docker writes the same files via the Docker archive API (PUT /containers/{id}/archive) right after create and re-mints on its own reconcile loop, recovering the credential from the running container across recreates. The token-exchange endpoint is backend-agnostic.
Secret env-var isolationrise env set --secret⚠️K8s stores secret env in a per-project Secret; Docker flattens them into plain container env (visible to docker inspect). Use the Kubernetes backend where at-rest env isolation matters.
Replicas > 1 (horizontal scale)replicasThe request is bounded by deployment_constraints.max_replicas (Docker default 10, RISE_MAX_REPLICAS) on both backends, and additionally hard-capped at 50 by the Docker controller. Docker runs N containers per spec behind ONE Traefik service (round-robin LB) and ONE shared, replica-free network alias (Docker DNS round-robins). Recreates roll one replica at a time — a running, drifted replica is replaced only while every other replica is healthy, so capacity never drops by more than one. K8s uses a Deployment’s replicas.
Zero-downtime active switchimplicit (blue/green)K8s does an atomic blue/green Service selector flip. Docker’s cutover overlaps old and new containers on one Traefik service and drains the old via Traefik’s per-server health check — no recreate gap, but a rolling overlap rather than an atomic switch (a documented, intentional backend difference per the parity policy).
Per-group network isolationimplicitDocker limitation: NetworkPolicy (K8s) has no single-host equivalent; all app containers share rise_default.

Keep this table in sync with the code. When you add or change a backend feature, update the relevant row (and add a row for a brand-new feature) in the same change.