A six-year engagement that moved a Ruby estate onto Phoenix microservices, separated camera logic from business logic, and put embedded Elixir on the hardware at the edge.
The problem
Physical cameras on construction sites are a hostile workload for a request-response framework. They go offline, come back mid-upload, produce snapshots continuously, and there are thousands of them. The business logic and the device logic had grown into the same Ruby application, so an incident in one became an incident in both.
A rewrite in place was not on the table — the existing applications were in production and paying for the migration.
Our approach
We did it in pieces rather than in one jump. The Sinatra API was rewritten to Phoenix with Swagger docs; the Rails dashboards kept running, tested with RSpec and Capybara, until their replacements existed. The Rails frontend moved to Nuxt and was pointed at the Phoenix API.
The architectural move that mattered was separating business logic from camera logic into distinct services. Once device handling was its own supervised system, a thousand flaky cameras became a concurrency problem — which is the problem Elixir is actually good at — rather than a reliability problem for the whole product.
Each camera got a supervised worker under a DynamicSupervisor with a one-for-one strategy, so one misbehaving device restarts alone instead of taking its neighbours with it.
What we built
- Sinatra API rewritten to Elixir and Phoenix with Swagger documentation and unit tests
- Monolith split into microservices, separating business logic from camera and device logic
- Rails frontend migrated to Nuxt and connected to the Phoenix API
- A microservice handling 1000+ camera workers over RabbitMQ with producer/consumer logic, DynamicSupervisor, and one-for-one supervision
- Snapshot Extractor built on a RabbitMQ fan-out topology using Oban, GenServer, and BroadwayRabbitMQ
- Kafka stream ingestion with BroadwayKafka and Protobuf decoding
- Embedded Elixir (Nerves) at the edge with Phoenix backends for telemetry, device health, and field operations alongside Hikvision and Axis IP cameras
- Live JPEG streaming through Phoenix channels and PubSub, with services coupled over Erlang RPC
- Hot code upgrades with Distillery and Ansible, SeaweedFS clusters for snapshot storage, and RAID and Nagios monitoring on bare-metal Hetzner servers
The outcome
The platform ended the engagement with device handling isolated, supervised, and independently deployable — and with the Ruby applications retired in sequence rather than in a single risky cutover.
Six years on one product is its own kind of evidence. Most of this work was not greenfield; it was migration carried out underneath a system that had to keep serving customers throughout.