DEV Community

Timur Tukaev
Timur Tukaev

Posted on

I built a real container orchestrator that runs on a Google Sheet

Some projects start as a bad idea you can't shake. Mine was: what if the only rule was "keep everything inside a spreadsheet"?

Not "export metadata to a CSV." Not "log to Sheets." I mean the whole control plane. Deployments, Nodes, Pods, Events. They're tabs. A scheduler reads and writes cells. A kubectl-style CLI talks to the sheet. And on the other end, real Docker containers actually start.

The spreadsheet doesn't pretend to be a cluster. It is the cluster.

I called it Sheeternetes. It's a joke. It also compiles, has tests, and survives a node dying. Let me show you how it works, how to run it in five minutes, how it goes air-gapped on a desktop Excel file, and the part I'm weirdly proud of: you can store a whole container image inside the cells and even boot DOOM from there.

First, the obligatory disclaimer

Don't run production on this. If you do, film the save dialog.

Everything below is real code though. The scheduler does bin-packing and failover. There's HMAC request signing, make-before-break live migration, and a container image format that lives in cells. The substrate is cursed. The engineering isn't.

The idea in one picture

Kubernetes, squinting: desired state in etcd, controllers that drag reality toward it. Swap etcd for a Sheets tab and the controller for a reconcile loop, and you've got Sheeternetes.

Your cluster is a spreadsheet

  • The sheet is the store and the source of truth. Four tabs: Deployments, Nodes, Pods, Events.
  • The apiserver serves the data and runs the scheduler. In the cloud that's Google Apps Script bound to the sheet. On-prem it's a small Python server over an .xlsx.
  • The kubelet is a bash script. It turns any Docker host into a node: sends a heartbeat, gets the pods it should run, and makes local Docker match.
  • skctl is a tiny kubectl. Just curl and jq.

The contract is small enough to fit in a tweet:

GET  ?token=T&kind=pods|nodes|deployments|events   ->  { "items": [...] }
POST { token, action: "apply"|"scale"|"delete", ... }
POST { token, node, pods:[...] }                    # kubelet heartbeat -> desired pods
Enter fullscreen mode Exit fullscreen mode

Five minutes to a cluster

Cloud version, on Google Sheets. You need a Google account, a Docker host, and bash curl jq. All the code is in github.com/sncfoundation/sheeternetes.

There's no template to copy, on purpose. A blank sheet turns itself into a cluster:

  1. Create a blank Google Sheet.
  2. Extensions, then Apps Script, and paste in Code.gs.
  3. Set a TOKEN at the top.
  4. Run setup() once. It builds the four tabs, seeds a couple of sample workloads, and starts a one-minute reconcile trigger.
  5. Deploy as a Web app and copy the /exec URL.

Now bring up a node on any Docker box:

export WEBAPP_URL=https://script.google.com/macros/s/XXXX/exec
export TOKEN=secret
NODE_NAME=node-a CPU_TOTAL=4000 MEM_TOTAL=8192 ./kubelet.sh
Enter fullscreen mode Exit fullscreen mode

No spare machines? ./local-cluster.sh fakes three nodes on one Docker daemon.

Deploy something. The manifest is plain JSON:

{ "deployments": [
  { "name": "web",   "image": "nginx:alpine",        "replicas": 2, "cpu_req": 100, "mem_req": 32 },
  { "name": "hello", "image": "hashicorp/http-echo",  "replicas": 1, "cpu_req": 50,  "mem_req": 16,
    "command": "-text=hello-from-a-spreadsheet -listen=:8080" }
]}
Enter fullscreen mode Exit fullscreen mode
skctl apply lab/hello-web.json
skctl get pods
Enter fullscreen mode Exit fullscreen mode

Here's the moment that got me. You open the sheet and your cluster is right there as rows. Change replicas in a cell and the next reconcile spins up the pods. It's kubectl edit, except the editor is your cursor.

Your cluster is a spreadsheet

And in the terminal it looks suspiciously grown-up:

skctl doing kubectl things

The containers are real. docker ps on the node proves it. Sheetlium (our Cilium) drops them on a shared Docker network and gives each deployment a DNS alias, which is basically a Service.

The scheduler is not a toy

The usual reaction is "it's just a list." It isn't. On every heartbeat the apiserver actually places replicas, and it's a pure function with unit tests behind it:

  • Bin-packing by cpu_req and mem_req. Fits nowhere, gets marked Unschedulable.
  • Sticky placement, so pods don't wander between nodes for no reason.
  • Failover. A node stops sending heartbeats, goes NotReady, and its pods land on the survivors.
  • cordon, drain, migrate, the kubectl way.
  • Affinity and taints/tolerations, so GPU jobs go to GPU nodes.
skctl drain node-a          # pods evacuate to other nodes
skctl taint node-a gpu=true:NoSchedule
Enter fullscreen mode Exit fullscreen mode

Watching pods hop off a dying node, in a spreadsheet, live, is a genuinely good time.

SheetOS, because the node needs an OS

If you have an orchestrator, you want a node OS to go with it. SheetOS is our Talos: minimal, immutable, declaratively configured, and self-bootstrapping. sheetstrap reads the node's desired image from a sheet, brings up the runtime, registers as a Node, and if you ask nicely, installs a Sheeternetes cluster on top of itself.

Yes, that's a spreadsheet OS bootstrapping a spreadsheet cluster. Try not to think about it too hard.

Bare metal: no cloud, no internet

The cloud version is fun, but the real flex is running this air-gapped on a desktop file. That's the sheeternetes-onprem edition. The apiserver is a local Python process over an .xlsx, same contract as the cloud, so kubelet.sh and skctl point at it unchanged.

pip install openpyxl
cp .skctl.env.example .skctl.env

make up      # apiserver over cluster.xlsx
make node    # this host becomes a node
make apply   # deploy the demo
make pods    # watch it schedule
Enter fullscreen mode Exit fullscreen mode

It bin-packs and fails over between LAN nodes, on a .xlsx. About Excel vs LibreOffice: openpyxl reads .xlsx, so in LibreOffice Calc just Save As .xlsx. Native .ods is on the list. And this edition ships with 52 tests and CI, because a joke with no tests is just a liability.

Wiring sheets together

One cluster is boring. The good part is federating several, including different kinds: a local Excel cluster talking to a cloud Google Sheets cluster.

On-prem boxes can't be reached from outside, so the bridge dials out. It reads the local apiserver and reconciles against a peer, giving you cross-substrate service discovery. Then the fun one, live migration, make-before-break:

python3 bridge.py migrate web --from local --to peer --rollback-window 30 \
  --local http://localhost:8787 --local-token secret \
  --peer  https://script.google.com/macros/s/XXXX/exec --peer-token secret2
Enter fullscreen mode Exit fullscreen mode

Copy the spec to the target, wait until it's actually up, then drain the source. If the target never comes up, the source is left alone, so there's no downtime. Add --rollback-window and it watches the target after cutover and rolls back if it degrades.

There's also two-way sync as a daemon, and optional HMAC signing on requests (timestamp plus signature, checked within a TTL) for when your peer is exposed across substrates. We have HMAC on spreadsheets now. I'm not sure how to feel about it either.

Two kinds of containers, and yes, DOOM

This is my favorite part. Sheeternetes runs containers two ways, and they're interchangeable.

OCI mode. Put image: nginx:alpine in the manifest and the node pulls it from a normal registry. Full compatibility with the entire container world, working today. The sheet just holds desired state; the bytes come from Docker Hub.

SICF native mode. Put image: sicf:web:v1 and the image is resolved from inside the workbook. Layers are stored as base64, split across cells, addressed by sha256. No registry. The kubelet pulls the layers out of the sheet, checks every digest, and runs it.

There's a lossless bridge between the two:

sheetbuild import nginx:alpine   # OCI image  -> rows in the sheet
sheetbuild export web:v1 ...     # rows       -> OCI image
Enter fullscreen mode Exit fullscreen mode

Same digests round-trip, so import then export gets you back exactly what you started with.

Which brings us to the question every infra project must answer. Can it run DOOM? It can, and the whole game lives in the cells. Shareware DOOM plus a tiny web front-end is a few hundred base64 cells, nowhere near the 10M-cell limit. The demo that makes people blink:

docker build -t doom:shareware . && docker save doom:shareware -o doom.tar
sheetbuild import doom.tar --name doom:shareware --store cluster.xlsx
docker rmi doom:shareware        # gone locally. it only exists in the spreadsheet now.
skctl apply doom.json            # image: sicf:doom:shareware
skctl get pods                   # doom-1  Running
Enter fullscreen mode Exit fullscreen mode

The kubelet saw sicf:, fetched the layers from the sheet, verified the hashes, docker loaded the image back into existence, and ran it. The game came out of a spreadsheet. Code is at github.com/sncfoundation/sheetdoom.

One honest note: the game still runs on the node's real CPU. The sheet stores the image and schedules the pod. It's the coordinator, never the executor.

Dashboards, in cells

Of course there are dashboards. Sheetheus scrapes metrics into a tab, Sheetfana renders them with conditional formatting and sparklines, in the sheet.

Sheetfana

Pods running, nodes ready, CPU allocated, a bar chart, a time series. All cells.

It grew a foundation

At some point there were enough projects that they asked for a foundation. So there's the Sheet-Native Computing Foundation, a straight-faced parody of the CNCF that happens to work.

There's a real standard, the Sheet Container Initiative, modeled on the OCI: SCRI for the runtime interface and SICF for images, both written from shipping code, with a conformance suite. Where you have two implementations, you should have a spec, and we had two.

There's verifiable certification. You open an issue, a GitHub Action mints a serial like SFE000001 into a public registry, and you collect credentials toward ranks: SheetCadet, SheetAstronaut, SheetCommander, SheetAdmiral.

Certified Sheeternetes Fundamentals Engineer

SheetAdmiral

And it's genuinely open. A community contributor recently built SheetHub, our GitLab-on-a-spreadsheet, from scratch: apiserver, web UI, CLI, tests. It went through a real review, picked up a couple of security findings, got fixed in hours, and shipped. That's the whole point.

Come build weird things

The community is international and English-speaking, so join from anywhere.

The actual point

None of this belongs in production. But building a working cloud-native stack on a ridiculous substrate turned out to be a great way to explain the real thing. People finally see reconcile happen. They get why make-before-break matters, what a digest is for, why a control plane is separate from execution. Sometimes the fastest way to teach a serious idea is to build it somewhere silly.

It reconciles.

Top comments (0)