Skip to main content
Tutorial

Laravel Multi-Tenancy: How I Built One Platform for 12 UAE Businesses

4 min read

Built a multi-tenant Laravel platform handling 2.1M monthly requests for UAE businesses. Here's what broke, scaled, and surprised me.

LaravelMulti-TenancyUAE TechFull-StackTawasul Limo

At 3:14 AM last year, I hit my keyboard harder than intended and broke a keycap. My screen showed 12 browser tabs — each for a standalone Laravel app I’d built for clients in Abu Dhabi, Dubai, and Riyadh. I was trying to push emergency updates to all of them simultaneously. That night, I hit a breaking point. This approach couldn’t scale beyond 20 clients, and I had a waiting list of 8 more. So I decided to rebuild everything into a single multi-tenant platform. Here's how I actually pulled that off.

Why Force-Multiplying Code Saves My Sanity

One of my first multi-tenant projects was Tawasul Limo, a luxury car service booking system with clients across the GCC. Originally, we built separate Laravel apps for each city — Dubai alone had seven franchises. Every time we added a new feature (like live SMS notifications), I had to manually replicate the code and database changes seven times.

Multi-tenancy let me consolidate these franchises into one codebase. The core trick? Identifying tenants via subdomains (dubai.tawasul, abudhabi.tawasul) using Laravel’s Route::domain() method:

php
Route::domain('{tenant}.example.com')->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'show']);
});

This kept the routing clean but introduced a new problem — database separation.

How I Blew Up Three Client Databases (and Fixed It)

My first approach — storing all tenants in a single database with a tenant_id column — lasted exactly 4 hours. During a migration for a client’s pricing update, two businesses in Jeddah lost customer records because of a typo in the WHERE clause. Let me be real: writing defensive SQL to prevent that was brutal.

Switching to separate databases per tenant solved this. I used Laravel’s dynamic connection config, loading connection details from a central tenants table:

php
config()->set('database.connections.mysql.database', $tenant->db_name);
DB::purge('mysql');  

Yes, this meant managing 12+ databases now, but tenants sleeping soundly was worth the trade-off.

Localizing Chaos for Regional Clients

UAE and Kuwaiti clients kept demanding Arabic interfaces. One construction company near Maktoum Airport wanted their invoices in both languages but refused to have separate subdomains. The solution? Storing language preferences in tenant settings:

php
App::setLocale($tenant->preferred_language ?? 'en');  

We baked localization into the Tenant model, including date/time formats. This bit me later when a Kuwaiti client scheduled maintenance at 12:59 AM — which in some PHP builds translates to 00:59 instead of 24:59. Cost me 6 hours debugging Ramadan hours.

Configuration That Didn’t Scale (Then It Did)

When onboarding the twelfth tenant — a real estate firm in Doha — I realized my config was a mess. Mail settings, API keys, and currency formats were hardcoded. I created a tenant_settings table with JSON columns, then built a settings panel in Inertia.js (Vue) for clients to tweak their own options.

One gotcha: Environment variables. I used Spatie’s laravel-tenancy package to auto-generate .env files for each tenant during deployment. It’s hacky, but works.

Why My Storage Bill Tripled Overnight

This cost me actual sleep: storage. Serving tenant-specific media (like hotel branding images) from S3 was clean, but backups were hell. My first backup script tried to gzip 12 databases into one file. After a 48-hour process that maxed our server costs, I switched to per-database dumps. Storage costs jumped from $200 to $1,500/month — but client trust was worth it.

Technical Wins That Didn’t Make My Hair Turn Gray

  • Used Laravel Horizon to queue tenant-specific jobs (e.g., Dubai’s daily reporting)
  • Packaged reusable logic like SMS notifications into a CompanyTools service provider
  • Leveraged Laravel Telescope for per-tenant debugging (filter by subdomain)

For a retail chain in Kuwait, I built a tenant dashboard that showed resource consumption — their ops team wanted to know why their database was 80GB (answer: they were storing logs directly in the DB. Gave them a long look when I told them that).

Real Talk: This Isn’t Perfect

Multi-tenancy works great until your host starts charging by database count. One UAE cloud provider slapped me with a new tier when I scaled to 15 tenants — separate DBs were suddenly cost prohibited. Now I’m eyeing PostgreSQL schemas but haven’t migrated. Technical debt like that sits in the back of my mind like a loose tooth.

Closing Thoughts

If you're in the same boat — fighting duplicate code across GCC client projects — give multi-tenancy a shot. But keep your eyes open about trade-offs. Last month, our consolidated platform handled 2.1 million requests across 12 UAE businesses without a single deployment error. I'll take that over broken keycaps any day.

Need another perspective? I wrote a case study about how we scaled the Tawasul Limo booking system to 6 GCC cities using similar architecture. Want to build something like this together? We’re hiring contractors.

S

Sarah

Senior Full-Stack Developer & PMP-Certified Project Lead — Abu Dhabi, UAE

7+ years building web applications for UAE & GCC businesses. Specialising in Laravel, Next.js, and Arabic RTL development.

Work with Sarah