serverless-agent: serverless, minus the container

A Go worker that unpacks code, drops privileges, clamps memory with cgroups v2, and streams execution logs through Redis.

8 min read · dashn9/serverless-agent

A zip archive drops directly into a branching Linux process tree while a narrow ring locks around one execution branch. A thin stream of log output escapes toward a Redis vault, showing that isolation and observability are part of the same execution path.
The worker treats execution as a process lifecycle, not a container lifecycle.
Key Takeaways

No container starts here

serverless-agent makes a blunt choice: it runs a function as an ordinary Linux process, then uses cgroups v2 and a privilege drop to keep that process in a cage. There is no container runtime in the path, which is exactly the point.

That choice changes the shape of serverless. Instead of paying for images, namespaces, and orchestration overhead, the worker spends its effort on a narrower problem: unpack code, fence it in, run it, and tear it down cleanly.

Why this worker exists

This repo is the node-side executor in a larger Serverless Fabric system. The control plane talks gRPC, the node reads YAML config, uses mTLS, and hands function work to this agent.

The interesting part is not that it can execute code, but that it does so with a small amount of machinery. Redis carries function metadata, execution state, and live log updates back to the rest of the platform, so the agent stays focused on local execution.

What happens when a function lands

One execution path, read three ways: deployment, isolation, and logging.

A deploy request lands as a zip archive. `DeployFunction` unpacks it into the runner directory, fixes ownership and permissions, and prepares it for execution.

`ExecuteFunction` then creates a fresh cgroup v2 node for the invocation, sets a memory ceiling, and launches the handler after dropping from root to the unprivileged `flux-runner` user. If the process misbehaves, the agent kills the process group, removes the cgroup, and writes the final status back out.

Live output is not a side channel. Stdout and stderr are wrapped by an `io.Writer`, streamed into Redis, and turned into execution records with a TTL so the control plane can fetch them later.

A close-up of a heavy key turning a gate closed around a small running process, while the key is already being set aside. The image explains the repo's trust model: privilege exists only long enough to create the boundary, then the handler runs unprivileged inside it.
Root opens the door, then steps back.

Root opens the door, then walks away

This is the repo's sharpest security move. Root is used only long enough to set up boundaries, then execution drops to `flux-runner` before any user code runs.

That split keeps the trusted part of the agent small. The privileged surface is creation and cleanup, not the runtime path itself.

A function emits two streams, stdout and stderr, into a vault-shaped Redis block where execution records stack like index cards. The image shows Redis acting as the platform's memory layer, not just a storage cache.
Redis is the bridge between a transient process and the rest of the platform.

Redis is the memory layer

Redis is doing more than acting as a cache. It is the bridge between a transient local process and a distributed control plane.

The agent stores execution records, function configuration, and live log state there, which means the platform can query what happened without keeping the entire history on the node. That is a practical choice when the node's main job is to run and dispose of work quickly.

Why this beats the usual stack

A split scene contrasts a layered container stack with a single Linux process wrapped by a cgroup ring. The left side looks like a tall tower with several overhead layers, while the right side is a stripped-down pipeline to Redis, showing the trade-off between abstraction and speed.
Containers buy generality. This worker buys a shorter path to execution.
Execution unitStartup pathIsolation strengthFilesystem overheadOperational complexityBest fit
serverless-agentLinux process plus cgroup v2Medium, kernel boundary plus UID separationLowLowLinux-native workers that need fast startup
Docker-based FaaSContainerStrong, with namespaces and image isolationMedium to highMedium to highGeneral-purpose multi-tenant serverless
Firecracker microVMMicroVMStrongMediumMediumTight isolation with less weight than a full VM
VM-based runnersFull VMVery strongHighHighHighly isolated agent sessions or bespoke tooling

The point is not that containers are obsolete. It is that a process plus cgroup can be enough when you care more about cold-start simplicity and Linux control than about the full isolation surface of a container or VM.

If your workload expects custom kernels or cross-platform portability, this design narrows the runway on purpose. That makes the trade-off easier to read, not weaker.

The limits are part of the design

This approach assumes Linux, cgroups v2, and a trustworthy host setup. That is a narrow target, but it is also the reason the implementation can stay lean.

It also leans on memory limits more than a broad resource policy. No CPU pinning, no disk quota, and no attempt to pretend portability is free. That makes `serverless-agent` feel narrower than Docker or microVM platforms, but cleaner for the exact problem it is trying to solve.