Changelog
All notable changes to RunR are documented in this file.
The format is based on Keep a Changelog. Version numbers are the Helm chart versions; a release whose application images did not change says so.
Unreleased#
Changed#
- The source is public at https://github.com/BRF-Tech/runr. The single-host install starts from a checkout of the release tag (compose/README.md).
0.5.7 - 2026-10-17#
Changed#
- The CLI downloads from runr.sh.
curl -fsSL https://runr.sh/install.sh | shinstalls therunrCLI on Linux and macOS (amd64, arm64): it reads the latest release fromhttps://runr.sh/dl/cli/latest.txt(RUNR_VERSIONpins one), checks the archive's SHA-256 against the release'schecksums.txtand installs into/usr/local/binwhen that is writable, else into~/.local/bin(RUNR_INSTALL_DIRsets another directory). The archives of every platform, Windows included, are underhttps://runr.sh/dl/cli/<tag>/.runr version --check-update(andupdate_check: true) reads the latest release from thatlatest.txt(docs/cli.md). - Terraform provider address
runr.sh/brf-tech/runr. runr.sh serves Terraform's provider registry protocol: withsource = "runr.sh/brf-tech/runr",terraform initdownloads the archive for the platform and checks it against the release'sSHA256SUMS, signed with the RunR release key (key ID2588846B241BC095). A configuration that usedbrf-tech/runrchangessourceand moves its state once withterraform state replace-provider registry.terraform.io/brf-tech/runr runr.sh/brf-tech/runr(docs/terraform.md). - The GitHub Action downloads the CLI from runr.sh.
cli-base-urldefaults tohttps://runr.sh/dl/cliandversion: latestis the tag in<cli-base-url>/latest.txt; thegithub-tokeninput is gone (a workflow that still sets it gets an "unexpected input" warning) (docs/github-action.md). - Documentation on runr.sh. The documentation is published at https://runr.sh/docs/ and the security policy at https://runr.sh/security/.
- The Helm chart comes from BRF's public Helm repository. The chart
runris installed without credentials:helm repo add brf-tech https://helm.brf.sh, thenhelm upgrade --install runr brf-tech/runr --set domain=…; the install, upgrade, key rotation and white-label examples use the chartbrf-tech/runrinstead of./helmof a repository checkout (README, docs/upgrade.md).
0.5.6 - 2026-10-17#
Changed#
- The CLI's own API token expires.
runr login --with-passwordcreated itsrunr-cli@<host>API token without an expiry. The token now expires 90 days after the login;--token-ttlsets another lifetime (30d,720h;0orneverkeeps a token without expiry).runr loginandrunr whoamishow when it expires (token_expires_atin their JSON output, and in the context of the config file); once it has expired, a command the API refuses says that the token of the context expired and to log in again, andrunr logoutno longer tries to revoke it. A newrunr logininto a context revokes the token an earlierrunr login --with-passwordcreated for it, asrunr logoutdoes: logging in again replaces a token from an earlier release with an expiring one.--expiresofrunr tokens createand--token-ttlrefuse a fractional day count such as1.5dinstead of reading it as1d(docs/cli.md).
Fixed#
ci.sh ml-runtimes --pushpushed untested images. The push built the PyTorch and TensorFlow images in a separate builder and pushed them right away; the smoke test, and on a GPU host its GPU check, ran for local builds only.--pushnow builds every image into the local Docker, smoke-tests it like a local build (with the GPU check wheredocker run --gpus allgives a GPU, required withML_SMOKE_GPU=1) and pushes only once every image passed:docker pushof the image the smoke test ran, its image ID checked again before the push.--push --no-smokeis refused (docs/runtimes.md).- A deleted user's tokens stayed in the database. An administrator deleting a user (
DELETE /api/users/{id}) left the user's sign-in sessions and API tokens behind; no request could use them without the user, but the rows stayed. Deleting a user — by an administrator or, as before, by the user (DELETE /api/account) — now deletes every sign-in session and API token of the user in the same transaction as the account, then the user's MCP server sessions. In the multi-tenant edition, a deleted account's copies in the organizations it belonged to go as well, with its groups and role assignments there (only the memberships went before); a member removed from an organization loses the API tokens they created in it (these work in that organization only; sign-in sessions and other organizations' tokens stay), and deleting an organization deletes the tokens created in it. The dailyphp artisan tokens:prunealso removes the tokens of users that no longer exist and, in the multi-tenant edition, the tokens created in an organization that no longer exists or whose user is no longer a member (a platform administrator's token stays), and the organizations' copies and role rows of users that no longer exist; an organization whose schema is missing is skipped. The rows earlier deletions left go after the upgrade without any action (docs/security.md).
0.5.5 - 2026-10-17#
Security#
- Signing out did not end the session. From 0.3.0 to 0.5.4,
POST /api/auth/logoutanswered "Logged out." and deleted the session's token, but the API's session tracking (client address, user agent and device of a token), which runs after every request, saved the deleted token again: the token stayed valid. The same kept the sign-in session alive thatrunr login --with-passwordends once it has created its API token. The session tracking now updates the token's row and never re-creates it, and writes only when the address, user agent or device changed. Revoking sessions, deleting API tokens and disabling or deleting the account deleted their tokens as intended; tests now cover each of these paths. - Sign-in sessions expire. A sign-in session — a password sign-in of the console or the CLI, an SSO sign-in, a self-service signup, an accepted invitation — ends 30 days after the sign-in (
RUNR_LOGIN_TOKEN_TTL_DAYS, Helm valueauth.loginTokenTtlDays;0keeps sessions until they are signed out). They had no expiry before. The lifetime counts from the sign-in for the sessions that exist at the upgrade as well: those older than 30 days end with the upgrade, among them the sessions that were signed out but stayed valid, and their users sign in again. Operators need to do nothing. A session signed out during the 30 days before the upgrade stays valid until it is 30 days old; its user can end it at once under Account → Active Sessions (Revoke). API tokens keep the expiry they were created with. - Changing the password ends the user's other sign-in sessions (their browsers are signed out); the session that changed it and the API tokens stay.
php artisan tokens:prune, scheduled daily, removes the sign-in sessions that ended and API tokens 30 days after they expired. API tokens cannot be namedinvitation-accept(norapi, as before): these names mark sign-in sessions, and the API token list no longer shows the session of an accepted invitation, which Account → Active Sessions lists (docs/security.md).
0.5.4 - 2026-10-17#
Upgrade notes#
- The last region workloads on RunR's earlier image names move too.
region:reconcile(stepimage-registry) left the node installers of Kata (with Firecracker's devmapper setup) and gVisor and the region's queue Redis onghcr.io/runrsh/ghcr.io/brf-techuntil their next install. Their pod templates now move toghcr.io/brf-tech/runras well, and their running pods keep running: the installer DaemonSets get the new images together with the update strategyOnDelete(a new pod would re-run the install on its node; kata-deploy's cleanup and reinstall restart the container runtime); the queue Redis, whose streams, consumer groups and KV bindings' data live in memory only, keeps its pod — the Deployment is paused while its ReplicaSet takes the new image, then takes it as well and is resumed without starting a pod. A pod created later (a new node, a deleted or evicted pod) starts fromghcr.io/brf-tech/runr. A gVisor install, a Kata install that re-runs kata-deploy and Kata's Firecracker setup applyRollingUpdateagain; Kata's rollout status counts the pinned installer pods by their config hash, and its in-place drop-in update keeps them pinned (docs/upgrade.md). - hf-serve 1.1.1. The defaults move to
ghcr.io/brf-tech/runr/hf-serve:1.1.1and…:1.1.1-cuda(ai.images.hfServe,ai.images.hfServeCuda). Installations that set these values, for example to a registry mirror of an air-gapped region, update them and mirror the new images. - Components on dedicated workers restart once.
region:reconcile(stepdedicated-worker-toleration) gives the registry, Prometheus, Loki, Tempo and the data services the toleration of the dedicated-worker taint where a pod of them runs on a dedicated worker; that pod restarts once, on the same node (below, Fixed). Components on the other nodes keep running as they are.php artisan region:reconcile --dry-runlists them. exec-tensorflow:2.21-gpuis published again under the same tag (below, Fixed). A function ontensorflow2.21-gpuruns on the new image from its next deploy: Knative resolves the tag to the new digest for the new revision. Installations that mirror RunR's images copy the tag again.
Changed#
hf-serve starts a cached model without the Hub. In online mode hf-serve asked the Hub at every start, also when the pinned commit was already on the model volume, so a start from zero depended on the Hub's reachability and latency. After a load of a pinned commit it now records the load next to the cached repository (per engine, task and pipeline arguments), and a later start with the same settings loads the cached snapshot directly; when that does not load, it loads from the Hub as before. A revision that is a branch or tag is still resolved by the Hub at each start (docs/huggingface.md).
The GPU case of
ci.sh test-aileaves Docker's default runtime alone. The Kind worker with the GPU gets the driver's files thatnvidia-container-cli listnames, mounted read-only (Kind's privileged nodes see the host's/dev/nvidia*already), so a host with the NVIDIA driver and Container Toolkit runs the case as it is configured. Where the NVIDIA runtime is Docker's default runtime and accepts device requests as volume mounts, the worker gets the GPU that way, as before (docs/ai.md).The GPU case of
ci.sh test-aineeds far less disk. Its check image is the new GPU CaaS example (below) instead of CUDA PyTorch (3.3 GB compressed), it goes into the Kind GPU worker only instead of every node, and the host's copy is removed once the worker has it.kind loadwrites its temporary archives toRUNR_E2E_TMPDIR(default/var/tmp) instead of/tmp, which is a RAM-backed tmpfs on some hosts. Before the cluster is created the suite adds up what the run writes, from the built images' sizes and per Kind node, plusRUNR_E2E_GPU_RESERVE_GB(default 12) for the rest; it does not start when Docker's root has less free and prints both figures. This replaces the fixedRUNR_E2E_GPU_MIN_FREE_GB. The case now also asserts that the container ran the kernel on the GPU and returned the CPU's result (docs/ai.md).The GPU CaaS example runs a CUDA kernel in a 43 MB image.
examples/ai/gpu-caaswas CUDA PyTorch with a small inference: 3.3 GB compressed, about 9 GB unpacked. It is now Python's slim image with a standard-library server that runs a SAXPY kernel through the CUDA driver API (the driver compiles the kernel's PTX for its GPU, so no CUDA toolkit is in the image) and compares the result with the CPU's: 43 MB compressed, 134 MB unpacked, for NVIDIA GPUs from compute capability 5.0 on. The answer keepscuda,deviceanddevice_count; the PyTorch fields give way tocompute_capability,memory_mb,driver_cuda_version,computed_on,checksum,expected_checksumandresult_ok(examples/ai/gpu-caas).ci.sh ml-runtimessmoke-tests the-gpuimages on the GPU. On a host wheredocker run --gpus allgives containers a GPU, the handler of a-gpuimage's smoke test runs with it, over HTTP and as a scheduled run: in both, the framework has to see the GPU and run a mean and a matrix product on it. Without a GPU the CUDA build is checked as before and the output says the GPU check was skipped;ML_SMOKE_GPU=1requires it,ML_SMOKE_GPU=0leaves it out (docs/runtimes.md).Pinned helm and kubectl in the API image. The image took kubectl from the latest stable Kubernetes release at build time, so two builds of one tag could differ. helm v3.16.0 and kubectl v1.35.9 are now pinned and checked against their published SHA-256 for amd64 and arm64, and the downloads retry on network errors.
ci.sh test-aikeeps etcd in memory. The suite setsKIND_ETCD_IN_MEMORY=1unless it is set: etcd keeps its data on the Kind control-plane's tmpfs. On a GPU host whose disk other jobs kept busy, the region setup's Knative Operator install failed withetcdserver: request timed out; with etcd in memory the same run got past it.KIND_ETCD_IN_MEMORY=0puts etcd on the disk again (docs/ai.md).Organization quotas overcommit scale-to-zero workloads. In the multi-tenant edition an organization's CPU and memory quota on a region splits into the same two budgets as the region capacity, with the region's overcommit factors. Always-on projects and model services, the Kata pod overhead, queue consumers, sandboxes and warm pool pods count in full against the quota; one pod of every scale-to-zero project, model service and git deploy preview counts against quota × the factor (CPU 3, memory 1.5 by default) minus what the always-on part holds. A factor of 1 on a region keeps the quotas there hard. The quotas on a region still add up to at most its capacity, stored quotas stay as they are, and billing is unchanged. A refusal (422) names the budget that is used up (
budget:always_onorfaas) and what it has free (docs/architecture.md).
Fixed#
- The region registry stopped after a worker selection. Zot installed before a region's workers were selected had no toleration of the dedicated-worker taint. Its node-bound volume (local-path, k3s's default class) keeps it on its node, and when that node was selected as a dedicated worker (
runr.dev/dedicated=projects:NoSchedule), the next Zot pod — after the restart ofregion:reconcile'sregistry-sync-config, the roll ofimage-registry,registry:rolling-restartor a Helm upgrade — stayed Pending ("untolerated taint" and "volume node affinity conflict"): builds failed and the registry pre-warm Job gave up after 600 s. Prometheus, Loki, Tempo and the data services (KV Redis, Garage, the CloudNativePG clusters) had the same exposure. All of them now tolerate the taint and prefer the nodes that are not dedicated workers. The worker selection adds the toleration to such a component whose pod runs on a selected node, andregion:reconcile(new first stepdedicated-worker-toleration) to those on any dedicated worker; a pod Pending on the taint is replaced, as a StatefulSet does not replace a pod that never became ready. The registry restart ofregistry-sync-configcarries the toleration as well (docs/architecture.md). - The registry and Knative after deselecting workers. A registry installed while dedicated workers were selected, its node mirror and Knative's system deployments select the workers by the label
runr.dev/dedicated=projects. Once the last worker was deselected their next pods found no node, and a registry whose volume sat on a deselected node could not start there again. The worker selection andregion:reconcile(stepdedicated-worker-toleration) now move such a registry to any node (the toleration kept, the other nodes preferred, a Pending pod replaced), apply the node mirror for every node once no worker is left — projects then run on any node and pull from the registry there — and drop Knative's nodeSelector (docs/architecture.md). - RunR's own pods on a node selected as a dedicated worker. When RunR runs in a cluster it also uses as a region, the node holding PostgreSQL's volume or the API storage could be selected as a dedicated worker; its taint then kept the next pods (a restart, an upgrade) from starting there. Every pod of the chart — the Deployments and the migration and bootstrap hook Jobs — now tolerates
runr.dev/dedicated=projects:NoScheduleand prefers the nodes that are not dedicated workers (placement.tolerateDedicatedWorkers, on by default). New valuesplacement.tolerations,placement.affinityandplacement.nodeSelectorapply to every pod; the chart adds its toleration, preference and the API storage colocation to them (docs/architecture.md). - The registry pre-warm Job's log. While the registry did not answer, every try printed wget's "can't connect to remote host: Connection refused". The Job now prints one line per wait instead:
waiting for registry http://… to answer: next try in 4 s (2 s waited). - Executor images in the Kind cluster of
ci.sh test. The preload of the default executor images matchedbrf-tech/…and loaded none since RunR's images moved toghcr.io/brf-tech/runr/. It loads the default general runtimes from there again; an ML runtime (several GB) is pulled by the node that runs it. - The TensorFlow
-gpuruntime ran on the CPU. Inexec-tensorflow:2.21-gpuTensorFlow registered no GPU and ran every function on the CPU, without an error: it opens the CUDA libraries of NVIDIA's pip packages by soname through its RUNPATH, which lacks the cuSOLVER directory, and one library it cannot open makes it skip all GPUs. The image now puts everysite-packages/nvidia/*/libdirectory into the dynamic loader's cache, whatever the entrypoint and a function's ownLD_LIBRARY_PATH, and its build fails unless every library there is in the cache. - diffusers model services and
dtype. hf-serve passed the dtype to diffusers astorch_dtype, which diffusers 0.41 deprecates (removed in 1.0); with adtypeinRUNR_PIPELINE_KWARGSthe load failed, because diffusers refuses both arguments together, and a dtype name such as"float16"fell back to float32. hf-serve 1.1.1 passesdtypeto both engines, takestorch_dtypeof the pipeline arguments asdtype, and gives diffusers the torch dtype a name stands for. - pip's dependency conflict in hf-serve's GPU image. The PyTorch base image ships
spin0.18, a developer CLI that requires click below 8.4, and hf-serve's server brings click 8.5: the build printed pip's dependency-conflict error andpip checkfailed in the image. hf-serve 1.1.1-cuda leavesspinout, which nothing in the image uses, and its build fails unlesspip checkpasses. - GPU nodes of
ci.sh test-ai. Every Kind node counted as a GPU node, because all of them are privileged and see/dev/nvidia0: on a GPU host each would have got the NVIDIA Container Toolkit, the GPU label and a--gpu-nodeargument. Only Kind nodes wherenvidia-smi -Llists a GPU count now — the worker with the driver. - Resource bars on overcommitted regions. The projects page added up the projects' reservations in the browser against the region capacity and capped the bar at 100 %, so a region holding more scale-to-zero reservations than its capacity looked full while it still admitted projects. "Total Resources" now shows the always-on and the FaaS overcommit budget per resource as the API admits against them, summed over the regions and per region, with the bars of Settings → Regions → (region) → Workers. In an organization a platform region shows the organization's quota there, split the same way (
capacitywithcapacity_scope: "quota"inGET /api/settings), and no longer shows as unreachable. - An organization's quota usage seen by platform administrators. Tenants → (tenant) → Quotas (
GET /api/tenants/{id}/regions) counted the projects of the organization the administrator was working in, not those of the organization opened. It now counts in that organization's own schema and shows its quota's always-on and FaaS overcommit budgets (capacityper region) with the bars of the projects page. - Quota refusals for a resource without a quota. A 422 of the organization quota (and a sandbox's
quota_region) reported 9223372036854775807 as what was left of a resource with quota 0. It now givesremaining_cpu_m/remaining_memory_mbonly for the resources with a quota and names only the resource that is short. - The disk check of
ci.sh test-aicounts the whole run. It counted hf-serve and the GPU image per Kind node plus a 12 GiB reserve, and let a GPU run start with 23 GiB free under Docker's root; that run was down to 5 GiB free by its first AI case, with the other AI cases, GPU support and the GPU case still ahead. The check now also counts each Kind node's own data (1 GiB: Kubernetes' images, copied into every node), the Compose stack (3 GiB) and, unlessKIND_PRELOAD=0, the preloaded images on every node and the host (1.4 GiB each): about 35 GiB with the default Kind cluster, 25 GiB withKIND_WORKERS=1 KIND_PRELOAD=0. Its refusal names both options (docs/ai.md). - A Kind e2e run stopped from outside cleans up.
ci.sh test, its suites (test-ai,test-runtimes, …) andtest-helm-upgraderemove their Kind cluster, Compose project, Kind lock and Cloud Provider KIND also when they are stopped: on TERM or HUP at once, also in the middle of a step, and on INT (Ctrl-C) once the running command has stopped. A run whose output pipe was stopped together with it ended at the cleanup's first line and left all of them behind; INT to the run while a command ran could let it go on with its next step; a stop duringdocker compose upleft the Compose stack; and a run stopped while it waited for the Kind lock deleted the cluster of the same name that the lock holder used. The cleanup touches only what the run created, and when the run's output is gone it writes to${TMPDIR:-/tmp}/<cluster>-cleanup.log(docs/development.md). - A short hiccup of the Kubernetes API server no longer fails the Knative Operator install. The operator's Helm install failed on its first error, and with it the region setup: on a cluster whose etcd could not keep up with a busy disk, Helm got
etcdserver: request timed outonce and the region had no Knative. The install is now tried again on transient errors of the API server — etcd timeouts and leader changes, 5xx and 429 answers, timeouts and dropped connections on the way to it — five tries in all, 2, 4, 8 and 16 s apart, each retry in the operator's install log. Any other failure, Helm's own--waitrunning out included, fails the install at once, as before.
0.5.3 - 2026-10-16#
Fixed#
- Tempo crash loop on storage that ignores fsGroup. With a storage driver that does not apply the pod's
fsGroup, Tempo's volume stayed owned by root and Tempo restarted with "mkdir /var/tempo/traces: permission denied". Tempo gets an init containerfix-permissionsthat hands/var/tempoto Tempo's user, as Loki's does;region:reconcile(steptracing) adds it to installed backends. A Tempo pod that crash-loops with the earlier template kept its StatefulSet from rolling: the step replaces it, and the new pod starts with the init container. - Tracing requests on small regions. On a single-node region, Knative's own pods stayed Pending on "Insufficient cpu" once the tracing backend was installed. The collector and Tempo request 50m CPU each (100m before), in line with the other region add-ons; the limits are unchanged.
region:reconcile(steptracing) moves installed backends to these requests, and the region capacity subtracts them like every other platform pod. - Registry pre-warm while the registry restarts. The
registry-prewarmJob started beforeimage-registryrolled the registry, so every image failed with "Connection refused", and the failed Job kept later reconciles from starting another one for 24 hours. The step now runs last inregion:reconcile; the Job waits for the registry to answer before it starts and before each further try of an image (three tries, backing off), a failed pod is retried twice, and a Job that failed is deleted and started again by the next reconcile. runr regionsshowed 0 projects to a tenant. A platform region assigned to a tenant answered withoutproject_countin the tenant's view (GET /api/knative-regions). It now counts the tenant's own projects there.
0.5.2 - 2026-10-16#
Upgrade notes#
- Image namespace ghcr.io/brf-tech/runr. Stored image references move automatically, from 0.5.1 (
ghcr.io/brf-tech/<name>) and from earlier releases (ghcr.io/runrsh/<name>): Helm values (runr.image), the database (migrationmove_images_to_runr_namespace, tenant migration0055,images:move-registryin the bootstrap job) and the region workloads (region:reconcile). A registry of your own that holds copies of RunR's images under their paths needs them under the new paths: copy them fromghcr.io/brf-tech/runr(docs/upgrade.md). - ML runtime versions. PyTorch 2.2 / 2.3 and TensorFlow 2.15 / 2.16 and their
-gpuversions, listed by the local and demo seeders only and never published as images, leave the catalog (migrationretire_unpublished_ml_runtime_versions). Projects that name one keep running as deployed and take the runtime's default version at their next deploy.executor:synclists the published versions: PyTorch2.14/2.14-gpu, TensorFlow2.21/2.21-gpu(docs/runtimes.md). - Tracing backend on existing regions.
region:reconcile(steptracing) installs the OpenTelemetry Collector and Grafana Tempo where tracing is enabled and the backend is missing — the region's own tracing mode, or for a region without one (Knative Serving installed by 0.4.x) the platform'sobservability.tracing.defaultModewhileinstallWithServingis on — and moves installed backends to collector chart 0.176.0 (collector 0.162.0) and Tempo chart 3.2.0 (Tempo 3.1.0); the collector and Tempo pods roll once. Tempo chart 3.2.0 appliestempoRetentionHours(default 72 hours) to the deletion of old traces, which chart 3.1.0 left at Tempo's 14 days. To keep tracing off on regions without their own setting, setobservability.tracing.installWithServing=falsein the upgrade (docs/upgrade.md).
Added#
- PyTorch and TensorFlow runtime images.
ghcr.io/brf-tech/runr/exec-pytorch:2.14(torch 2.14.1, torchvision 0.29.1) and…:2.14-gpu(the same torch for CUDA 12.6),ghcr.io/brf-tech/runr/exec-tensorflow:2.21(TensorFlow 2.21.0, CPU build) and…:2.21-gpu(CUDA 12): Python 3.12 with Keras (TensorFlow), NumPy, SciPy, pandas, Matplotlib, scikit-learn and Pillow, on the Python runtime's handler server, for linux/amd64.runtime/ci.sh ml-runtimes [--push] [--list]builds them; a build fails unlesspip checkpasses and the framework and its CUDA build match the tag, and a local build answers a tensor-op handler over HTTP and as a scheduled run.executor:syncreads aX.Y-gputag as the GPU version ofX.Y. The node image pre-pull and the region registry pre-warm leave the ML runtime images out: the nodes their functions run on pull them (docs/runtimes.md). - Billing events, newest first.
GET /api/billing/events?order=desclists the newest events first;before=<event id>bounds the list asafter=does, andmeta.next_beforecontinues a newest-first list (meta.next_afteran oldest-first one) (docs/billing.md). The console's billing settings read their 20 newest events with one request. - Registry pre-warm.
region:reconcilehas the internal registry of each region fetch the base images of the release's function builds before a first build needs them (stepregistry-prewarm, a Job in the registry namespace) (docs/runtimes.md). - Any sandbox command on a terminal.
POST /api/sandboxes/{id}/exec/wstakestty,colsandrows: the command runs on a terminal of that size, so shells, REPLs, editors and pagers work as in a terminal window.{"type":"resize","cols","rows"}frames change the size,stdin_closesends Ctrl+D,TERMdefaults toxterm-256color, and the start frame and the answer say whether the command has a terminal. Withoutttythe exec WebSocket works as before (docs/sandbox-api.md). runr sandbox exec -it.-truns the command on a terminal over the exec WebSocket; with-iton a terminal runr's terminal is raw while the command runs (every key, Ctrl+C included, goes to the command), the size follows the window (SIGWINCHon Linux and macOS, the console size on Windows), the terminal is restored on every way out and runr exits with the command's exit code (docs/cli.md).- Exec WebSocket in the sandbox SDKs. Python
Sandbox.exec_ws()/AsyncSandbox.exec_ws()and Nodesandbox.execWs()/runCodeWs()run a command over the exec WebSocket: input goes to the command while it runs (send_stdin/sendStdin,close_stdin/closeStdin), the output arrives as events or throughwait(),kill()stops the command, and with a terminal (tty,cols,rows)resize()changes its size. The Python SDK depends onwebsockets13 or newer; the Node SDK uses the runtime'sWebSocket(Node.js 22+, Bun, Deno) or its own client on Node.js 18 and 20 and keeps no dependencies (sdk/sandbox/python, sdk/sandbox/node). runr metrics livereads what a deployment uses right now — CPU, memory and requests per second from the region's Prometheus, with the averages of the last hour (--quickwithout them) — for the live deployment or--deployment, once or every--watchinterval,-o json(docs/cli.md).- Region lifecycle in the CLI.
runr regionscreates, checks, changes and removes regions (create,check,get,set,rm,status), saves a region's kubeconfig to a file only its owner can read (kubeconfig;--stdoutprints it) and chooses the worker nodes projects run on (nodes set). It installs, adopts and removes Knative (knative install|uninstall|detect) and manages the queue Redis, the image registry (also regions created with one, and checks of their storage), the move to another cluster (migration), function-domain TLS, Skupper sites, the FaaS overcommit factors (capacity), demo projects and WebAssembly support (wasm [status]|install|uninstall). Kubeconfigs, passwords and keys come from files or stdin and never reach the trace; removals ask for confirmation unless--yes, and--waitfollows background installs (docs/cli.md). - Mirror fallback for Docker Hub's pull limit. When Docker Hub refuses a copy,
ci.sh mirror-reqsandmirror-basescopy the image frommirror.gcr.ioorpublic.ecr.aws/docker, by the digest Docker Hub reports for it, so the copy is the same image (REQS_UPSTREAMSsets the sources and their order).mirror-reqstakes image names to copy only those and exits 1 when a copy fails.
Changed#
- Build deadline. Function build Jobs (Kaniko and WebAssembly toolchain builds) run up to
BUILD_DEADLINE_SECONDS, 900 s by default; the build checks and the stale-build check follow it. A build that runs out of time while the region registry still fetches its base image is built once more, and itsbuild_errorsays so meanwhile. - Sandbox start. A starting sandbox pod is read every 150 ms (
SANDBOX_START_POLL_MS, 100–200) for its first 30 s and once a second after that, so a sandbox is reported running at most one interval after its pod runs (docs/sandbox-api.md). - Warm pools. A pod a pool refill starts can be claimed as soon as it runs; the next per-minute refill no longer has to mark it warm first (docs/sandbox-api.md).
- Model docblocks. A
migraterunside-helper:models --write --resetonly withAPP_ENV=localandIDE_HELPER_POST_MIGRATE=true, so migrations in test containers leave the models alone (docs/development.md). - Tracing images from the reqs mirror. The region collector and Tempo run
ghcr.io/brf-tech/runr/reqs/opentelemetry-collector-contrib:0.162.0andghcr.io/brf-tech/runr/reqs/tempo:3.1.0, RunR's copies of the Docker Hub images; the Helm valuesobservability.tracing.collectorImage/tempoImagepoint them at a registry of your own.collectorChartVersion/tempoChartVersionare empty by default: the versions RunR pins. - Build base images from the reqs mirror. The WebAssembly build images, the Spin shim build, hf-serve's GPU image and the GPU images of the pytorch and tensorflow runtimes build on
ghcr.io/brf-tech/runr/reqscopies of their Docker Hub bases (TinyGo, Node.js, Python, Rust, PyTorch, CUDA).ci/check-dockerfiles.sh(CI jobcheck:build-scripts) fails on a Docker Hub base image. - Image builds pull their base images. Every image build of
runtime/ci.sh(local and--push, the release tars ofbuild-deploy) and ofcompose/build.sh/compose/deploy.shruns with--pull, so an older copy of a base image in the build host's Docker is never built on. - Runtime versions from the image registry.
executor:synclists the versions of the built-in runtimes from the tags of their images underghcr.io/brf-tech/runr(the registry's OCI distribution API, read anonymously) instead of Docker Hub'srunrsh/exec-*repositories, so every version published in RunR's image namespace reaches the catalog, PyTorch and TensorFlow included.versionDiscovery.repositorynames the whole image repository (ghcr.io/brf-tech/runr/exec-python); one without a registry host is on Docker Hub (docs/runtimes.md). - Image namespace ghcr.io/brf-tech/runr. RunR publishes every image under
ghcr.io/brf-tech/runr, so the organization's other packages never share a name with RunR's:reqs/<image>,exec-<runtime>,hf-serve,image-upload,containerd-shim-spinkeep their names there,runr-wrapper,runr-agent,runr-queue-consumerandrunr-supply-chainbecomewrapper,agent,queue-consumerandsupply-chain, andrunr/apiandrunr/uistay where they are.runtime/ci.shpushes toREGISTRY=ghcr.io/brf-tech/runrby default,runr devpulls from it, and the system images policy passesghcr.io/brf-tech/runr/**.
Security#
- ML runtimes.
pip-auditfinds no known vulnerability in the packages of the four ML runtime images. The PyTorch 2.3 package set they replace had 20 advisories in torch (among them CVE-2025-32434) and the TensorFlow 2.16 set one in protobuf that TensorFlow 2.16'sprotobuf < 5pin cannot take the fix of; pip and setuptools are upgraded to 26.2.1 and 84.0.0. The PyTorch weights example (examples/ai/hf-python/weights) takes transformers 5.20.
Fixed#
- Registry leftovers. Deleting a runtime deletes its images (
runr-runtimes/<id>) from the registries of the regions it was built in. Builds no longer open an upload before they are built, so a failed build leaves no empty repository, and a daily job hands build repositories without a tag to Zot's garbage collection, which removes them. - Sandbox API e2e. The Kata guest-size check creates its sandboxes with
cpuandmemory, so they have the size whose vCPUs it checks. - Upgrade hook while PostgreSQL restarts. An upgrade that restarts the control plane's PostgreSQL (a new image, or its volume moving to another node) failed in the post-upgrade
runr-bootstraphook, which started before the database accepted connections. The hook now waits up to 4 minutes for the database, and its deadline is 10 minutes. - WebAssembly build images on a docker-container builder. A buildx builder with the docker-container driver (
ci.sh wasm --push, CI) gives every build stepOTEL_EXPORTER_OTLP_TRACES_ENDPOINT=unix:///dev/otel-grpc.sock, and Spin 4.1 exited with "Failed to initialize telemetry: invalid URI". The build steps ofruntime/wasm-*run Spin withOTEL_SDK_DISABLED=true(a build argument: the images do not carry it). --pushexited 1 after a successful push.ci.sh build,queue-consumer,wrapper,build-agent,test-imageandsupply-chainended with a test that failed when pushing; they exit 0 once the push is done.
0.5.1 - 2026-10-16#
Upgrade notes#
- Region capacity admission. Scale-to-zero projects and model services no longer hold the region capacity: each counts one pod against a FaaS overcommit budget of capacity × 3 (CPU) and × 1.5 (memory), set per region. Every existing region gets these defaults; a factor of 1 keeps every reservation against the capacity. Sandbox and model service pods are no longer subtracted as platform pods, so a region's capacity rises by what they request at its next status check (docs/architecture.md).
- Image namespace
ghcr.io/brf-tech. RunR's images are published underghcr.io/brf-tech— the same names, tags and digests as underghcr.io/runrshup to 0.5.0 — and the upgrade moves every reference an existing installation holds (exact prefix only; other registries stay): image values of the Helm release, also the ones--reuse-valueskeeps (chart helperrunr.image); stored references in the central and every tenant schema (migrationmove_images_to_brf_tech_registry,php artisan images:move-registryin the bootstrap job); and the workloads in the regions (region:reconcile). Knative Services get a new revision that takes the traffic once it is ready; the other workloads roll once. The node installers of Kata and gVisor and the region's in-memory queue Redis keep running as they are, a restart would cost more than the move (docs/upgrade.md). - Terraform. The provider's address is
brf-tech/runr: changesourceand runterraform state replace-provider registry.terraform.io/runrsh/runr registry.terraform.io/brf-tech/runronce. - Model images. The defaults move to TEI 1.9.4 (
ai.images.teiCpu…:cpu-1.9.4,ai.images.teiGpu…:{arch}1.9.4) and hf-serve 1.1.0 (ghcr.io/brf-tech/hf-serve:1.1.0,…:1.1.0-cuda). Installations that set these values, for example to a registry mirror of an air-gapped region, update them and mirror the new images: TEI 1.5 can no longer download models from the Hugging Face Hub.
Added#
- Why a model service does not start. While a model service deploys, RunR reads its pods and shows Kubernetes' reason next to the status —
CrashLoopBackOffwith the engine's last output,OOMKilledwith the memory limit,ImagePullBackOff/ErrImagePullwith the image and the registry's answer,Unschedulablewith the scheduler's message — in the console, the API (status_reason, details instatus_message) and the MCP tools (docs/huggingface.md). - Model image pre-pull. Setting up a region's model storage pulls the hf-serve image onto the nodes projects run on, and creating or deploying a model service pulls its engine image onto the nodes it can run on (GPU nodes for GPU services), so a first start no longer waits for a half-gigabyte image (docs/huggingface.md).
- TEI on Blackwell GPUs. Compute capability 10.0, 12.0 and 12.1 get TEI's
100-,120-and121-images; any other GPU getscuda-, which picks its kernels when it starts. TEI services answer with their served name in/v1/embeddings.
Changed#
- Region capacity: always-on and FaaS overcommit budgets. Always-on projects (
min_scale≥ 1 orscale_to_zero: false) reserve (size + queue-proxy + Kata overhead) ×max(1, min_scale)against the region capacity without overcommit, together with the Kata pod overhead of scale-to-zero projects, queue consumers, always-on model services, sandboxes and warm pool pods. Scale-to-zero projects and model services count one pod (size + queue-proxy) against capacity × the region's overcommit factor, minus the always-on reservations. Refusals name the budget and what is still free. - Overcommit settings.
GET/PUT /api/knative-regions/{id}/capacity(regions.view/regions.manage) shows both budgets and setsfaas_cpu_overcommitandfaas_memory_overcommit(1–10); Settings → Regions → (region) → Workers shows and sets them. The region resource carries the factors and acapacityobject;used_cpu_m/used_memory_mbnow include the sandboxes and may exceed the total on an overcommitted region. Capacity alerts measure the fuller budget. Audit eventregion.capacity.update. - Sandbox API. A sandbox that needs a new pod, and every warm pool pod, needs room on the region's always-on budget; otherwise the create is refused with
quota_capacity. - Repository. The repository is github.com/brf-tech/runr: Go module paths
github.com/brf-tech/runr/…, the GitHub Actionbrf-tech/runr/actions/deploy, CLI release downloads and update checks, the Terraform providerbrf-tech/runr(mirror repositorybrf-tech/terraform-provider-runr), chart and SDK metadata. - Images. Images under
ghcr.io/brf-tech: the API and UI, the runtime and WebAssembly build images, the wrapper, region agent, queue consumer, image-upload, supply-chain, hf-serve and Spin shim images and thereqsmirror; the CLI'srunr devand every region component use them. - Region reconcile.
region:reconcilehas two more steps:admission-policybrings the supply-chain admission policy of a region to this release's image globs,image-registrymoves region workloads to RunR's image namespace;traefik-imagealso moves a Traefik of another version fromghcr.io/runrsh, andwasm-sandboxre-runs the WebAssembly installer for its shim image. - Model sizes count the weights an engine loads. A Hub repository that holds one model in several formats (safetensors, PyTorch, TensorFlow, Flax, ONNX, OpenVINO) counts one of them: a 268 MB model gets 1536 MiB on CPU instead of 3328 MiB. Hub models already in the library are sized again the next time a one-click deploy reuses them (docs/huggingface.md).
- Model service threads follow the CPU limit. Model service containers get
OMP_NUM_THREADS/MKL_NUM_THREADS= the CPU limit rounded up, and hf-serve reads its cgroup quota when they are missing: a 1-CPU hf-serve service no longer runs one PyTorch thread per node CPU. - hf-serve 1.1.0 restarts on a failed model load. It writes the reason to the termination log and exits, so the pod retries with back-off and the service status shows why, instead of staying unready.
Security#
- Dependency updates for published advisories: the console's code editor takes monaco-editor 0.57 (its bundled DOMPurify 3.4.15) and the console builds with Tailwind CSS 4 (it looks as before; braces, micromatch, chokidar, fast-glob and postcss-selector-parser leave the build tooling), the workflow SDK tests with Vitest 4 (development needs npm 11), every Go module requires go1.26.9 (GO-2026-6603 … 6617 in net/http, HTTP/2, net/textproto and crypto/tls) and the CLI takes
golang.org/x/sys0.48.npm auditreports one low advisory: DOMPurify'sIN_PLACEmode in the copy bundled with Monaco, which does not use that mode. With Tailwind CSS 4 the console needs Safari 16.4, Chrome 111, Firefox 128 or newer. - hf-serve 1.1.0 (the
transformers/diffusersengine and the mirror and upload helpers): PyTorch 2.5.1 → 2.14.1 (19 advisories, among them CVE-2025-32434), transformers 4.49 → 5.20 (24), Starlette 0.52 → 1.8 with FastAPI 0.143 (5), huggingface_hub 1.33, pip 26.2 and setuptools 84 in the image (8); the GPU image is built on PyTorch 2.14.1 with CUDA 12.6 and diffusers 0.41.pip-auditfinds no known vulnerability in the new image; hf-serve's tests run on pytest 9.1.
Fixed#
- Kata overhead of the deployed RuntimeClass. Capacity counted a project's Kata pod overhead from the region's current sandbox mapping; it now follows the RuntimeClass the project (or model service) was last deployed with, so a mapping change counts from each workload's next deploy.
- Model services and sandboxes counted twice. The capacity refresh subtracted model service and sandbox pods as platform pods while admission counted them as well (a region showed 3800m used of 3405m). They are counted once now, from their records.
- TEI embedding services crash-looped with "relative URL without a base": the Hugging Face Hub answers downloads with a relative redirect that TEI 1.5 cannot follow. RunR runs TEI 1.9.4.
0.5.0 - 2026-10-15#
Upgrade notes#
- Upgrading. Upgrade with
helm upgradeof the running release (--reuse-valuesworks), never uninstall and install again; back up the database and do not edit projects or deploy while the upgrade rolls out (docs/upgrade.md).ci.sh test-helm-upgraderepeats the upgrade frommainon a Kind cluster. - Roles and permissions. In the single-tenant edition the administrator account becomes Owner and every other user Admin (0.4.x had no permission checks); in the multi-tenant edition tenant
owner,adminandviewermap to the built-in roles of the same name, and tenantmembermaps to a custom role Member, created in every tenant that has members: what members could do before (the Developer permissions withoutmodels.deployand the hosted MCP server permissions, plussettings.manageandsso.manage). Tenant viewers, who could change and deploy projects in 0.4.x, are read-only now. API tokens with*are unchanged; tokens with 0.4.x scopes (projects:read,deployments:write, …) are translated to permissions, also when an older client still sends them — and are enforced now, so aprojects:readtoken no longer deploys. Users the previous version creates while the upgrade rolls out get the default role from the bootstrap job (php artisan rbac:assign-missing). See docs/permissions.md. - Secrets at rest. The upgrade migrations encrypt every stored secret in the central and every tenant schema with
APP_KEY. Keep a copy of the key — without it the encrypted columns cannot be read. Rows written in plaintext by an older pod during the rollout are encrypted on their next save, or runphp artisan secrets:encryptonce the rollout is done; after a key change,php artisan secrets:encrypt --rotatere-encrypts with the new key (docs/security.md). - Supply chain. Regions that exist at the upgrade keep SBOM generation and scanning off, so an upgrade never starts Jobs whose tools image a region cannot pull; enable them per region in the region's Supply chain tab. New regions start with both on (docs/supply-chain.md).
- Private broadcast channels. Every WebSocket channel is private. A custom client that subscribes to
platform,notifications,deployment.{projectId}or the ticket channels must authorize the subscription withPOST /api/broadcasting/authand the user's API token; a user's own notifications arrive onnotifications.{userId}(docs/security.md). - SSO code exchange. OAuth, OIDC and SAML sign-ins redirect to
/oauth/callback?code=<one-time code>instead of carrying the API token in the URL. A custom front end exchanges the code (valid for 60 seconds, one use) withPOST /api/oauth/exchange(docs/security.md). - New endpoints on the console domain. The Helm gateway and the Compose Caddyfile route
/git/*,/mcp,/mcp/*,/oauth2/*and/.well-known/oauth-*to the API, streamed. A proxy of your own in front of the console must do the same, without buffering or compressing them (with ingress-nginx:proxy-buffering: "off"and aproxy-read-timeoutof at least 900 throughingress.annotations). The MCP server and its OAuth server, including dynamic client registration, are on by default (MCP_ENABLED,MCP_OAUTH_ENABLED,MCP_OAUTH_OPEN_REGISTRATION);gitDeploy.enabled: falseturns the git remotes and provider webhooks off. - Per-region features. GPU support, WebAssembly support and the data services of bindings are installed per region when you need them; KV, object and Postgres bindings are refused in a region until its data service is ready. Custom runtimes stay off until a holder of
runtimes.manageswitches Custom Runtimes on (Settings → Platform). Existing tenants get no GPU limit (gpu_quotaempty). - Triggers. The
workflowtrigger type runs durable workflows; theeventtrigger type is rejected by validation (422). Existing rows of either type stay readable and deletable. - Billing (multi-tenant). Payment records become charges (
turn_payment_records_into_charges): the billing service's invoice id is kept asexternal_ref, cancelled records are void, records of an ended period are closed with their invoiced (else recorded) amount. RunR no longer calls a billing service to create invoices, poll payment status or refund: your billing system reads closed charges through the billing API or thebilling.period.closedevent and invoices them.billing.gatewayUrlandbilling.gatewayTokenare not used any more;billing.webhookSecretnow signs the billing events sent to the newbilling.webhookUrl./api/tenants/{tenant}/paymentsand/api/tenant/paymentsgive way to/api/billing/chargesand/api/tenant/billing(docs/upgrade.md). - Defaults. The first administrator's default e-mail is
admin@example.com(ADMIN_EMAIL, Helmadmin.email); existing installs keep theirs.api/.env.dockerships without anAPP_KEY(scripts/devready.shgenerates one) andcompose/deploy.shtakes its target fromDEPLOY_SERVER,DEPLOY_USERandDEPLOY_DIR.
Added#
Functions and runtimes#
- Pluggable runtimes (docs/runtimes.md): every language is a plugin — a
runtime.yamlmanifest plus an image — that runs inhttp,execorpersistentmode; inexecandpersistentmode the universal wrapperrunr-wrapperturns HTTP requests, queue CloudEvents and schedule ticks into invocations over stdin / stdout. The built-in runtimes are plugins of the same kind; platform administrators register platform runtimes and organizations custom ones (Settings → Runtimes,/api/runtimes, permissionsruntimes.manage,runtimes.use_custom), with manifest validation, runtime image builds and test deploys. Example runtimes for C, Rust, Bash and the toy language Q inexamples/runtimes;ci.sh test-runtimes. - WebAssembly functions (docs/wasm.md): Rust, Go (TinyGo), JavaScript / TypeScript and Python as Spin components on the runwasi Spin shim. Install in the region's Isolation tab puts the shim on the selected nodes, creates the
wasmtime-spin-v2RuntimeClass and turns on the Knative features Wasm functions need; the runtimeswasm-rust,wasm-go,wasm-jsandwasm-pythonthen appear in that region's runtime picker.ci.sh test-wasm. - RunR's build of the Spin shim, containerd-shim-spin v0.26.0-runr.1 (
ghcr.io/runrsh/containerd-shim-spin,runtime/wasm-shim-spin,ci.sh wasm-shim): upstream v0.26.0 with its runwasi library giving the Linux containers of a Wasm pod — Knative's queue-proxy — the environment of their pod spec, which the upstream releases do not; built from pinned sources and toolchains, Apache-2.0 with LICENSE and NOTICE in the image. Thewasm-installertakes it from the image (init container) and checks it against the image's SHA256SUMS (docs/wasm.md). region:reconcilebrings the shims on the nodes of a region with WebAssembly support up to the release: when the region'swasm-installerinstalls other shim binaries or is missing, it queues the region's WebAssembly install with the same shims and nodes, which replaces the binaries in place (the container runtime restarts only where RunR's configuration block changes).wasm-execruntime (docs/wasm.md): any WASI command — a preview 1 module from C / C++ (wasi-sdk) or Zig, a WASI 0.2 component from Go (TinyGo with wasm-tools), a projectbuild.shor an uploaded.wasm— on Spin's WAGI executor (the request on stdin and in CGI variables, the response on stdout); runtime manifests on the Spin shim may declarewagi. Examplesexamples/wasm/c-execandexamples/wasm/q-exec.- Uploaded images (docs/images.md):
docker savearchives, OCI image-layout tars and.wasmmodules uploaded through Settings → Images,POST /api/images/uploadsorrunr images pushinto a region's registry in resumable chunks — also for regions without internet access. Uploaded images run as CaaS images, as runtime versions (upload:source) and aswasm-execmodules, get the supply-chain pipeline of a build and count against a per-organization storage quota. Permissionsimages.view,images.upload,images.delete; Helm valuesimages.*.
Deploy#
- Git deploy (docs/git-deploy.md): every project has a git remote
https://<console>/git/<organization>/<project>.git(smart HTTP, an API token as password); a push to the deploy branch becomes a version with its commit, appliesrunr.yaml, builds and deploys. Projects connect to repositories on GitHub (GitHub App), GitLab or Forgejo / Gitea with push-to-deploy, commit statuses and a deploy history; monorepos use a root directory. Permissionsgit.pull,git.push,git.manage,git_providers.manage; Helm valuesgitDeploy.*. - Preview environments per pull / merge request:
<project>-pr-<number>on the function domain with environment overrides and scale-to-zero, a PR comment kept up to date, removal on close, merge or after a TTL, a per-project limit and the organization quota; secrets reach forks only when allowed. Permissionpreviews.manage. runrCLI (docs/cli.md): log in, scaffold (runr init), run locally on the runtime images (runr dev, also for runtime plugins) and deploy (runr deploywithrunr.yaml), plus commands for every feature of this release — secret variables, git deploy and previews, runtimes, workflows, bindings, models, sandboxes, uploaded images, traces, uptime checks and alerts, MCP clients and hosted MCP servers, supply-chain reports, data residency, members, roles, groups and the audit log. Releases for Linux, macOS and Windows.- Terraform provider
terraform-provider-runr(docs/terraform.md) for projects, environment variables (write-only secrets withvalue_wo), domains, deployments, bindings, runtimes, workflow triggers, model services, hosted MCP servers, roles, groups, project access and branding. - GitHub Action
actions/deploy(docs/github-action.md): deploys a project with the CLI on push and runs pull request preview environments.
Data#
- Bindings (docs/bindings.md): managed KV (a Redis ACL user and key prefix), object storage (a Garage bucket or an external S3 endpoint), Postgres (CloudNativePG, shared or dedicated cluster) and external credentials, attached to projects with an env prefix and injected through the Secret
<project>-bindings; credential rotation with a rolling restart, deletion with retention and restore, health, usage metering and per-organization quotas. Console: project Bindings tab, Settings → Data, region Data services. Permissionsbindings.view,bindings.manage,bindings.view_credentials,regions.data_services.manage; Helm valuesbindings.*. - Durable workflows (docs/workflows.md): code-first, step-based workflows whose progress survives crashes, redeploys and long sleeps — memoized steps with retries and backoff, sleeps, waits for events, child workflows and events — started by the API, events, cron, queue messages or other workflows. Python (
runr-workflows) and TypeScript (@runr/workflows) SDKs on a language-neutral protocol; a project Workflows tab with run timelines and an Events view. Permissionsworkflows.view,workflows.run,workflows.manage,events.send.
AI#
- GPU workers (docs/ai.md): GPU nodes, products, GPU memory and allocation per region; Install GPU support puts the NVIDIA device plugin (with GPU Feature Discovery and optional time-slicing) or the NVIDIA GPU Operator on a region. Permission
regions.gpu.manage. - GPU workloads:
config.gpu(count,type,memory_min) on CaaS projects and on functions of GPU runtime versions — GPU limits, node selection by product and memory, runc or gVisor with nvproxy — admitted against the region's GPU nodes and the tenant GPU quota, and metered as GPU time in the usage aggregation. - Hugging Face models (docs/huggingface.md): a Hub browser, one-click model services (vLLM, TGI, TEI, RunR's
hf-servefor transformers / diffusers, or a custom image) online or offline, offline copies in the region's model volume (mirror Jobs, chunked uploads and imports for air-gapped regions), a playground, and model bindings that give functions in any languageRUNR_MODEL_<ALIAS>_*environment variables. Permissionsmodels.view,models.deploy,models.manage; examples inexamples/ai. - RunR MCP server (docs/mcp.md): a built-in Model Context Protocol server at
/mcp(Streamable HTTP, sessions, progress for long-running tools) with tools, resources and prompts over the RunR API; the tools a client sees follow its token's permissions and every call is audited. MCP clients connect with an API token or through OAuth 2.1 (dynamic client registration, PKCE, a consent page in the console). Console: Settings → AI agents (MCP). - Hosted MCP servers: any HTTP project served at
/mcp/servers/<slug>behind a gateway with public, RunR-token or OAuth access, a signed caller identity, a call log and an inspector in the project's MCP tab; Python and TypeScript starters as the runtimesmcp-pythonandmcp-node(examples/mcp). Permissionsmcp.servers.manage,mcp.servers.call. - Sandbox API (docs/sandbox-api.md): isolated sandboxes for untrusted code on your own cluster — pods under the region's strongest sandbox runtime (Kata, else gVisor) with Pod Security
restricted, default-deny networking with an optional egress allowlist and resource limits. Exec as JSON, Server-Sent Events or WebSocket, background processes, files, a code interpreter, a console terminal, pause / resume, TTL and idle expiry, warm pools, organization quotas and usage metering. Python (runr-sandbox) and TypeScript (@runr/sandbox) SDKs; permissionssandboxes.view,sandboxes.create,sandboxes.exec,sandboxes.manage; Helm valuessandboxApi.*.
Security and enterprise#
- Roles and permissions (docs/permissions.md): one permission catalog for every API route, built-in roles (Owner, Admin, Developer, Viewer, Billing), custom roles, groups, per-project access and API tokens limited to a selection of their owner's permissions. SSO providers (OIDC, SAML) map identity-provider groups to RunR groups and roles and provision members just in time;
RBAC_DEFAULT_ROLEsets the role of new members. - Encryption at rest for every stored secret (docs/security.md): environment variable values, version snapshots, region kubeconfigs, Redis / registry passwords and S3 credentials, cold-storage credentials, provider credentials and white-label keys, with key rotation through
APP_PREVIOUS_KEYSandphp artisan secrets:encrypt --rotate. Helm valuessecret.appKey,secret.previousAppKeys. - Secret environment variables: write-only values per project, delivered through the Kubernetes Secret
<project>-secret-envandsecretKeyRefentries; reading a value back needssecrets.view_values(runr env set --secret,secret = truein Terraform). - Audit log: an append-only record of sign-ins, account and token changes, project, deploy and rollback, shell, domain, gateway, region, runtime, member, impersonation, settings, billing and SSO actions, branding, signup, plan and billing-integration changes (field names only, never secret values) and authorization denials, with filters, CSV / JSON export, a daily retention prune (Helm
audit.retentionDays, default 365 days) and optional JSON-line forwarding to a SIEM (Helmaudit.siemForwarding). Console: Settings → Audit log; permissionsaudit.view,audit.export. - Software supply chain (docs/supply-chain.md): an SBOM (SPDX and CycloneDX), a vulnerability scan (also with an offline database) and a cosign signature with the region's key for every image RunR builds or receives; a policy gate before deploy, re-scans of deployed images and signed-image admission in the region. WebAssembly functions (Spin apps) are signed and verified like images; they get no SBOM or scan, and the gate does not require one of them. Permissions
security.view,security.override_policy,regions.supply_chain.manage; Helm valuessupplyChain.*. - Data residency: organizations limited to allowed regions and data locations, enforced on projects, deploys, bindings, model services, uploads and pushes (permission
tenants.residency.manage, multi-tenant edition).
Operations#
- Observability (docs/observability.md): an OpenTelemetry Collector and Grafana Tempo per region (or an existing backend), auto-instrumentation of functions, a Traces tab with search and waterfall, request replay of captured failed requests with a response diff, uptime checks run by the region agent, a public status page per organization, and alert channels and rules. Permissions
traces.view,replay.manage,uptime.manage,alerts.manage,status_page.manage,regions.observability.manage; Helm valuesobservability.*. - White-label (docs/white-label.md): the installation's brand (name, logos, colours, login texts, e-mails) without a rebuild and, in the multi-tenant edition, per tenant; self-service signup with e-mail verification, a domain allowlist, invite-only mode, CAPTCHA and terms, a tenant per signup with an onboarding wizard; optional plans (off by default) with monthly / yearly prices, trial days, limits (CPU, memory, projects, GPUs, invocations per month, code storage) and feature flags. Helm values
branding.*,signup.*,plans.*; permissionsbranding.manage,signup.manage. - Billing (docs/billing.md, multi-tenant): RunR calculates what each tenant owes and leaves invoicing and payment to the operator's billing system. Charges per tenant and calendar month — metered usage priced with the cost configuration (functions, model services, bindings, sandboxes), plan fees, capacity deals and one-off charges — close after the month with final line items, the credit they draw and the amount due. The billing API (
/api/billing/…) reads usage of any period, charges, plans and tenants, changes a tenant's plan, records purchases made elsewhere with their reference (credit, prepaid plan terms, add-ons that raise plan limits) and stores the billing system's reference on charges; signed billing events (billing.period.closed,billing.plan.changed,billing.usage.threshold,billing.charge.voided) go to a webhook with retries and stay readable in an event log. Platform administrators connect a system with an API token limited tobilling.vieworbilling.manage; tenants see their plan, limits, charges, credit and purchases in the console and a link to the operator's store. Helm valuesbilling.webhookUrl,billing.webhookSecret,billing.usageThresholds,billing.storeUrl. - Scheduled lifecycle tasks (docs/monitoring.md):
close-stale-sessions(every ten minutes) closes deployment sessions open for more than 30 minutes without a reported end, in the central schema and every tenant schema, and the pod's real end later corrects a session closed too early;capacity-deal-expiry(hourly, multi-tenant) sets active capacity deals past their end date toexpired. - Tests and CI: Vitest and Playwright suites for the console, ESLint, Pint and Larastan for the API, Go tests for the agent, the queue consumer, the wrapper, the CLI, the Terraform provider and the Sentry tunnel, and end-to-end suites on kind (
runtime/ci.sh test-*), run as quality, security and e2e gates in GitLab CI and GitHub Actions; ESLint fails on warnings, and the API and console tests on coverage below their floor (docs/development.md).
Changed#
- Documentation lives in
docs/in English, with an index (docs/README.md) and an architecture overview; MITLICENSE,CONTRIBUTING.md,SECURITY.md,CODE_OF_CONDUCT.md, issue and merge request templates; English comments in the Helmvalues.yaml. - The console, page title, favicon, API docs and image labels carry the RunR name and wordmark.
- API tokens carry permissions of the catalog instead of the 0.4.x scopes (see Upgrade notes).
- The development Compose network is
runr_default. - API caching (docs/architecture.md): the trace endpoints answer
Cache-Control: no-storewithout anETag, so neither the response cache nor a proxy keeps a trace that is still receiving spans; the public status page (public, max-age=30) andGET /api/branding(public, max-age=60) keep their directive next to theETag, so a proxy or CDN may cache them. - Code and file contents (editor saves, runtime files, sandbox file writes, MCP tool arguments) are no longer trimmed by the API: they are stored byte for byte.
- Audit events list the added, removed and changed environment variable names in
metadata.changes[].env_vars, never values (docs/security.md). PUT /api/tenants/{tenant}/security-policydrops repeated region ids and locations instead of refusing the request.- The API test suite sets
memory_limit=1GandAPP_URL=http://localhostitself (api/phpunit.xml), whatever the.envsays. - Text files are stored and checked out with LF on every platform (root
.gitattributes). - The region Traefik runs its image from RunR's mirror (
ghcr.io/runrsh/reqs/traefik:v3.7.4) instead of Docker Hub, like RunR's other third-party images;region:reconcilemoves existing regions running the same Traefik version, andknative.traefikImage(KNATIVE_TRAEFIK_IMAGE) names another registry (docs/upgrade.md). The Bash and Rust example runtimes use the mirroredbash:5.2andrust:1.83-alpine.
Security#
- Cached API responses are served only to the user they were cached for; an unauthenticated request no longer receives a cached project list.
- Every WebSocket channel is private and authorized with the user's permissions; a user's notifications go to their own channel and internal ticket notes to a ticket-manager channel.
- SSO callbacks (OAuth, OIDC, SAML) redirect with a one-time code instead of the API token in the URL.
- Broadcasts mask environment variable values and secret fields; version and deployment broadcasts carry no environment variables; the git snapshot of a version lists environment variable names only; queue payloads with kubeconfigs or credentials and the shell session handoff are encrypted; the SSO client secret and linked-account OAuth tokens are never returned by the API.
- The queue consumer reads the Redis address and password from the Secret
<slug>-qc-redis, and the signing key of a workflows project and the queue bridge's sink URL reach the workload from Kubernetes Secrets, instead of plain values in the spec. - Tenant invitation tokens are stored as SHA-256 hashes; links already sent keep working.
- A tenant's copy of a platform region no longer double-encrypts the Redis and registry passwords.
- The development Sentry tunnel (
proxy/) forwards envelopes only to the host of a configured DSN; without one it refuses them instead of relaying them to whatever host they name. - Dependency updates for published advisories: the API's Composer packages (Laravel 12.70 with Horizon 5.50, whose workers declare the
queue:workoptions Laravel 12.70 added, Guzzle, Symfony 7.4, league/commonmark, phpseclib and others:composer auditreports none),golang.org/x/netand gRPC in the region agent and the Terraform provider, Go 1.26 for the agent, queue consumer, wrapper and test images, and Pillow, FastAPI, Starlette, transformers, huggingface_hub, safetensors and diffusers inhf-serve.TRUSTED_PROXIES=private_rangeskeeps meaning the private, loopback and link-local networks: newer Symfony releases add publicly routable prefixes to their private-subnet list, which RunR no longer follows. - An IPv4-mapped peer address (
::ffff:a.b.c.d, how dual-stack listeners such as Octane / FrankenPHP report IPv4 clients) is matched againstTRUSTED_PROXIESas its IPv4 address: a mapped private peer is a trusted proxy, a mapped public peer is the client, and itsX-Forwarded-For,-Protoand-Hostare not used. - The notification list, its unread count, marking read and deleting cover the signed-in user's own notifications and the shared ones only; a member no longer sees or clears another user's (the result of a job someone else started, each admin's copy of a ticket notification).
- Revoking a session that is not the user's answers 404 and no longer signs out the console that holds that token.
Fixed#
An offline model copy whose Job waits for a model volume that cannot be bound (for example a
ReadWriteManyclaim the local-path provisioner cannot provision) shows Pending with the reason from the claim's events instead of Copying with no message, and so do the offline services waiting for it; the region's model volume status gives the same reason.Dragonball (Kata runtime-rs) guests get 1 vCPU + the pod's CPU limits like Cloud Hypervisor and QEMU guests; with kata-deploy 3.28 they had 1 vCPU whatever the limit. Every workload RunR writes on Dragonball carries the vCPU count as Kata pod annotations, which Dragonball's drop-in enables on containerd nodes (docs/kata-hypervisors.md); existing Dragonball functions get it on their next deploy.
The login page no longer reloads itself endlessly: the console subscribed to the (now private) broadcast channels while signed out, the 401 of the channel authorization sent the browser to the login page again, and the API's rate limit started answering 429. The console subscribes once a session exists.
Installing Knative Serving on a region whose Traefik trusts no proxy (no
ingress_trusted_ips, the default) no longer fails at the Traefik release withports.web.proxyProtocol.trustedIPs: Invalid type. Expected: array, given: object.A build or readiness check that outruns its own 30 s limit (a slow region API server) checks again instead of marking the build or the deployment failed; the overall timeouts are unchanged.
A change the console hears about live (saved code, settings, deployment status, notifications, support tickets) no longer answers 500 while the WebSocket server (Reverb) is unreachable: the change was saved all the same, and a client that retried saved it twice. The failed broadcast is reported in the log.
Migrating a region to a new cluster reinstalls the region's managed Redis there.
A plain HTTP request on the shell or agent WebSocket route is answered with 400 instead of failing.
runr:patch-cluster-observabilitywaits for the Cilium compatibility DaemonSet instead of logging a failure.Log search highlights queries that contain
<,>or&; a container command with repeated or surrounding spaces no longer sends empty arguments.On SQLite (the Valet development setup) the usage aggregation added another usage record of the day on every run instead of updating it, and billing summed them.
Under the Turkish locale, upper-cased labels turned the i of English terms into İ (KNATİVE BÖLGELERİ, BASE DOMAİN, BELLEK (MİB)); the terms keep English capitals now.
The development Sentry tunnel no longer crashes, at every start, on a queued envelope whose DSN has no http(s) scheme; it pauses between retries while Sentry answers 5xx, refuses envelopes over 10 MiB (413) instead of forwarding them cut off, and its health check fails while Redis is down.
Removed#
- Leftover AI screens (DAG visualizer, GPU metrics, agent observability, blueprint editor) and dead code paths of the AI project types retired in 0.4.0; their rows keep a read-only detail page with delete. A deployment of a retired type fails instead of being marked active, and region migrations skip them.
- The duplicate Node.js Sentry tunnel (the Go implementation in
proxy/ships). - The unused setting
KNATIVE_MIN_K8S_MAJOR(Helmknative.minK8sMajor).
[0.4.4] - 2026-10-01#
Added#
- Hardware virtualization check for Kata: a short-lived, unprivileged DaemonSet (non-root, no capabilities, the node's
/devread-only) checks every Kata target node for the CPU'svmx/svmflag and/dev/kvmor the KVM module, and records the result as the node labelrunr.dev/kvm=true|falseand the annotationrunr.dev/kvm-probe. Node Feature Discoverycpu-cpuidlabels are used for nodes RunR has not checked; new nodes are checked in the background. - The console's Kata card shows hardware virtualization on the region's nodes: without a KVM-capable node Kata is shown as unavailable (no Install button, no hypervisor list, not offered in the project-type mapping) and an existing Kata install gets a warning to move its mappings to gVisor or runc; on mixed clusters the card names the nodes Kata pods run on.
Changed#
- Every Kata RuntimeClass also selects
runr.dev/kvm=true, so on mixed clusters Kata pods are only scheduled on nodes that can start them.region:reconcileupdates the nodeSelector of existing RuntimeClasses in place (no restart) and prints a warning for Kata installs on regions without KVM; they are never uninstalled automatically.
Fixed#
- Kata no longer installs "successfully" on nodes that cannot run virtual machines (for example cloud VMs without nested virtualization), where its pods never started: the install API returns 422 for Kata when no target node has KVM, gVisor and runc are unaffected, and a working Kata install stays ready.
[0.4.3] - 2026-10-01#
Fixed#
- Adding a hypervisor (for example Firecracker) to a ready Kata install no longer breaks it when the addition fails before the nodes' Kata configuration or the RuntimeClasses change: the region stays ready with its previous hypervisors and the
kata-*mappings keep working; the install log ends with the error, the job completes with a warning and the console shows it on the ready card. - Firecracker's devmapper setup no longer takes a stale pod's result from an earlier install for its own: the previous
kata-devmapperDaemonSet is deleted with its pods first, and RunR only waits on pods of the current DaemonSet and pod template. - Installing Firecracker again after a rollback or cleanup sets up the devmapper thin pool again instead of failing with "thin-pool runr-thinpool is not active":
runr-devmapper.serviceis stopped and reset before it is removed, and the install restarts it.
[0.4.2] - 2026-10-01#
Changed#
- queue-proxy gets a 1-CPU limit over its 100m request on every Knative Service RunR renders (all project types and triggers, runc, gVisor and Kata), so it can use idle node CPU while a pod starts and under load. Project pods become Burstable instead of Guaranteed; admission, tenant quotas, capacity checks and the organization ceiling count requests only. Revisions from before the upgrade get the limit on their next deploy.
- Kata guests on every hypervisor boot the Kata agent as init instead of systemd; the image's
sysctl.dsettings andRLIMIT_NOFILE1048576 are still applied, and Dragonball gets the cgroup v2 kernel parameters its guest needs. - A change to RunR's Kata configuration drop-ins is written on every node in place by a short-lived DaemonSet instead of running kata-deploy again, so it no longer restarts the container runtime; 0.4.1 installs are recognised and updated without a restart.
- The organization ceiling's ResourceQuota limits
requests.cpuonly (thelimits.cpuentry is dropped);region:reconcileupdates existing regions.
Fixed#
- Region capacity admission checks the effective size (the value sent, else the stored one, else the service type's default) on create, update, deploy, scaling and trigger changes and region moves; projects created or deployed without an explicit size skipped the check before. The region demo checks that all four demo projects fit before creating any of them.
- The Kata and gVisor install log is kept after the install, with the result appended as its last line; the console shows the log of a ready runtime in a collapsed "Install log" section and the whole log on an error.
- Firecracker's devmapper check accepts containerd 2.x config dumps. A failed setup no longer crash-loops its DaemonSet: the error is shown in the region's Kata status, and the thin pool, unit and containerd config of a first setup are removed again without a restart.
- A failed live-progress broadcast (for example while the WebSocket server restarts) no longer fails sandbox installs and other jobs; the install log and the job result are stored either way.
region:reconcileHelm upgrades pin the deployed chart version, read fromhelm list, and leave a release alone when that version cannot be determined, instead of upgrading to the newest chart in the repository.- Single-tenant installs no longer run the Tenancy module's migrations, which logged
Database connection [central] not configuredon every migrate.
[0.4.1] - 2026-09-30#
Added#
- Firecracker (
fc) as a Kata hypervisor: RunR sets up containerd's devmapper snapshotter on the nodes (sparse thin pool under/var/lib/runr/devmapper, boot unit, containerd config) before kata-deploy runs. The Kata card has a thin-pool size field (devmapper_pool_gb), and all four hypervisors (clh,qemu,dragonball,fc) can be installed together. The card states that installing or uninstalling Kata restarts the container runtime on every node and asks for confirmation. region:reconcilebrings existing Kata installs to the current configuration: RuntimeClass-only changes are applied in place, an installer change queues the region's Kata install once and reports the container runtime restarts it causes.
Changed#
- Kata installs: a region is ready only after kata-deploy has finished on every target node (20-minute timeout naming the pending nodes); RunR's runtimes keep the shim, VMM and virtiofsd inside the pod's cgroup with fixed VM sizing; the
kata-<hypervisor>RuntimeClasses declare their pod overhead; unknown hypervisors are rejected (422); uninstall is refused while pods run with a Kata RuntimeClass; node detection covers every node, including dedicated workers. - Projects mapped to a Kata runtime reserve that RuntimeClass's pod overhead per pod in tenant quota admission, region capacity validation and usage reporting.
- Region capacity is node allocatable minus the requests of platform pods, so admission and tenant quotas no longer count CPU and memory that system pods already request; build Job pods count with their project.
- On regions with the internal registry, builds pull their base images through it: the registry also caches
ghcr.io(region:reconcileupdates existing registries). Kaniko is pinned tov1.28.5. - Python runtime images ship precompiled bytecode for the standard library, so a cold start does not compile the modules it imports; the PyTorch and TensorFlow image builds are fixed.
- Queue workers block on Redis instead of sleeping between polls, and build and deployment readiness checks start 1 s after submit and repeat every 2 s (previously 5 s).
Fixed#
- Function dependencies are installed in the executor image the function runs on, so native npm modules, compiled wheels and bytecode match its interpreter; Composer resolves PHP packages for the runtime's PHP version.
- Log retention deletes from the region's own Loki (RunR-managed or reused, also for remote regions) through the Kubernetes API proxy and logs refused deletes; a RunR-managed Loki is configured to accept deletes (
region:reconcileapplies it, restarting Loki once), andkeep_last_n_deploymentsprotects the logs of the newest deployments.
Security#
- The region Traefik trusts PROXY protocol and
X-Forwarded-*headers only from the new region settingingress_trusted_ips(default: nobody); before, any client could set the client IP seen by functions, the gateway rate limit and the API.region:reconcileupdates existing installs. - The API takes the client IP from known proxy hops (
TRUSTED_PROXIES,TRUSTED_PROXY_HOPS; chart valuesclientIp.trustedProxiesandclientIp.proxyHops) instead of trusting every proxy, so a spoofedX-Forwarded-Forchanges neither the client IP nor its rate-limit bucket.
[0.4.0] - 2026-09-30#
Added#
- SAML 2.0 single sign-on: SP-initiated login with SP metadata, login and ACS endpoints (also per tenant), optionally signed AuthnRequests and strict response checks (InResponseTo, single use, destination, audience, issuer, validity window, signed assertion). Configured under Auth Providers; the login page shows a SAML button.
- Custom domains for HTTP-triggered projects: ownership is verified with a TXT record and the routing record is checked; TLS per domain is the region's default certificate, an uploaded PEM pair or a cert-manager certificate from the region's ClusterIssuer. A Domains tab on the project page lists the records to create.
- Private connections from functions to Managed PostgreSQL through Skupper v2 (detected on the region, never installed by RunR): credentials are kept only in a Secret and exposed as
DATABASE_URLandPG*environment variables; new "Private connections" project tab. - API rate limiting per tenant, per user and per IP for unauthenticated calls, plus a login limiter, with JSON 429 responses and
Retry-After/X-RateLimit-*headers (API_RATE_LIMIT_*,AUTH_RATE_LIMIT_PER_MINUTE); shared gateways can set an optional Traefik rate limit. - External billing integration (chart values
billing.gatewayUrl,billing.gatewayToken,billing.webhookSecret; off when empty): closed periods are invoiced through the billing gateway, payment status arrives through a signed webhook, and admins can refund; tenants see their payments and invoices in the console. - Organization resource ceiling: chart values
quota.cpuandquota.memoryGiBbecome a ResourceQuota and LimitRange in each region's project namespace; tenant quotas on a region cannot add up to more than the region capacity or the ceiling. region:reconcile [--region=] [--dry-run], run by the Helm bootstrap job on every upgrade (time-limited, never blocks the upgrade), brings existing regions to the release's region configuration: Knative revision GC and deployment settings, removal of the old Prometheus NodePort, organization ceiling.
Changed#
- Instance sizes: six reference sizes (nano to xlarge) are offered as presets when creating, editing and cloning projects; new XaaS and FaaS projects default to micro (250m / 256 MiB), CaaS to small (500m / 512 MiB). A project reserves its size × max(1,
min_scale) plus queue-proxy (now 100m), its queue consumer or schedule pod, with one formula for quota admission, region capacity and usage; existing projects are grandfathered. - Tenant deletion runs as a queued job (202): it tears down the tenant's projects, domains, labelled workloads and the agent of tenant-owned regions, and drops the database only when every teardown succeeded (or with
force). The tenant list shows "Deleting…" and "Deletion failed" with Retry and Force delete actions; writes to a tenant being deleted return 409.
Removed#
- The AI project types (AaaS, AgentFlow, MaaS) can no longer be created, changed or deployed; existing rows still list and can be deleted. Tenant plan labels are removed from the API, the CLI and the console.
Fixed#
- Registry retention keeps the newest ready builds plus every image still in use (active deployments, live Knative revisions, CronJobs, queue consumers); pruned versions are marked "pruned" and deploying one returns 422.
- Tenant-owned regions store the monitoring and function-TLS settings introduced in 0.3.9, so Prometheus, Loki and cAdvisor are installed on them like on platform regions; optional Prometheus scrape resources no longer fail a Serving install.
Security#
- Platform regions are read-only for tenant users: region-changing endpoints (including Skupper site setup) and the kubeconfig download require a platform admin, or the owner or admin role on a tenant-owned region. Reused Prometheus and Loki URLs must be in-cluster Services of the region's cluster, are read through the region's Kubernetes API proxy, and only platform admins or the tenant owner may set them; the region setup wizard accepts them together with a Loki tenant ID (
X-Scope-OrgID). - Function-domain TLS checks that the certificate matches the key, is unexpired and covers
*.<function domain>; it refuses a Traefik TLSStoredefaultin another namespace and replaces one RunR did not create only on explicit opt-in (a checkbox in the console). Console mode reads the console TLS Secret of the active ingress TLS mode. - The
runr-apiServiceAccount keeps only the two reads it performs (thekube-systemnamespace and the console TLS Secret), and pods that need no token no longer mount one. The generated admin password is no longer written to logs, anddeploy.shinstalls get a generated one.
[0.3.9] - 2026-09-30#
Added#
- Regions can reuse an existing Prometheus and/or Loki instead of installing RunR's monitoring stack: a
prometheus_urlorloki_urlgiven at region creation marks that component as unmanaged. Existing regions keep their RunR-managed stack. - Function-domain TLS per region (
PUT /api/knative-regions/{id}/function-tls): upload a certificate and key for the region's function hosts, served through the region Traefik's default TLSStore with the key kept only in a Kubernetes Secret, or reuse the console certificate when the region runs on the console's cluster. Regions that never use it are unchanged. - Build image retention for the internal registry: after each successful build the newest
registry.retain_versions(default 10) build tags are kept, and a deleted project's images are removed. External registries are never touched.
Fixed#
- Deleting a tenant tears down every project's workloads (Knative Services, CronJobs, queue consumers) and storage before the tenant database is dropped.
Security#
- The control-plane ClusterRole is narrowed to the Knative resource kinds RunR uses (
servicesinserving.knative.devandknativeservings), without wildcards or grants on Knative's internal API groups.
[0.3.8] - 2026-09-30#
Changed#
- Knative Serving keeps a bounded number of non-active revisions (time-based retention with a hard ceiling) instead of every revision.
- queue-proxy runs with CPU and memory request equal to limit, so project pods get Guaranteed QoS; quota and capacity accounting include this per-pod overhead.
Fixed#
- The max scale field in the console accepts values up to 1000, as the API does.
- The region demo's contact form shows the backend's message instead of a generic "Sent!".
Security#
- Prometheus is no longer exposed through an unauthenticated NodePort (30090); RunR reads it in-cluster or through the Kubernetes API proxy and removes the old NodePort Service.
- No default
admin123administrator password: whenADMIN_PASSWORDis unset, a random password is generated and printed once.
[0.3.7] - 2026-09-29#
Fixed#
- Removing the region demo inside a tenant deletes the demo projects instead of failing with an SQL error.
- Seeding the region demo is refused while the region's demo projects still exist, so the demo is never created twice.
[0.3.6] - 2026-09-29#
Added#
- Console TLS modes in the chart (
ingress.tls.mode):letsencrypt(cert-manager ClusterIssuer, defaultletsencrypt-prod),custom(uploaded certificate and key, validated at install) andnone(the ingress controller's default certificate). An empty mode keeps the previous behaviour, also withhelm upgrade --reuse-values. - Chart values
storage.applicationandstorage.databasefor the API and PostgreSQL volume sizes, used by the Advanced settings of marketplace install forms.
Changed#
- New regions get a managed Redis by default, so queue triggers and the region demo work right away;
redis_mode(managed,in_cluster,externalornone) withredis_addressandredis_passwordselects another setup.
Fixed#
- Saving the files of a FaaS project whose handler is not
main.*no longer fails with "Main file (main.php) cannot be deleted". - The region demo's frontend calls its backend through the in-cluster service address (no public DNS or publicly trusted certificate needed), and all demo components use the tenant-aware queue stream name.
[0.3.5] - 2026-09-29#
Added#
- The queue consumer retries failed deliveries with exponential backoff (
RETRY_BACKOFF,RETRY_BACKOFF_MAX), moves a message to a dead-letter stream (DLQ_STREAM, default<stream>-dlq) afterMAX_RETRIES(default 5) attempts, claims the pending messages of restarted consumers and reports its counters onGET /stats.
Changed#
- Project, code and deploy input is validated against Kubernetes limits with clear messages: project name (DNS label of at most 44 characters, unique), cron syntax, environment variable names and size, timeout up to the region's request timeout, CPU quantity and code size (below 1 MB); the console shows the same hints. Deploy-time
cpu,memory_mbandtimeout_secondsoverrides are applied to the workload.
Fixed#
- gVisor installs the sidecar binaries (
gvisor-bin/) that current runsc releases require; gVisor pods failed with "sidecar gvisor_sentry not usable" before. Earlier installs are completed on the next install run. - Uninstalling gVisor, or a failed first install, cleans the nodes: RunR's runsc configuration is removed from containerd (checked with
containerd config dumpbefore the file is replaced), the runtime is restarted only where needed and the runsc binaries are removed; uninstall is refused while gVisor pods run. - Tenant projects on a platform region run with the sandbox runtime the region maps to their project type.
- Deleting a project removes everything it runs (Knative Service, CronJob, queue consumer, code ConfigMaps) whatever its deployment status; a redeploy only removes what the new deployment does not reuse.
- Queued jobs no longer end in unresolved states: jobs run once unless they are safe to repeat, long jobs are not handed to a second worker, and failed deploys, builds and readiness checks mark the deployment or build as failed with the reason.
- Usage metering works without a cost config (zero cost in
BILLING_DEFAULT_CURRENCY, default USD), and one tenant's failure no longer stops the others. - A scheduled task interrupted by a pod restart runs again once its overlap lock expires, after 10 to 120 minutes depending on its frequency, instead of 24 hours.
[0.3.4] - 2026-09-29#
Changed#
registry:rolling-restartrestarts containerd in place (no eviction) when no target node can be drained, for example when RunR runs on every node or the cluster has a single schedulable node; when only some nodes were skipped it prints the--in-placecommand for them.
Fixed#
- The in-cluster agent URL uses the gateway port again; the Kubernetes service-link variable
RUNR_GATEWAY_PORThad turned it into port 0. The settings are nowRUNR_AGENT_GATEWAY_SERVICEandRUNR_AGENT_GATEWAY_PORT. - gVisor installs download the sha512-verified release tarball, as upstream no longer publishes the individual binaries; the installer is ready only after runsc is loaded, and a failed install removes the
gvisorRuntimeClass again. - Each deploy is applied once: the deployment listeners were registered twice, which created an extra Knative revision per deploy and ran teardowns twice.
- Region capacity comes from the schedulable nodes when no dedicated workers are selected, so tenant quotas can be set on such regions; the Serving version is read from the KnativeServing CR for manual and adopted installs.
- Helm install and upgrade run pending tenant migrations in the multi-tenant edition; a failure stops the upgrade before new code runs on an old tenant schema.
[0.3.3] - 2026-09-29#
Added#
imagePullSecretsFromDefaultServiceAccount(on by default): every pod inherits the pull secrets attached to the install namespace'sdefaultServiceAccount, as marketplace installers set them up, also on--reuse-valuesupgrades.imagePullSecretsaccepts names or{name: …}entries and also applies to PostgreSQL and Redis.- Agent connections without public DNS: the agent uses the region setting
agent_ws_url, else the in-cluster gateway when the region is RunR's own cluster, elseRUNR_AGENT_WS_URL, elseAPP_URL. - gVisor and Kata installers support containerd 2.x and k3s / RKE2 (configuration drop-ins or templates, restarting the k3s / RKE2 unit); CRI-O and mixed runtimes are refused up front, and a node's error becomes the region's sandbox status message.
Changed#
registry:rolling-restartnever drains RunR's own node or the only node of a single-node cluster (--in-placerestarts containerd there without cordon or drain); k3s and RKE2 need no restart.- The region Traefik chart is pinned to 40.3.0. When another controller owns the
traefikIngressClass, the region Traefik installs without one, and uninstalling Serving keeps a Traefik that still serves other Ingresses (remove_traefik: trueremoves it anyway).
Fixed#
- The registry mirror works on containerd 2.x and k3s / RKE2 without producing an invalid containerd config (existing duplicate keys are repaired); the Zot chart is pinned and its PVC size and storage class are honoured; cAdvisor uses the distribution's containerd socket.
- Serving counts as ready only with a ready KnativeServing CR;
GET /api/knative-regions/{id}no longer returns 500; failed Knative installs record their error status and log.
Security#
- Each region's agent authenticates with its own random token, of which only the SHA-256 hash is stored; requests without a token or with an unknown or wrong one are rejected. Agents deployed before 0.3.3 are redeployed with
php artisan agent:redeploy, which the chart's bootstrap hook runs on upgrade. - Production installs of the multi-tenant edition no longer seed a test login and demo tenants; the bootstrap hook creates the configured admin unless an administrator already exists.
[0.3.2] - 2026-09-28#
Chart-only release; the application images stay at 0.3.0.
Fixed#
- Marketplace installs create the administrator from the install form: a new
runr-bootstrappost-install / post-upgrade hook seeds it while the users table is empty and changes nothing once any user exists (logins failed with 401 before).bootstrap.enabled: falseturns it off.
[0.3.1] - 2026-09-28#
Chart-only release; the application images stay at 0.3.0.
Changed#
- The
fix-permissionsandfix-pgdatainit containers useghcr.io/runrsh/reqs/busyboxinstead of pullingbusyboxfrom Docker Hub; configurable withinitImage.repositoryandinitImage.tag.
[0.3.0] - 2026-09-28#
Initial tagged release.
Added#
- Projects on Knative Serving in three types: Code Snippet (XaaS, a single file), Code Project (FaaS, multiple files with a web IDE and platform-internal Git versioning) and Container (CaaS, any container image).
- HTTP, schedule (cron) and Redis-stream queue triggers, with scale to zero and configurable minimum and maximum scale.
- Runtimes for Python, Node.js, Go and PHP, plus PyTorch and TensorFlow images; builds run as Kaniko Jobs, and interpreted code without dependencies deploys straight from a ConfigMap.
- Every saved change creates a Version, and any Version can be deployed; deployments have logs, live metrics and a browser shell.
- Regions: connect a Kubernetes cluster by kubeconfig; RunR installs the Knative Operator and Serving, Traefik, Prometheus, Loki and Redis, can place projects on dedicated workers, and moves a region to a new cluster (cluster migration with rollback).
- An internal Zot registry per region (PVC or S3 storage) with a node registry mirror, or an external registry.
- Sandbox runtimes mapped per project type and region: runc by default, gVisor or Kata Containers.
- A runr-agent per region reports metrics, sessions and events; the console updates live over WebSockets.
- Multi-tenant edition: tenants with their own database schema, invitations, per-region quotas, usage metering and cost configuration.
- Accounts with two-factor authentication, session management and API tokens with permissions; sign-in through Google, GitHub, Microsoft, GitLab and OIDC providers.
- API gateways with routes, in-app notifications and support tickets.
- Helm chart with a single-tenant / multi-tenant edition, an install form (
values.schema.json), generated secrets and a Traefik ingress; a Docker Compose setup for development.