How we are building Katara

Small teams don't lose to bad technology. They lose to too much of it. Every new service, framework and moving part is something to learn, deploy, monitor and keep in your head at two in the morning, and a team of a few people only has so much head to go around.

We started building Katara in September 2025 with that in mind. The goal was never the most interesting stack; it was the one that let a handful of people ship a hiring product quickly and keep shipping it as it grew. A year in, most of the original choices are still standing. One of them changed significantly, and this post is about why.

Rails, on purpose

We are a Ruby team. Ruby is pleasant to write and easy to read, and Rails has matured into a framework where most of what a product like ours needs already exists, either in the framework itself or in a well-maintained library.

Everything that matters in Katara lives in Rails: the business logic, the database, routing, authentication, billing, notifications and background work. Having one place for all of it means one mental model for the whole product, and anyone on the team can follow a feature from the URL down to the database without switching languages or codebases.

Fewer services, not more. Recent versions of Rails let us run background jobs, caching and websockets on top of the database we already have, rather than adding a separate store for each. For our scale, that means one less thing to run, back up and debug, and a deployment that stays easy to understand. If load grows enough, we'll split them out, but we'd rather make that move when we need to than pay for it in advance.

Types where they help. We add static type checks to the parts of the Ruby codebase where a mistake is expensive, without forcing them everywhere. It catches a class of bugs before they reach production while keeping the speed that made us choose Ruby in the first place.

The frontend we outgrew

At launch, Katara's frontend was Hotwire with server-rendered templates and partials all the way down. It was the natural choice for a Rails team: Ruby on both sides, everything in one place, and almost no JavaScript to maintain.

It served us well while pages were mostly documents. It started to strain as parts of the product became genuinely interactive. A pipeline board you drag candidates across, a live inbox, filters that respond as you type and scheduling that juggles several people's calendars all carry state that lives in the browser. Expressing that through templates and small pieces of JavaScript wired to them became harder to reason about with every feature.

Svelte, without an API

We moved the frontend to Svelte 5, connected to Rails through Inertia.

Svelte for the interface. Svelte let us build the interactive parts of Katara as components, with state that lives next to the markup that uses it. It compiles away most of its own runtime, which keeps pages light, and it reads closer to plain HTML than most alternatives, which made the move easier for a team that had been writing templates.

Inertia instead of an API. The usual cost of a modern frontend is a separate API to feed it, with its own versioning, authentication and documentation. Inertia removes that. Rails still owns routing and controllers, and each page simply returns a Svelte component with the data it needs. We kept the simplicity of a server-rendered app and gained a component-based interface, without building and maintaining a second backend. The price is a frontend that's tied closely to Rails, and a team that now writes two languages instead of one. Both felt small next to running a second backend.

Control over what reaches the browser. As more data flowed to the frontend, we wanted precise control over its shape and size. We serialise with a lightweight, fast serialisation library rather than the Rails default, which lets us decide exactly what each page receives and keeps responses small as the product grows.

Shipping boring

How code reaches production hasn't changed since launch, and we don't expect it to soon.

One container, one deploy. Katara runs as a Docker container deployed with Kamal onto a DigitalOcean droplet, backed by a managed PostgreSQL database. There is no cluster to operate and no orchestration layer to learn.

Merging is shipping. Every push to our production branch runs through GitHub Actions, which builds the image and deploys it. Releasing a change is as simple as merging a pull request, which keeps releases small, frequent and easy to roll back.

A single primary server isn't how you'd build for millions of users, and one day we'll outgrow it. Until then, every hour we don't spend operating infrastructure is an hour spent on the product, and for a small team that's the trade that matters most.