OtaFlux: Simple OTA Firmware Updates via OCI Registries

./otaflux-iot-ota-upgrades.png

Keeping IoT devices updated Over-the-Air (OTA) is critical if you want to avoid running around unplugging, flashing, and re-plugging devices every time you fix a bug or ship a new feature.

For my ESP32-based home sensors, I wanted something simple, secure, and easy to integrate with modern workflows. So I built OtaFlux, a small HTTP server that fetches firmware from an OCI-compliant registry and serves it to devices when updates are available.

In this post, I will explain how OTA works, how OCI registries became useful beyond container images, and how OtaFlux fits in. I’ll also show how this enables robust CI/CD for firmware development, and what’s next for the project.

Why Over-the-Air?

If you are building embedded devices, especially ones that are hard to access, you need OTA updates. Without it, every firmware change means physically plugging in, flashing, and rebooting devices by hand. OTA lets devices regularly check for a new version, download it securely, validate it, and apply it with no user intervention.

But OTA comes with a few challenges:

  • How do devices know what the latest version is?
  • Where is firmware hosted?
  • How do you validate updates (size, CRC, etc)?
  • How do you keep the implementation lightweight?

That’s where OtaFlux comes in, but first let’s understand what’s an OCI registry.

OCI registries beyond containers

If you have used Docker, then you are already familiar with container registries. They are systems that store and distribute container images. These registries follow a specification defined by the Open Container Initiative (OCI), originally designed to standardize how container images are packaged and shared.

The container ecosystem introduced the OCI as a standard for packaging and distributing container images. But the format is general-purpose enough to store any kind of binary artifact.

This means you can now:

  • Version firmware as OCI artifacts
  • Make firmware version immutable
  • Use existing registries like Harbor, GitHub Container Registry, or your own
  • Push and pull using standard tools (oras, docker, etc)

oras is a CLI tool that lets you push and pull any files to OCI registries, not just container images. It can be used to manage firmware, configs, or binaries using the same infrastructure you use for containers.

For example, to push a firmware binary to a registry using the oras CLI:

oras push \
    registry.example.com/yourorg/device-firmware:1.2.3 \
    firmware.bin:application/vnd.unknown.layer.v1+binary

This stores your firmware with proper metadata and versioning.

The beauty is: your firmware pipeline can now re-use the same infrastructure used for containers.

Now, let’s see how new version for IoT firmware can be released using standardized CI/CD processes.

CI/CD for releasing new firmware version

The big win is how well this fits into modern CI/CD workflows. Because firmware is versioned and immutable in the registry, you can publish updates straight from your build system.

The diagram below provide an overview of what the CI/CD process could look like:

EEEdnbnrnegugegviiilienlnenledeaeoeeseprwrermoerwnkotfrlkwofowlrodkppwrfuualllpfoolmlutwp-e-b-errrlrnegeieqeqsluuheeeassstteRRReCRRReCduuutouuutoetnnncmnnncmvo.p.pisltisltiOcdeieleielReecnsecnseA-vuttuttSiirisbrisbdciniinic:etgntgnls-yayaihirradyyt-:ab1g2.c23.d34ddeevvpipiucucrdseseeeh-h-lviieifdfdaci:i:sersrse-mhmhiwawafda-a-i:rarbr1e1e2m.bcw223a.cdr334e((Feei..OrRgRgIvme.e.oewOgOgTraCihCih-rIsaIsaFtetrtrlhrbrbeeUyoyoe-prrtAgir))rade(e.gD.rarfvet1l.eR2ae.sl3ee-adsreafter)

To summarize:

  • A GitHub Action builds your firmware
  • It tags it with a version (e.g. living-room:1.2.3)
  • Pushes it to an OCI registry
  • Devices check the registry via OtaFlux and download the latest version

This setup gives you a clean separation between build and deploy. You no longer need to manage state on the device or keep track of which version is running where. OtaFlux serves as the stateless bridge between CI/CD and your fleet of devices.

This approach opens up smarter release patterns too. By tagging firmware versions, you can control exactly which devices upgrade when. I have used this for gradual rollouts where test devices get updates first, then non-critical devices, and finally the full fleet. This ring-based release approach considerably reduce the blast of a faulty release.

How OtaFlux works

OtaFlux is a small Rust server that:

  1. Expose GET /version?device=living-room endpoint which returns the latest firmware version, CRC32 and firmware size for a given device. Example response from the endpoint:
    0.1.1
    4051932293
    942320
    
  2. Looks up the latest tag for that device in the configured OCI registry
  3. Pulls the firmware binary using the OCI API
  4. Caches the result in memory
  5. Exposes GET /firmware?device=living-room endpoint which returns the firmware binary

All of this with minimal memory overhead and a simple configuration via environment variables.

Security Considerations

OTA updates present a significant security vector if not properly secured. OtaFlux addresses this in several ways:

  • mTLS: Devices should use mutual TLS to authenticate both the server and client. A possible future improvement of OtaFlux is to verify the device id with the client certificate CN (common name) to prevent devices from pulling other devices firmware
  • CRC validation to prevents corrupted firmware from being applied
  • Registry security: leverages existing OCI registry security features for access control and artifact signing (on the road map)

Real world use-case

I use OtaFlux with my ESP32 home sensor project. Each device is flashed with a unique name (e.g. living-room, bedroom) and runs firmware with OTA using mTLS (enabled via Cargo feature flag).

Devices regularly check for new firmware and download it if the version differs from what’s running.

This setup gives me:

  • GitHub Actions for building, releasing, re-configuring devices
  • Harbor as a self-hosted OCI registry
  • OtaFlux as OTA update server
  • ESP32 devices with fully automated upgrades

It’s simple, reliable, and doesn’t require any custom protocol or platform specific tooling.

Lessons learned

  • Using OCI registries for firmware transforms versioning and storage into a standard process. No more custom scripts or ad-hoc storage solutions needed.
  • Watch out for registry compatibility. Not all registries fully implement the OCI specification. I found Harbor, GitHub Container Registry, and Google Artifact Registry to work reliably.
  • Never skip CRC checks in your implementation. They are essential for preventing corrupted firmware from bricking devices, especially over spotty Wi-Fi connections.
  • The beauty of this approach is how it fits into existing workflows. Since it’s using the same tools as container workflows, your team doesn’t need to learn a whole new deployment system.
  • This setup give me the possibility to roll out firmware gradually. First to test devices, then to the rest of the production devices. It’s been a game-changer for catching issues before they affect the entire fleet.
  • You could potentially start running automated tests against real devices as part of the CI/CD pipeline since the firmware is just another artifact, it’s easy to pull it for testing before release.
  • OTA becomes far more manageable when firmware is just another release artifact. You get versioning, rollbacks, and distribution all in one familiar package.

What’s next

Right now, devices poll OtaFlux at regular intervals. This works fine, but it’s not ideal for more dynamic update flows. The next step is to support MQTT-based notifications:

  1. Devices subscribe to a topic, e.g. updates/living-room
  2. OtaFlux continuously scan for new version of firmware in the OCI-compliant registry
  3. When new firmware is pushed, OtaFlux detect the new version and publish a message in the topic
  4. Devices download immediately instead of polling

This would reduce latency and network load while staying simple and event-driven.

Other planned features:

  • Support for private registries with credentials
  • Better device authentication mechanism
  • More detailed observability and metrics

You can find the project here: github.com/etiennetremel/otaflux

Let me know if you end up using it, or want to collaborate on the next phase.