← ontrack lab

Why ONTRACK is moving to Rust, Swift and Kotlin

A smaller backend and native apps should make ONTRACK simpler and cheaper to run as it grows.

Oliver FoxOliver FoxFounder of ontrack · doctor & researcher · runner

25 August 2026 · 3 min read

ONTRACK is being rebuilt. The Django backend is moving to Rust with Axum, while the React Native app is being replaced by native Swift and Kotlin apps.

The point isn't to chase fashionable technology. It is to make ONTRACK simpler and cheaper to run as it grows.

A smaller backend

The current Django service runs on Google Cloud Run. It uses enough memory, and starts slowly enough, that I keep an instance running even when nobody is using the app.

The Rust service uses much less memory and starts quickly enough to scale to zero. Its container contains a compiled binary, a CA certificate bundle, and almost nothing else: no interpreter, package manager, or general-purpose operating-system image.

That reduces both the dependency surface and the cost of keeping idle servers alive. The less ONTRACK spends on infrastructure, the less of the app needs to sit behind a subscription.

Replacing Django safely

The backend is moving one route at a time. I worked backwards from the routes the app actually calls, rather than porting everything Django has accumulated. Unused endpoints will be retired instead of rewritten.

Each Rust endpoint must behave like the Django endpoint it replaces. That has uncovered a few obscure differences between Python and Rust, including cached Python pickles, integers larger than 64 bits, and Unicode case conversion.

None of this makes the app faster. It makes the migration invisible: a route can move to Rust, or move back to Django, without breaking the app.

Matching behaviour does not mean copying the implementation. One startup permission check used a database query that eventually began timing out. A different version of the query completes in milliseconds while returning the same result. Compatibility matters; matching old inefficiencies does not.

Letting the phone do more

The native app rewrite is less about CPU speed and more about removing the layers between the app and the operating system.

The React Native app depends on many npm packages and several patched native bridges. The Swift app currently has one external dependency: Google's sign-in SDK. Maps, health data, animation, and most interface components come directly from iOS.

Maps are the clearest example. ONTRACK's 3D route screen used several Mapbox layers to render terrain and buildings. MapKit provides both through a single native map style. The metro map lost around 120 lines of Mapbox workarounds; HealthKit lost a wrapper and its patch; a small Lottie animation became a few SwiftUI shapes.

Native maps also change the cost of running ONTRACK. Mapbox charges per map load, so the bill grows whenever more activities are viewed. MapKit on iOS and Google Maps on Android do not charge ONTRACK for displaying these maps.

Some of the new code is dramatically shorter, but that is not entirely a win for native development. Rewriting the app also gave me a chance to remove old duplication and design shared components properly the second time around.

The trade-offs

Maintaining separate iOS and Android apps is more work. To keep that manageable, the iOS app is the reference implementation and Android follows it screen by screen. Pure maths and domain logic live in a Kotlin module with no Android dependencies, keeping platform code focused on the platform.

Native apps also require some new infrastructure. Push notifications currently go through Expo, while the iOS app receives an APNs token. The backend therefore needs to support Apple's notification service directly before the native app can fully replace the Expo version.

The final part of any rewrite is usually the hardest. Write endpoints need more care than reads, and tools such as the heavily customised Django admin are larger than they first appear. Rather than recreate all of it, I will use access logs to find what is still used, rebuild that, and retire the rest.

I have not yet run proper latency benchmarks, so I am not going to claim that Rust makes every request ten or fifty times faster. What is already clear is that the new backend uses less memory, can scale to zero, and has fewer moving parts. The native apps can use the phone's capabilities directly and remove some costs that currently rise with usage.

That is the goal: not a rewrite for its own sake, but an ONTRACK that stays affordable as more people use it.