Architecture
Overview / architecture
Section titled “Overview / architecture”The standalone stack brings up everything Rise needs to deploy container apps
through the docker deployment controller (deployment_controller.type: docker):
| Service | Container | Role |
|---|---|---|
rise | rise-backend | Control plane + Docker reconciler. Mounts the host Docker socket to create/route app containers. |
postgres | rise-postgres | State store (migrations auto-run on startup). |
dex | rise-dex | OIDC provider (demo IdP). |
registry | rise-registry | OCI registry for app images. |
traefik | rise-traefik | Reverse proxy; routes {project}.<domain> to app containers. |
The reconciler creates one container per running deployment and stamps two
families of labels on each (see
crates/rise-backend-docker/src/labels.rs):
- Bookkeeping labels (
rise.dev/managed-by=rise,…/project,…/deployment-id,…/route-hash, etc.) so the reconciler can find its containers, detect drift, and garbage-collect orphans. - Traefik labels (
traefik.enable, the per-routerHost(`…`)rule, entrypoint, service port, optional TLS certresolver, optional forwardAuth middleware) on routable containers.
All containers — Rise’s own services and the app containers the reconciler
creates — join the rise_default Docker network. Traefik is pinned to that same
network (--providers.docker.network=rise_default, mirrored by
deployment_controller.traefik_network) so it can reach app containers at their
address on that network. Traefik discovers each app container through the Docker
provider and the per-router traefik.docker.network label (the app containers
carry no extra network alias of their own). The Compose project name is fixed to
rise so the network is always named rise_default regardless of the launch
directory.
Cutover & health checks
Section titled “Cutover & health checks”When a new deployment becomes the active one for its group, traffic moves from
the old containers to the new via a health-driven rolling overlap: the new
and old containers join one group-scoped Traefik load-balancer service
immediately. When the project sets a health_check, the reconciler emits Traefik
health-check labels (traefik.http.services.<svc>.loadbalancer.healthcheck.*,
e.g. path, interval, timeout) so Traefik routes only to servers that pass
the check. The new deployment is retired-old-gated on Traefik’s per-server
serverStatus: the reconciler reads it via the internal Traefik API at
deployment_controller.traefik_api_url and only drops the old deployment once
the new servers are actually UP in Traefik’s rotation. This serverStatus
signal is authoritative with no fallback: a health-checked container is
“ready” only once Traefik reports it UP, so the old deployment is never retired
while the new server is still invisible to Traefik (and therefore receiving no
traffic). deployment_controller.traefik_api_url is therefore required for
health-checked projects — see the note below. Old and new overlap during a
deploy — there is no single atomic cutover; this is a rolling update, like a
Kubernetes rolling update (vs. an atomic blue/green switch).
No Traefik credential needed. The Traefik API is enabled internally (
--api.insecure=true) and reached only over therise_defaultnetwork (http://rise-traefik:8080); it is never published to the host. If you do put the API behind basic-auth, embed the credentials intraefik_api_url’s userinfo (http://user:pass@host:8080).
Probing is opt-in — but requires the Traefik API. With no
health_checkset, a deployment is considered ready as soon as its container is running (Traefik publishes noserverStatuswithout a health check, so run-state is the only signal). Setting ahealth_checkswitches readiness to a 2xx–3xx check enforced by Traefik’s per-server health check, read back viaserverStatus. That signal is authoritative with no Rise-side fallback, so ahealth_checkrequires a reachabletraefik_api_url: without one the deployment never becomes Healthy (the controller logs a clear warning).The zero-gap rollover guarantee only holds when a
health_checkis set. Without one Traefik has no per-server check to drain against, so a new server joins the load balancer the moment it is running — Traefik may route to it before the app inside has finished starting (the same exposure a Kubernetes pod with no readiness probe has). Set ahealth_checkfor apps that need traffic withheld until they are ready.
traefik_api_url defaults differ per environment: the standalone stack defaults
to the in-network http://rise-traefik:8080 (the standalone Traefik enables its
API internally without publishing :8080), while the host-run dev backend
(mise br docker) overrides it to http://localhost:8090 (the dev Traefik
publishes its dashboard there).