# waverless schedules serverless GPU work by pulling tasks, and it is the layer behind wavespeed.ai

> WaveSpeedAI/waverless is a Go system for orchestrating serverless GPU tasks across Kubernetes, Novita and Docker backends, with a RunPod compatible API so existing runpod-python code migrates without changes. It is Apache 2.0 licensed according to the repository metadata, MIT according to the README, and the tags stopped at v1.0.2 in February 2026.

**WaveSpeedAI/waverless** — High-performance serverless GPU task orchestration — the scheduling layer behind wavespeed.ai

- Repository: https://github.com/WaveSpeedAI/waverless
- Website: https://wavespeed.ai
- Stars: 633 · Forks: 68
- Language: Go
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/wavespeedai-waverless

## A scheduling layer, not an inference engine

Waverless is described as a high performance serverless GPU task orchestration system, and the repository description calls it the scheduling layer behind wavespeed.ai. The parent project is an AI image and video generation platform with an image generator and a video generator in the browser.

That position determines what the software does. It does not run the model. It decides where a task should run, on which backend, and it keeps that backend's fleet busy. The result is an HTTP API that accepts a task, plus the machinery to place it, track it and return its result.

The feature list names five things, and two of them carry most of the design weight. The first is a pull based architecture, where workers actively pull tasks for better load balancing. The second is RunPod compatibility, described as zero code migration from the runpod-python SDK. The rest are multi provider support across Kubernetes, Novita Serverless and Docker backends, smart autoscaling driven by queue depth, priority and resource awareness, and graceful shutdown with zero task loss during rolling updates.

The audience is a team already operating GPU inference and now fighting the fragmentation of running it across several providers, or a team that wants to leave a single RunPod compatible interface in place while changing the underlying fleet.

The language is Go, with a module named waverless requiring go 1.24.0, and the dependency list gives the shape of the system: gin for the HTTP API, go-redis and MySQL through gorm for state, the Kubernetes client libraries for one backend, the AWS SDK for another, and gorilla/websocket for the Web UI channel.

## Workers pull tasks, which is the whole scheduling idea

The architecture diagram in the documentation is the clearest statement of how the system works, and it is worth reading in full rather than summarising.

The clients, which are a V1 API client and a Web UI, submit into the core. The core is the Waverless API Server, containing a task queue, worker management, an autoscaler and a store backed by Redis and MySQL. The core talks to a provider layer of Kubernetes, Novita and Docker, and the provider layer manages workers. The arrows at the bottom are the important part: workers pull tasks from the core.

That last arrow is the design decision. In a push model, the scheduler hands a task to a specific worker, which means the scheduler must know enough about every worker to pick correctly, and an idle worker is a worker you paid for. In a pull model, workers ask for work, so a busy worker simply does not ask, and the load balance follows from the pull itself. That is what the feature list means by better load balancing, and it is why the architecture can sit in front of several very different provider backends without a per provider scheduling algorithm.

The flow in the diagram is therefore: submit into the queue, let the core decide which provider has capacity, let that provider's workers pull from the queue, and return the result through the same path. The autoscaler sits inside the core and acts on queue depth, priority and resource awareness, which means it can react to a backlog of work rather than to a fixed fleet size.

The store is worth noting separately, because it is the only stateful part. Redis plus MySQL is a fairly conventional choice, and the presence of both suggests a fast hot path in Redis and durable records in MySQL, though the documentation linked from the repository covers the data flow in more detail than the overview does.

## The API is shaped so runpod-python code keeps working

The compatibility claim is the most consequential thing in the project, and it is stated without much elaboration: RunPod compatible, zero code migration from the runpod-python SDK.

A submit call is a plain HTTP POST with a JSON body, sent to a versioned path that names the endpoint, and a second call fetches the status of a task by its identifier. The local setup section of the overview shows both calls with curl, against a local port.

That shape is what a RunPod compatible client expects, and the reason it matters is organisational rather than technical. A team with a working RunPod integration can put waverless in front of its existing fleet, or move between providers, without the application code changing. The cost moves to the operator: the fleet is now described by waverless's configuration and provider backends rather than by a RunPod endpoint.

The programs directory reinforces that this is a supported path rather than an aspiration. It holds three scripts, a client, a simple client and a worker, so both halves of the contract are demonstrated, the side that submits tasks and the side that executes them.

A Web UI is part of the client side, and the dependency list includes gorilla/websocket, which suggests the UI receives task state updates over a socket rather than polling. The Web UI directory in the repository is a separate top level entry, so it is a distinct front end rather than a set of static assets in the Go binary.

## Running it locally takes a database, a config file and one Go command

The local setup is short enough to run without reading further, and it is split into a local path and a Kubernetes path.

```bash
# Local development
docker-compose up -d mysql redis
go run cmd/main.go

# Kubernetes deployment
./deploy.sh install
```

The first command brings up MySQL and Redis with docker-compose, the second copies the shipped configuration file into place, and the third runs the service directly with go run. The Kubernetes path is a single script invocation, ./deploy.sh install.

Note that the local steps assume a configuration file exists before the service starts, which is why the copy command is not optional. The repository has a config/ directory at the top level, and the docs/ table lists a user guide that covers deployment, an API reference, autoscaling and troubleshooting, plus a developer guide for code structure, core design and provider integration.

The docker-compose file shows the shape of the local dependency set. It defines a redis service on the redis:7-alpine image with port 6379 published, a named volume for its data, and appendonly enabled. It also defines a waverless service built from the repository root, publishing port 8080, with an environment variable pointing at the configuration path inside the container, depending on redis, and mounting the local config directory into the container.

One inconsistency is worth flagging before you follow any of this. The local setup in the overview runs the service with go run cmd/main.go, while the Makefile target for running the service uses go run ./cmd, and the Dockerfile builds the binary with CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o waverless ./cmd. Three slightly different invocations for the same entry point, which is normal in a young repository but worth knowing when a path fails.

## The Makefile is the map of the development workflow

The Makefile is short and it is the fastest way to understand what the maintainers actually run.

```make
build:
	go build -o waverless ./cmd

# Run service (new architecture)
run:
	go run ./cmd

# Run tests
test:
	go test -v ./...
```

There is a build target that produces a binary named waverless from the ./cmd package, a run target that executes the service through go run, and a test target that runs the whole test suite verbosely. A clean target removes the binary and an output directory, deps downloads and tidies modules, fmt runs go fmt, and lint runs golangci-lint.

Two targets deserve a second look. The dev target sets CONFIG_PATH=config/local.yaml before running the service, which tells you the local configuration is a separate file from the main one and that configuration is selected by environment variable rather than compiled in. And the docker-run target publishes port 8080 and passes REDIS_ADDR pointing at host.docker.internal:6379, which is the standard way to reach a host service from inside a container on a laptop.

The repository also carries a Dockerfile and a Dockerfile.simple, a build.sh and a build-and-push.sh, a migrations/ directory for database schema changes, a k8s/ directory, a scripts/ directory, an internal/ and pkg/ directory split, a cmd/ entry point, an app/ directory, a web-ui/ directory, and a test_worker.py at the root alongside the Go tests.

That layout, with migrations and both a Dockerfile and a Kubernetes directory, is the signature of a service intended to be deployed rather than merely run.

## The image is a static Go binary on Alpine

The Dockerfile is a conventional two stage build, and there is one decision in it that matters for deployment.

The builder stage is golang:1.24-alpine, pinned to the linux/amd64 platform explicitly, with the module files copied first so the dependency download layer is cached, then the source, then the build. The runtime stage is alpine:latest with ca-certificates installed.

The build command is the interesting line. It sets CGO_ENABLED=0, sets GOOS=linux, and passes -a and -installsuffix cgo, producing a static binary with no cgo linkage. That is the right choice for a container image, since a static binary needs no libc in the runtime stage, and the alpine base stays small.

The runtime stage copies the binary, copies the config directory, and exposes port 8080. The comment in the file notes that the specification file specs.yaml and the files beside it are copied from the builder stage, so they are treated as data shipped with the image rather than mounted in production. The compose file, by contrast, mounts a config directory for local development, and the environment variable CONFIG_PATH points at the file inside the container.

One caveat for anyone building on Apple Silicon: both stages are pinned to linux/amd64, so a local docker build on an ARM machine will produce or run an emulated image unless you override the platform, which is a routine but avoidable annoyance.

## Apache 2.0 or MIT: the two sources disagree

The licence situation needs a decision before you vendor this code, because the two authoritative-looking sources in the repository do not match.

The licence field in the repository records Apache-2.0, while the README, in its own License section, says MIT License. Those are different licences with different obligations, and there is no note in the repository explaining which one is authoritative.

The LICENSE file at the repository root is the place to look, and whoever owns licensing in your organisation should read it and settle the question in writing before you depend on the code. This review cannot resolve it, and it would be wrong to guess in a direction that happens to be convenient.

The rest of the maintenance picture is easier to read. The repository is not archived and the last push was on 2026-08-21. The published tags are v1.0.2 from 2026-02-04, v1.0.1 from the same day earlier, and v1.0.0 from 2025-11-14. So the project reached 1.0 in November 2025, shipped two patch releases in early February 2026, and has had no tag since, while commits have continued for about six months.

That is the pattern of a system in production use, where the deployable artefact is the container image rather than a tagged module. For a Go project consumed as a library, though, the absence of a version since February means you would be tracking the main branch, and the dependency list in go.mod is a useful guide to how much of the surface area you would be inheriting.

## Conclusion

Use waverless if you already run serverless GPU inference and want one scheduling layer across Kubernetes, Novita and Docker providers, or if you want to move an existing runpod-python integration across without rewriting it. Do not choose it if you need a documented, versioned API, since the newest tag is v1.0.2 from 2026-02-04 while the last push was on 2026-08-21, and the README and the repository metadata disagree on the licence. Before adopting, resolve the licence question in writing, bring up the stack with docker-compose up -d mysql redis, and submit a test task to the V1 run endpoint to see how a real provider backend behaves in your environment.

## FAQ

### What is waverless?

It is a Go system for orchestrating serverless GPU tasks, described as the scheduling layer behind wavespeed.ai. It places and tracks work across Kubernetes, Novita Serverless and Docker backends using a pull based architecture.

### Can I use my existing runpod-python code with waverless?

The project claims RunPod compatibility with zero code migration from the runpod-python SDK. The API is a submit endpoint that takes a JSON input and a status endpoint that takes a task identifier.

### How do I run waverless locally?

Bring up the dependencies with docker-compose up -d mysql redis, copy the shipped configuration file in config/ to config/config.yaml, then run the service with go run. On Kubernetes the documented path is ./deploy.sh install.

### What does the pull based architecture change about scheduling?

Workers actively pull tasks rather than being handed them, so the load balance follows from the pull and a busy worker does not ask for more work. The autoscaler acts on queue depth, priority and resource awareness.

### Which providers does waverless support?

The feature list names Kubernetes, Novita Serverless and Docker as backends, and the dependency list includes the Kubernetes client libraries and the AWS SDK, with a store backed by Redis and MySQL through gorm.

### What licence is waverless released under?

The sources disagree. The licence field records Apache-2.0 while the README license section says MIT License, and the root LICENSE file is the place to check before you depend on the code.

## Sources

- [License: Apache-2.0](https://github.com/WaveSpeedAI/waverless/blob/main/LICENSE)
- [Project website](https://wavespeed.ai)
- [README](https://github.com/WaveSpeedAI/waverless/blob/main/README.md)
- [Releases](https://github.com/WaveSpeedAI/waverless/releases)
- [WaveSpeedAI/waverless on GitHub](https://github.com/WaveSpeedAI/waverless)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/wavespeedai-waverless
