NanoFaaS 0.23.0: the first release candidate
We just tagged NanoFaaS 0.23.0, and it is a milestone for us: the first version we are comfortable calling a release candidate. It took a month, 410 commits and 29 merged pull requests to get here. From now on, the plan is to stop piling up features and spend our time making the existing ones solid. The one exception is the peer-to-peer work, which is brand new and ships as beta.
Before the tour, a word on what “release candidate” means for us. It is a statement about scope, not about running mission-critical traffic. NanoFaaS is still a single control-plane instance, it keeps executions and queues in memory (only the function catalog is saved to disk), and it has no authentication. Keep it behind your own access control, and expect in-flight work to be lost if the control plane restarts.
Functions on rootless containerd
Until now you had two places to run functions: a Kubernetes cluster, or a single machine with a Docker-compatible runtime. 0.23.0 adds a third: plain containerd, running rootless with crun. If you want functions on one box without a Docker daemon, and without anything running as root, this is the backend for you.
It does what the other two do. It applies CPU and memory limits, waits for a replica to be ready before sending it traffic, scales up and down, and picks up running replicas again after the control plane restarts. You can also point it at registry mirrors, plain-HTTP registries or a custom CA. As with the other backends, you choose it when you build NanoFaaS, and you pick exactly one. The containerd guide walks through the setup.
Change the scheduler without restarting
NanoFaaS has two ways of queueing work: one queue per function, or a single shared queue. In 0.23.0 both run on the same engine, and you can switch between them on a live system. Callers waiting at the moment of the switch still get their answers. That makes it easy to try both under your real workload and keep the one that behaves better.
You choose the starting strategy with nanofaas.scheduler.strategy. To switch later, enable the runtime-config admin API and send a PATCH:
curl -fsS -X PATCH http://localhost:8080/v1/admin/runtime-config/scheduler \
-H 'Content-Type: application/json' \
-d '{"expectedRevision":0,"values":{"strategy":"shared-queue"}}'
Read the current revision with a GET first; a stale one gets a 409 back, so two people cannot overwrite each other’s change. This works when both queue modules are in the build, which is the default. And remember that the admin API has no authentication, which is why it is off unless you turn it on.
While we were in there, retries got politer: they now wait as long as Retry-After asks instead of firing again straight away. A function that has just been deployed also gets no traffic until it is actually ready.
Native builds from any laptop
Recipes are how you describe your own NanoFaaS distribution: which modules, which functions, which images. Version 2 of the format adds three things we kept missing.
- Build native images without installing GraalVM. Set
builder: container, and the native build runs inside the same GraalVM container we use for releases. All you need is Docker, so it works on macOS and Windows too. - Tune the native build per component: optimisation level, garbage collector and monitoring.
- Publish multi-architecture images. List
linux/amd64andlinux/arm64and you get one image for both, with build provenance attached if you ask for it.
schemaVersion: 2
name: demo
registry:
repository: ghcr.io/my-org
tag: "1.0.0"
platforms: [linux/amd64, linux/arm64]
provenance: true
controlPlane:
modules: [async-queue, container-deployment-provider, build-metadata]
build:
mode: native
builder: container
native: {optimization: s}
Your version 1 recipes keep working unchanged. We also fixed a subtle mismatch: a native build now reads the recipe’s configuration, so the JVM and native versions of the same recipe end up with exactly the same components.
Rust, and lighter function images
You can now write functions in Rust. The SDK follows the same contract as the others, fn-init can scaffold a Rust project, and the four example functions come in Rust as well. That brings the list to Java, Python, Go, JavaScript and Rust, plus Bash examples.
We also put the function images on a diet. Smaller images mean faster pulls and quicker cold starts; the Python example is now ready in about 0.8 seconds instead of 1.
| Language | Before | Now |
|---|---|---|
| Go | ~21.5 MB | ~8.9 MB, a static binary on scratch |
Python (word-stats) |
156.6 MB | 72.8 MB on Alpine |
| JavaScript | 165.5 MB (Node 20) | 108.9 MB (Node 24) |
In beta: nodes that cooperate
The newest part of 0.23.0 is aimed at setups with several NanoFaaS nodes, for example at the edge. All of it is off unless you enable it, and its configuration and APIs may still change, which is why it is labelled beta.
p2p-discoverylets nodes find each other, measure the latency between them and pick their nearest neighbours. Neighbours can also tell each other which functions they run, which images they already have and how busy they are. It suits groups of tens to a few hundred nodes. Its README has the details.forecastingestimates how many requests each function will receive next, from the traffic it has seen or from a trace you provide (docs).- One-shot offload brings the two together. Nodes use the forecasts to agree in advance on who prepares which replicas, and each request then runs locally, on a neighbour or in the cloud (docs).
The less visible work
A release candidate has to earn the name, so a large share of this release went into things you will hopefully never notice. Our release checks now stop on any failed or skipped job and on any new static-analysis finding. We cleared all 538 open SonarQube issues across the five languages. We fixed several native-build problems and a control plane that refused to restart when a saved function had explicit resource limits, and timeouts from external functions are now reported as timeouts rather than generic errors.
Before you upgrade
- Python projects need Python 3.12 or newer.
- The JavaScript SDK and functions need Node 24.
- If a synchronous call times out while waiting for its result, the caller now gets HTTP 408 with the execution id and
status=timeout. The execution itself keeps going, and you can still fetch its result. - Errors from the function-management API all share one JSON shape:
{error, message, details?}. - The Helm chart runs exactly one control plane and upgrades it with the
Recreatestrategy.
Give it a spin
git clone --branch v0.23.0 https://github.com/Nanofaas/nanofaas.git
cd nanofaas
./gradlew :control-plane:bootRun
From there, the quick start has you registering and invoking a function in a couple of minutes. The full list of changes is in the diff since 0.22.0.
This is the version we want you to try and to break. If something is confusing, missing or just wrong, open an issue. Reports like that are what will turn this candidate into a release.