v0.1.2 — built on Raspberry Pi OS Lite (64-bit)
Flash. Boot.
Deploy anywhere.
piletOS turns your devices into self-managing nodes of your fleet. Deploy containers, ship updates over the air and watch state flow back. No cluster, no expertise, no downtime.
One broker. Any number of Raspberry Pis. Deploy from anywhere.
Fragile scripts. Heavy clusters. Meet the middle.
Managing IoT devices usually means brittle SSH scripts — or dragging a full orchestrator to the edge. piletOS gives you the discipline of one with the footprint of the other.
Hand-rolled scripts
SSH loops, cron, copy-paste
- Zero infrastructure to run
- No desired state — what runs where lives in someone's head
- A dropped connection mid-update leaves the device half-configured
- Nothing heals itself after a power cut
piletOS
one tiny agent, one broker
- Declarative and self-healing — declare it once, the device converges
- MQTT-light: built for flaky networks and tiny hardware
- OTA updates with zero workload downtime
- Flash, boot, done — no expertise required
Kubernetes at the edge
k8s / k3s on every site
- Declarative and self-healing
- Control plane is heavy for small devices
- Assumes stable, cluster-grade networking
- Real ops expertise required to run well
A control plane in every device.
piletOS brings two worlds together — the proven ideas of the Kubernetes ecosystem, adapted to the realities of IoT: small devices, unreliable networks, no ops on site.
MQTT-native support
QoS 1, retained state, MQTT 5 request/response — lightweight messaging that thrives on unreliable networks.
OTA updates, zero downtime
One message updates the agent: fetch, verify sha256, install, hand off to systemd. Your containers never stop serving.
Every app is a container
Install, modify or delete any Docker workload with a single message. Ports, env vars, CPU and memory limits — all in the spec.
Automatic reconcile
Declare what should run and the device converges to it — drift, crashes and missed messages heal on the next sync.
Survives power cuts & restarts
Pull the plug, plug it back in. The node boots, re-lists its desired state and revives every workload — no hands required.
AsyncAPI compatible
Every topic and payload is specified in a machine-readable AsyncAPI 3.1 contract.
Deploy any service in one message.
A little simulation, right in your browser. Press Deploy and watch the messages fly.
nginx
nginx:alpine
web server — port 80 → device :8000
pi-node-01
- model
- Raspberry Pi 4B
- ip
- 192.168.1.41
- capacity
- 4 cpu · 7820Mi
- pilet
- v0.1.2
containers
— none —
mosquitto — pilet/v1/pi-node-01/#
subscribed. waiting for messages…▌
http://raspberry-ip:8000
This site can't be reached
nothing is serving on :8000 yet — press Deploy
Welcome to nginx!
If you see this page, the nginx web server is successfully installed and working. Served by pi-node-01.
OTA — over-the-air updates
Update the fleet while it works.
One message per device updates pilet over the air. Each device downloads the new version, verifies it and swaps itself over — no SD card, no SSH, no site visit.
Your workloads keep serving the whole time. Zero downtime.
fleet — 6 devices
Staggered rollout — old and new versions coexist safely mid-update.
Fleet-ready in three steps.
No expertise required. If you can flash an SD card, you can run a fleet.
Flash with Raspberry Pi Imager
Open the official Raspberry Pi Imager and follow the steps — when prompted to choose an OS, pick Use custom and select the piletOS image you downloaded.
Drop your config on the card
After flashing, re-insert the SD card: it shows up as a small boot drive on any computer. Open it, find pilet.env and fill in your broker, device name, admin password and Wi-Fi. Read once at first boot, then deleted.
# boot/pilet.env
PILET_BROKER_HOST=192.168.1.32
PILET_DEVICE_ID=pi-node-01
ADMIN_PASSWORD=change-me
WIFI_SSID="MyNet"
WIFI_PASSWORD="secret"
WIFI_COUNTRY=GR
Boot. That's it.
Power on and the node announces itself with a retained status snapshot. It's already reconciling — deploy from anywhere.
$ mosquitto_sub -t 'pilet/v1/+/status'
✓ pi-node-01 Ready · 4 cpu · 7820Mi · pilet v0.1.2
✓ pi-node-02 Ready · 4 cpu · 3792Mi · pilet v0.1.2
▌
pilet-os-0.1.2.img.xz
654 MB download · 4.8 GB on card · sha256 5cf29e6e…e54d09
Raspberry Pi 3, 4 & 5 (64-bit).
Speak the contract.
The whole protocol is specified in a contract compatible with the AsyncAPI 3.1 spec. Below: trimmed examples of the real payloads.
{
"apiVersion": "pilet/v1",
"kind": "Device",
"metadata": { "name": "pi-node-01" },
"status": {
"nodeInfo": {
"model": "Raspberry Pi 4 Model B Rev 1.4",
"architecture": "aarch64",
"containerRuntimeVersion": "docker://27.1.1",
"piletVersion": "0.1.2"
},
"capacity": { "cpu": "4", "memory": "7820Mi", "maxContainers": 20 },
"conditions": [
{ "type": "Undervoltage", "status": "False", "reason": "PowerOK" },
{ "type": "ThermalThrottled", "status": "False", "reason": "CpuCool" }
],
"images": [{ "names": ["nginx:alpine"], "sizeBytes": 20512000 }],
"addresses": [{ "type": "InternalIP", "address": "192.168.1.41" }]
}
}
{
"type": "ADDED",
"object": {
"apiVersion": "pilet/v1",
"kind": "Container",
"metadata": { "name": "nginx" },
"spec": {
"restartPolicy": "Always",
"containers": [
{
"name": "nginx",
"image": "nginx:alpine",
"ports": [{ "containerPort": 80, "hostPort": 8000 }],
"resources": { "limits": { "cpu": "500m", "memory": "128Mi" } }
}
]
}
}
}
{
"apiVersion": "pilet/v1",
"kind": "Container",
"metadata": { "name": "nginx" },
"status": {
"phase": "Running",
"conditions": [{ "type": "Ready", "status": "True" }],
"containerStatuses": [
{
"name": "nginx",
"image": "nginx:alpine",
"containerID": "docker://415d0a1a81b0",
"ready": true,
"restartCount": 0,
"state": { "running": { "startedAt": "2026-07-14T09:12:44Z" } }
}
]
}
}
# asyncapi.yaml — AsyncAPI 3.1, MQTT 5 bindings, QoS 1 everywhere channels: nodeStatus: address: pilet/v1/{deviceId}/status # retained containerStatus: address: pilet/v1/{deviceId}/containers/{name} # retained specWatch: address: pilet/v1/{deviceId}/spec/watch specListRequest: address: pilet/v1/{deviceId}/spec/list/request specListResponse: address: pilet/v1/{deviceId}/spec/list/response updateCommand: address: pilet/v1/{deviceId}/update
6 channels · typed payloads · machine-readable — generate docs, clients and mocks with the AsyncAPI toolchain.