extjac's avatar

Multi-Tenancy with subdomains and .env files

I would like to use the same Laravel App (infra) for all customers but each customer/subdomain to have its own DB (same schema)

Example:
siteA.laravel.app => customerA DB 
siteB.laravel.app => customerB DB
siteC.laravel.app => customerC DB

I have tried creating one .env file per customer and map it to the customer subdomain. So basically, it created a folder called /envs and then add the below files

.env.siteA.laravel.app
.env.siteB.laravel.app
.env.siteC.laravel.app

Then, I am adding below code to the boostrap/app.php

if(isset($_SERVER['HTTP_HOST']) && !empty($_SERVER['HTTP_HOST']))
{
    $domain = $_SERVER['HTTP_HOST'];
    if (isset($domain) ) 
    {      
        $dotenv = \Dotenv\Dotenv::createImmutable( base_path(), '/envs/.env.'.$domain );
        try 
        {      
            $dotenv->load();      
        } catch ( \Dotenv\Exception\InvalidPathException $e ) {      
            // No custom .env file found for this domain      
        }
    }
}

And the web.php would like like this...

Route::domain('{site}.laravel.app')->group(function (  $site ) {
	//Routes go here
});

Finally, i added below to the .htaccess

    <Files .env>
    order allow,deny
    Deny from all
    </Files>

All works....But question I have is....

Have you done this ? if so, what approach have you used?

If you haven't done it...what issues do you see with above approach? Mainly...security.

0 likes
11 replies
extjac's avatar

@Tray2 not looking into implementing this approach at the moment...but thanks

Intr0spect1ve's avatar

I am using the same approach for over 5 years now, and I had exactly the same question. It works, but is it the best approach?

Since I'm currently doing a major rewrite of my site by using more modern techniques , I thought it was a good time to explore the different multi-tenancy solutions. I followed courses here and on Laravel Daily, and I concluded there are a lot of multi-tenancy solutions, but none works as good as the environment one for my use case (we have a site which runs in 3 different countries, and all countries are fully separated, so different server, website and database).

I did find this package which also uses environment files: https://github.com/gecche/laravel-multidomain

This package uses the same approach, and I like it very much, but it seems overkill for my use case because it jumps through hoops to make it possible to run multiple sites from the same installation, but since I have totally separate installations per server I don't need this. So I think I'll stick to my own simple code, which is much like your approach.

Btw, I deploy by using a script which can deploy the code to the specific server I want. I use rsync in this script and I exclude all the .env files from syncing. So after the rsync I do a scp for the specific .env I need. This way I don't have multiple .env files on the servers, just one per server.

Just my two cents on a multi tenancy approach, which I find very easy to use, and to my surprise is never even mentioned by any multi-tenancy course/how-to etc (as I'm aware of).

3 likes
martinbean's avatar

@int0spect1ve Well it sounds like you have actual multi-tenancy, if you have multiple instances of your application running in different geographic locations. This is where environment variables should be used.

Your codebases on each server should be the same, but its behavior changed at runtime using environment variables (i.e. different AWS_DEFAULT_REGION values if each application instance communicates with AWS resources in different AWS regions, etc).

Environment variables are exactly that: variables for each environment, not “customer” or “website” or “account” or whatever other entities you have in your application. Changing your application’s configuration at runtime using environment variables forms one of the 12 Factor App tenets: https://12factor.net/config

2 likes
Intr0spect1ve's avatar

@extjac In app/Http/Kernel.php:

public function bootstrap()
{
    if (!empty($_SERVER['HTTP_HOST'])) {
        $env = '';
        if (str_contains($_SERVER['HTTP_HOST'], 'site.tld1')) {
            $env = 'country1';
        } elseif ($_SERVER['HTTP_HOST'] == 'country1.site.test') {
            $env = 'country1test';
        } elseif (str_contains($_SERVER['HTTP_HOST'], 'site.tld2')) {
            $env = 'country2';
        } elseif ($_SERVER['HTTP_HOST'] == 'country2.site.test') {
            $env = 'country2test';
        } elseif (str_contains($_SERVER['HTTP_HOST'], 'site.tld3')) {
            $env = 'country3';
        } elseif ($_SERVER['HTTP_HOST'] == 'country3.site.test') {
            $env = 'country3test';
        } else {
            trigger_error('Undefined environment.');
        }
        app()->loadEnvironmentFrom('.env.' . $env);
    }
    parent::bootstrap();
}

So I have env files like .env.uk and .env.uktest and for console commands I use --env=uk or --env=uktest.

1 like
Intr0spect1ve's avatar

@extjac Just for some external packages, and this works (just put the --env= in the command). But in our normal workflow we don't use migrations (the website is only there to support some aspects of a stand-alone client-server application).

iosc's avatar

@Intr0spect1ve

You save my day...I have been researching on multi-tenency to start my SaaS using laravel but i found that in the real life practical, multi tenancy not been realistic since my server is on cPanel :( I can't get cPanel to auto create database credentials automatically

now with your solution here I able to achieve semi-auto setup using cPanel and cloudflare to do the sub-domains ways to do the multi-tenancy

I am building one copy of laravel 11 running on different mariadb for each clients, providing e-invoicing project for the e-tax scheme in Malaysia here, so i can just maintain one copy of the laravel for multiple business multi user database ;)

Please or to participate in this conversation.