A minimal, modular Function-as-a-Service control plane.

NanoFaaS registers, deploys and invokes containerized functions on Kubernetes or on a single host. The control plane is one reactive Java service, and everything beyond invocation is a module you choose at build time.

  • Java 25
  • GraalVM native
  • Kubernetes
  • Docker
  • containerd/crun
  • MIT License

Latest updates

All posts →
  • NanoFaaS 0.23.0: the first release candidate

    Our first release candidate: functions on rootless containerd, schedulers you can swap on a running system, native builds from any laptop and a Rust SDK. P2P discovery arrives in beta.

Only what you need, nothing more

A build contains only the modules it needs. Queueing, autoscaling, concurrency control, hot reconfiguration, offloading and each deployment backend are opt-in.

  • Build-time modularity

    Twelve optional modules with declared defaults, requirements and conflicts, validated before a single task runs.

  • One API, three execution modes

    Managed deployments, passthrough to external endpoints and in-process execution share the same invocation endpoints.

  • Interchangeable backends

    Kubernetes, a local Docker-compatible runtime, or rootless containerd/crun.

  • Concurrency control with SLOs

    Per-function limits that are static, adapt to latency, or share a platform-wide budget with weighted max-min fairness.

  • Native builds

    The control plane, CLI and Java example functions compile to GraalVM native executables; services ship on Distroless images.

  • Any language

    Functions are HTTP services behind a small contract, with SDKs for Java, Python, Go, JavaScript and Rust.

  • Reproducible distributions

    One versioned YAML recipe builds your own control plane and its functions, images included.

Quick start

You need Java 25; if it is missing, Gradle downloads a JDK. Clone the repository and open three terminals.

  1. Start the control plane

    ./gradlew :control-plane:bootRun
  2. Start an example function on port 9090

    ./gradlew :functions:java:word-stats:bootRun --args='--server.port=9090'
  3. Build the CLI, register the function and invoke it

    ./gradlew :nanofaas-cli:installDist
    alias nanofaas="$PWD/clients/cli/build/install/nanofaas-cli/bin/nanofaas-cli"
    
    cat > word-stats.yaml <<'EOF'
    name: word-stats
    image: nanofaas/java-word-stats
    executionMode: EXTERNAL
    endpointUrl: http://localhost:9090/invoke
    EOF
    
    nanofaas fn apply -f word-stats.yaml
    nanofaas invoke word-stats -d '{"text": "to be or not to be", "topN": 2}'

    The response:

    {"executionId":"cf9360a6-…","status":"success","output":{"averageWordLength":2.17,"wordCount":6,"topWords":[{"count":2,"word":"be"},{"count":2,"word":"to"}],"uniqueWords":4},…}

Asynchronous invocation

Enqueue a call and get an execution ID straight away.

nanofaas enqueue word-stats -d '{"text": "hello world"}'
nanofaas exec get <executionId>

Plain HTTP

The CLI wraps the HTTP API, which you can also call directly.

curl -X POST http://localhost:8080/v1/functions/word-stats:invoke \
  -H 'Content-Type: application/json' -d '{"input": {"text": "hello"}}'

Next: run functions on Kubernetes, or write your own function.

Architecture

One control plane with a small core. Optional modules plug in around it, and a deployment provider runs the functions it manages.

NanoFaaS architecture Clients call the control plane core over HTTP on port 8080. The core works with optional modules and a deployment provider. The provider runs managed instances; the core forwards external calls to an external endpoint. Client · SDK · CLI HTTP :8080 Control plane Optional modules queues · autoscaler · concurrency · offload Core registry · dispatch · execution store Deployment provider Kubernetes · Docker · containerd/crun External endpoint EXTERNAL mode Managed instances DEPLOYMENT mode

A synchronous call (POST /v1/functions/{name}:invoke) is validated, rate-limited and checked for idempotency, then recorded in the execution store. It goes through sync-queue if that module is present, otherwise through async-queue, otherwise straight to the function. Metrics and health are served on port 8081.

Execution modeBehaviour
DEPLOYMENTThe control plane owns the function's instances through a deployment backend.
EXTERNALThe function runs elsewhere; invocations are forwarded to its endpointUrl.
LOCALIn-process execution, for testing.

Modules

Twelve modules around a small core, picked at build time. Each one declares its defaults, requirements and conflicts, and an invalid selection fails before any task runs.

Queueing and admission

Select both and they plug into one scheduler engine: pick the strategy at startup and switch it at runtime.

  • async-queueDefault

    Per-function queues and a scheduler for asynchronous calls. Without it, :enqueue answers 501 Not Implemented.

  • sync-queueDefault

    Shared queue with admission control and backpressure for synchronous calls, rejecting work on saturation or excessive estimated wait.

Scaling and concurrency

  • autoscalerDefault

    Scales managed replicas from queue depth and in-flight work, within per-function bounds and cooldowns. Replicas still starting are never walked back.

    Needs async-queue or sync-queue

  • concurrency-controlDefault

    Per-function concurrency governor with five policies: FIXED, STATIC_PER_POD, ADAPTIVE_PER_POD, BUDGETED (weighted max-min fair share of a platform budget) and SOJOURN.

    Needs async-queue or sync-queue

Deployment backends

Pick one: the three providers are mutually exclusive. Without a provider, functions run in EXTERNAL or LOCAL mode.

  • k8s-deployment-providerDefault

    Runs managed functions as Kubernetes deployments.

  • container-deployment-providerOptional

    Runs functions as containers on a local Docker-compatible runtime.

  • containerd-deployment-providerOptional

    Runs functions on rootless containerd with crun and CNI networking: no Docker daemon, no root.

Edge and multi-node

  • offloadDefault

    Transparently proxies synchronous calls to a remote NanoFaaS, for example edge to cloud, either always or when the local sync-queue is saturated. The execution stays tracked locally.

  • p2p-discoveryOptional

    Nodes find each other over SWIM gossip, measure latency, choose active neighbours and exchange messages, optionally sharing functions, images and resource usage. Sized for tens to a few hundred nodes.

  • forecastingOptional

    Forecasts each function's external arrival rate, either with an EWMA over fixed time windows or from an uploaded trace.

Operations

  • runtime-configDefault

    Hot reconfiguration through a namespaced admin API. Other modules contribute the parameters they let you change.

  • build-metadataDefault

    Diagnostics endpoint reporting how the running artifact was built, so you can prove which build is serving.

Choose modules

./gradlew :control-plane:bootJar -PcontrolPlaneModules=none
./gradlew :control-plane:bootJar -PcontrolPlaneModules=async-queue,autoscaler
./gradlew :control-plane:bootJar -PcontrolPlaneModules=all

Ship it as your own distribution

To fix the module selection together with build mode, configuration and functions, write a recipe.

Build your own NanoFaaS

A recipe is a versioned YAML file that describes a custom NanoFaaS distribution: which control plane to build, how, and which functions to package with it. One command turns it into artifacts and container images.

  • Exact module set for the control plane, validated before anything is built.
  • JVM or GraalVM native build, with per-component native-image options.
  • Spring configuration baked into the control plane.
  • Functions and services from any SDK, packaged as images next to it.
  • Your registry and tag, with optional multi-architecture images.

Validation and preview need no Docker daemon, GraalVM or registry credentials, and the preview shows exactly what the build will use.

schemaVersion: 2
name: edge-demo

registry:
  repository: ghcr.io/my-org
  tag: "1.0.0"
  platforms: [linux/amd64, linux/arm64]

controlPlane:
  modules: [async-queue, container-deployment-provider, build-metadata]
  build:
    mode: native

functions:
  - name: word-stats
    sdk: java-lite
    build: {mode: native}
    container: {image: word-stats-java-lite}

Four commands

./gradlew listRecipeFunctions                              # functions available to package
./gradlew validateRecipe -Precipe=recipes/edge-demo.yaml   # validate and preview, builds nothing
./gradlew assembleRecipe -Precipe=recipes/edge-demo.yaml   # build artifacts and images
./gradlew publishRecipe  -Precipe=recipes/edge-demo.yaml   # build, then push to your registry

Start from recipes/local-demo.yaml or read the recipe guide.

Write functions in any language

A function is an HTTP service behind a small contract. SDKs make it a few lines; examples ship for every SDK language plus Bash.

  • Java
  • Python
  • Go
  • JavaScript
  • Rust