Case Study

Multi-tenant fleet SaaS codebase migration

A fleet-management and dispatch SaaS for construction-haulage operators, the firms moving aggregates, muck-away, and waste between sites. I came in as the senior engineer to lead the codebase migration, white-label through a partner agency: four years of steady client work had proven the product. What it needed was a platform that could keep up.

Anonymized under NDA: the product appears here as “Atlas Fleet”, and the tenant shown is fictional.

Laravel 13 (PHP 8.4)Livewire 4Flux UIMySQLReact NativePestFCMNginxSupervisor

The problem

The system I was asked to replace served twenty client companies: real regulated work, with Sage exports, weighbridge capture, EWC coding for regulated waste, and PDF dockets with proof-of-delivery. It was not a prototype.

What held it back was structural. Signing a twenty-first client meant another deployment, and every shared fix had to be merged twenty times over. The design that got the product to twenty customers made the twenty-first expensive. Each client ran on its own database, its own git branch, and its own folder of compiled assets.

kestrel.atlasfleet.app · /login

Laptop showing a login page flooded in a single brand color, with a white sign-in card and a company logo above it.
[fig. 1] Tenant login: each client signs in on its own subdomain, and the page wears that tenant’s brand color. Branding is a database row, not a per-client build.

What I built

The codebase migration onto Laravel 13 and PHP 8.4 landed in under four months: an office side for planning and compliance, and a driver app in the field; the workflows the product had built up over four years carried over intact.

The core change is one shared database: every record carries an account, each tenant stays inside its own data, and the subdomain decides who a request belongs to.

It stayed a modular monolith on purpose: one multi-tenant schema, queue workers under Supervisor, and role-scoped access down to a PIN-token driver.

kestrel.atlasfleet.app · /dashboard

Operations dashboard with six stat tiles, a red-and-amber vehicle-compliance alert row, and a recent-transports table.
[fig. 2] The morning walk-around checks feed this row: a defect logged by a driver at 6am is an amber tile in the office by 6:05.

Planning a week, vehicle by vehicle

The operational heart of the office side is a week-by-vehicle planning grid.

Own trucks appear by plate, subcontractors by their code, and jobs with no vehicle yet sit in an amber row, next to the daily safety checks, whose defects surface on the dashboard above.

kestrel.atlasfleet.app · /operations/planning

Planning screen with a week-by-vehicle grid: plates and subcontractor codes down the left, day columns with job chips, and an amber unassigned row.
[fig. 3] Planning. The week × vehicle grid: own fleet by plate, subcontractors by hub code, transport-type chips (PD, MA, CO, CF), and the amber “no vehicle assigned” row.

What drivers use

A React Native app, with subcontractor drivers joining on a PIN token instead of an account. A driver starts a transport by picking how full the truck is, closes it with a docket number, a photo, and a signature, and sees the tonnes still to move on the project.

Thinking about a codebase migration of your own? The first step is a senior read of the version you have now. Fixed price, 1–3 days, written report you keep.
Start With an Audit

kestrel.atlasfleet.app · driver-app

Two phone screens of the driver app: one with project tonnage totals and load-fraction buttons, one with a docket field, photo button, signature pad, and a finish-transport button.
[fig. 4] Two screens is the whole driver app: start by load fraction, finish with docket, photo, and signature. Everything else happens in the office.

What changed

  • One database instead of one per client: account scoping in a single schema, instead of twenty named connections.
  • One branch instead of one per client: no more merging every shared fix across twenty branches.
  • Onboarding: from a deployment to a row of data: one server, one certificate, one database, one branch.

Have a codebase to migrate?

Tell me what’s straining and how big it has grown. Sometimes the fix is a full migration; often it is one bottleneck you can pull out without one.

Book a Free Call