DEV Community

Cover image for Multi-Tenant SaaS on Deploynix: Database-Per-Tenant vs. Shared Database
Deploynix
Deploynix

Posted on • Originally published at deploynix.io

Multi-Tenant SaaS on Deploynix: Database-Per-Tenant vs. Shared Database

Building a multi-tenant SaaS application is one of the most common reasons developers reach for Laravel. The framework's elegant ORM, middleware system, and event-driven architecture make it a natural fit for applications that serve many customers from a single codebase. But the architectural decision that will shape your application more than any other is your tenancy model: do you give each tenant their own database, or do all tenants share one?

This is not a decision you can easily reverse. Your choice affects your data model, your deployment pipeline, your backup strategy, your scaling approach, and your operational complexity for years to come. In this guide, we will walk through both approaches in depth, examine the trade-offs that matter in practice, and show how Deploynix's infrastructure supports each model.

Understanding the Two Approaches

Shared Database with Tenant Column

In the shared database model, all tenants store their data in the same database. Every table that contains tenant-specific data includes a tenant_id column, and every query is scoped to the current tenant. Laravel's global scopes make this transparent: once you apply a TenantScope to your models, all queries automatically include the WHERE tenant_id = ? clause.

This is the simpler approach and the one most Laravel SaaS applications start with. Your database schema has one set of tables, your migrations run once, and your backup is a single database dump. Tools like Stancl/Tenancy and the Tenancy for Laravel package support this model out of the box.

Database Per Tenant

In the database-per-tenant model, each tenant gets their own database. When a request comes in, the application identifies the tenant (usually from the subdomain, domain, or a header) and switches the database connection to point at that tenant's database. Each database has an identical schema, and migrations run against every tenant database.

This approach offers stronger data isolation, simpler per-tenant operations (backup, restore, migration), and the ability to place high-value tenants on dedicated database servers. It also introduces significant operational complexity that grows linearly with your tenant count.

The Case for a Shared Database

For the majority of Laravel SaaS applications, especially those with hundreds or thousands of small-to-medium tenants, a shared database is the right starting point. Here is why.

Operational Simplicity

With a single database on a Deploynix Database server, your operational story is clean. One database to back up, one database to monitor, one database to tune. Deploynix's automated backup system sends your database dumps to AWS S3, DigitalOcean Spaces, Wasabi, or any S3-compatible storage on the schedule you configure. One backup job covers all your tenants.

Your migrations run once against one database. Your index optimization covers all tenants simultaneously. Your slow query log shows you exactly which queries need attention, without needing to aggregate across dozens of databases.

Query Flexibility

Cross-tenant queries are trivial in a shared database. Need to generate a platform-wide analytics report? Run a single query. Need to find all tenants whose subscription is expiring? One query. Need to search for a user across all tenants for support purposes? One query.

In a database-per-tenant model, each of these operations requires iterating over every tenant database, running the query, and aggregating the results. For a thousand tenants, that is a thousand database connections and a thousand queries.

Resource Efficiency

Database connections are not free. Each connection consumes memory on both the application server and the database server. A shared database means your connection pool is shared across all tenants. A database-per-tenant model means each tenant potentially needs its own connection, and your application server needs enough memory to maintain all of them.

On Deploynix, your Database server is provisioned with MySQL, MariaDB, or PostgreSQL. With a shared database, a single well-tuned database server can handle hundreds of tenants efficiently. The connection pool stays manageable, the query cache is effective, and the buffer pool serves all tenants.

Implementation with Laravel

The shared database approach integrates naturally with Laravel's architecture. A global scope on your base model handles tenant scoping:

protected static function booted(): void
{
    static::addGlobalScope('tenant', function (Builder $builder) {
        if (auth()->check()) {
            $builder->where('tenant_id', auth()->user()->tenant_id);
        }
    });
}
Enter fullscreen mode Exit fullscreen mode

Packages like Stancl/Tenancy automate this pattern, handling tenant identification from domains or subdomains, applying query scopes, and managing tenant-specific configuration. The package integrates with Laravel's service container, so tenant context is available everywhere in your application.

The Risks to Mitigate

The shared database model has real risks that you need to address proactively.

Data leakage is the most serious. A missing tenant_id scope on a single query can expose one tenant's data to another. Automated testing is essential: every query that returns tenant data should be tested to verify it respects tenant boundaries. Use Laravel's model factories to create data for multiple tenants and assert that queries only return the correct tenant's data.

Noisy neighbors are the second risk. One tenant running an expensive report can slow down the database for all tenants. Use Laravel's queue system to offload expensive operations to Deploynix Worker servers, and implement rate limiting on API endpoints that trigger heavy queries.

Schema coupling means all tenants share the same schema. If one enterprise tenant needs a custom field, you cannot just add it to their database. You need nullable columns, JSON columns, or a separate metadata table.

The Case for Database Per Tenant

Despite the operational overhead, database-per-tenant is the right choice in specific scenarios.

Regulatory and Compliance Requirements

Some industries require strict data isolation. Healthcare applications handling PHI, financial applications subject to audit requirements, and applications serving government clients may need to demonstrate that tenant data is physically separated. A shared database with logical isolation through WHERE clauses may not satisfy an auditor who wants to see that Tenant A's data literally cannot be accessed through Tenant B's database connection.

Enterprise Customer Demands

Large enterprise customers often contractually require data isolation. They want the ability to request their data as a complete database dump, verify that their data is stored in a specific geographic region, and know that a database breach at another tenant cannot expose their information.

Per-Tenant Performance Guarantees

If you offer performance SLAs to premium tenants, database-per-tenant lets you place their database on a dedicated Deploynix Database server with guaranteed resources. A premium tenant on their own database server is completely insulated from the query patterns of other tenants.

Simplified Per-Tenant Operations

Restoring a single tenant's data from a backup is trivial with database-per-tenant: restore their database. With a shared database, you need to selectively restore rows across multiple tables, which is error-prone and slow.

Similarly, migrating a tenant to a different region, archiving an inactive tenant, or providing a tenant with a copy of their data for compliance purposes is much simpler when their data lives in its own database.

Implementing Database Per Tenant on Deploynix

If you choose the database-per-tenant model, here is how to architect it on Deploynix.

Database Server Strategy

For small-to-medium scale (up to a few hundred tenants), a single powerful Deploynix Database server can host all tenant databases. MySQL and PostgreSQL both handle hundreds of databases on a single instance without issues, as long as the total data size fits in memory and the connection count stays manageable.

As you grow beyond that, provision additional Database servers on Deploynix and distribute tenants across them. Keep a mapping table in a central database that records which database server each tenant lives on. Your application reads this mapping during tenant identification and connects to the correct server.

For premium tenants, provision a dedicated Database server. Deploynix lets you provision servers across DigitalOcean, Vultr, Hetzner, Linode, AWS, or custom providers, so you can place a tenant's database server in their preferred region.

Migration Management

Running migrations against hundreds of tenant databases requires automation. Stancl/Tenancy handles this with an artisan command that iterates over all tenants and runs migrations against each database. Add this to your Deploynix custom deployment script to ensure migrations run on every deployment.

Be aware that migration failures become more complex in this model. If migration 47 fails on tenant 238 out of 500, you need to handle partial migrations gracefully. Implement idempotent migrations and maintain a log of migration status per tenant.

Backup Strategy

With database-per-tenant, you cannot rely on a single backup job. Each tenant database needs its own backup. Deploynix's backup system can be configured for each database, but at scale, you will want to script this with a scheduled job that iterates over tenant databases and backs up each one to your chosen storage provider (AWS S3, DigitalOcean Spaces, Wasabi, or custom S3-compatible storage).

Schedule these backups during off-peak hours using Deploynix's cron job management. Stagger the backups to avoid overwhelming your database server with hundreds of simultaneous dump operations.

Connection Pool Management

Configure your Laravel application to manage database connections carefully. Use lazy connections so you do not connect to a tenant's database until a query actually executes. Close connections promptly after request completion. Monitor your connection count through Deploynix's server monitoring to ensure you are not approaching your database server's connection limit.

The Hybrid Approach

Many successful SaaS applications use a hybrid model. Shared data (user accounts, billing, platform configuration) lives in a central database. Tenant-specific application data lives in per-tenant databases. This gives you the best of both worlds: simple cross-tenant operations for platform concerns and strong isolation for application data.

On Deploynix, this means your App server connects to at least two databases: the central database for platform operations and the tenant-specific database for application queries. Configure multiple database connections in your Laravel database.php config, and use the appropriate connection for each model.

Scaling Considerations

Shared Database Scaling

Scale a shared database vertically first (bigger Deploynix Database server), then horizontally with read replicas. You can provision additional Database servers on Deploynix and configure Laravel's built-in read/write database connections to direct read-heavy queries to replicas while writes go to the primary.

Deploynix's Valkey cache servers reduce database load significantly. Cache expensive queries, session data, and computed results. Use Laravel's cache tags to invalidate cache entries per tenant when their data changes.

Database-Per-Tenant Scaling

Scaling database-per-tenant means distributing tenants across more database servers. This scales naturally because each server handles a subset of tenants. The challenge is the operational overhead of managing more servers, more backups, and more monitoring.

Use Deploynix's health alerts to monitor each database server. Set up alerting thresholds for disk usage (tenant databases grow at different rates), connection counts, and query latency.

Making the Decision

Here is a straightforward decision matrix:

Choose shared database if you have many small-to-medium tenants, you do not have regulatory isolation requirements, you need cross-tenant analytics, and you want to minimize operational complexity.

Choose database-per-tenant if you have regulatory requirements for data isolation, your enterprise customers contractually require it, you need per-tenant performance guarantees, or you need simple per-tenant data operations (backup, restore, export).

Choose hybrid if you need platform-wide operations (billing, analytics) to be simple but also need tenant data isolation for application data.

Conclusion

Both tenancy models work well on Deploynix. The shared database model leverages Deploynix's Database server provisioning, automated backups, and monitoring for a simple, efficient architecture. The database-per-tenant model leverages Deploynix's ability to provision multiple Database servers across cloud providers, with independent monitoring and backup for each.

The most important thing is to make this decision deliberately, based on your actual requirements rather than theoretical concerns. Start with the shared database model unless you have a specific, documented reason for per-tenant databases. You can always migrate to per-tenant databases later (it is painful but possible), while the reverse migration is nearly impossible.

Top comments (0)