How to Run Multiple Laravel Sail Projects on the Same PC

Written by in Laravel on 8~12 minutes
How to Run Multiple Laravel Sail Projects on the Same PC

In a previous post, we explained how to run Laravel applications on different ports. That approach is suitable for simple scenarios which use shared services with all the applications. But how to create completely isolated, multiple environments for every application? Of course, we can use Laravel Sail. Laravel Sail provides a complete development environment conveniently less than in 5 seconds. But the question is, how can we run multiple Laravel Sail projects at the same time on the same PC? The main problem here is all the Laravel applications, database servers, mail servers and every service trying to use the same ports. As a result of that, only the first Laravel Sail project will run and all other projects will be failed to start.

In this post, you will be able to learn how to run multiple isolated Laravel Sail development environments on a PC. Mainly we explain how to run the following services in your Laravel projects.

  • Laravel application
  • Database servers (MySQL, MariaDB and PostgreSQL)
  • MailHog
  • Redis
  • Memcached
  • Minio

But you will be able to apply the same concept to any other services that you may use as well. Also, even though we explained how to run all these services, you only need to configure the services that you use in your projects. At the end, we are going to test each service with test cases to make sure every service is working as expected. So make sure to remove all the test cases for unused services.

Laravel Application

The very first and the most important thing is to change the Laravel application port. Usually, it runs on port 80, so you can use another port to avoid port conflicts. Open the .env file of a Laravel project and add a new environment variable called APP_PORT. For convenience, we assign the port 8081 for the first project like below.

APP_PORT=8081

Apply incrementing ports 8082, 8083,.. for other Laravel projects as well. Make sure not to use the same port for any of the services.

Database Servers

Laravel mainly supports 3 DBMS (Database Management Systems) namely, MySQL, PostgreSQL and MariaDB. In addition to that, Laravel supports SQLite as well, but since it is a file-based database there is no need to do any additional configuration other than the SQLite database configuration. All the main 3 DBMS services check the FORWARD_DB_PORT environment variable when starting the container. So, likewise we changed the APP_PORT, now we are going to change the database port as well. Add the following line to the .env file with different port numbers to all the Laravel projects. Usually, both MySQL and MariaDB use the port 3306 as the default port while PostgreSQL uses port 5432. So you can assign something like 33061 or 54321 as the port. Assign port 33062, 33063,.. for other Laravel projects. Again, make sure to avoid using the same port.

FORWARD_DB_PORT=33061

MailHog

MailHog mainly expect two ports called FORWARD_MAILHOG_PORT and FORWARD_MAILHOG_DASHBOARD_PORT. Change them accordingly in the .env files with incrementing values for all the Laravel projects.

FORWARD_MAILHOG_PORT=1021
FORWARD_MAILHOG_DASHBOARD_PORT=8021

But still, emails will not be delivered unless you assign an email address to the MAIL_FROM_ADDRESS environment variable in the .env file. So update that following line.

MAIL_FROM_ADDRESS=[email protected]

Redis

Redis uses the port 6379, so create an environment variable called FORWARD_REDIS_PORT in all the Laravel projects and assign unique incrementing ports for all the .env files.

FORWARD_REDIS_PORT=63791

Memcached

At the moment of writing this post, the Laravel Sail docker-compose.yml file does not load custom ports from the .env file. Instead, it loads the hardcoded port 11211 in the docker-compose.yml file. But we can easily change it so open the docker-compose.yml file and scroll down till you see the memcached service. Then add the FORWARD_MEMCACHED_PORT environment variable to its port like below.

memcached:
    image: 'memcached:alpine'
    ports:
        - '${FORWARD_MEMCACHED_PORT:-11211}:11211'
    networks:
        - sail

After that, open the .env file and add incrementing ports for all the Laravel projects.

FORWARD_MEMCACHED_PORT=11211

Minio

Minio expects two environment variables called FORWARD_MINIO_PORT and FORWARD_MINIO_CONSOLE_PORT. Add the following two lines to the .env file of all the Laravel projects with incrementing ports.

FORWARD_MINIO_PORT=9001
FORWARD_MINIO_CONSOLE_PORT=8901

Also, update the values of the following environment variables as they are without changing any value.

FILESYSTEM_DRIVER=s3
AWS_ACCESS_KEY_ID=sail
AWS_SECRET_ACCESS_KEY=password
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=local
AWS_ENDPOINT=http://minio:9000
AWS_USE_PATH_STYLE_ENDPOINT=true

Test Service Configurations

Ultimately, since we may have dozens of services in multiple projects, checking each service manually one by one can be a bit cumbersome. So, now we are going to write some test cases to automate it to quickly check whether all the services are properly running or not. However, this testing step is not mandatory and completely optional.

Create a feature test by running the following make:test command.

php artisan make:test ConfigTest

It will create a file called ConfigTest.php in the tests/Feature directory. Add the following test cases into the ConfigTest.php file according to the services that you use.

Laravel Application Testing

The following test case will check whether the application is working or not.

public function test_app()
{
    $response = $this->get('/');

    $response->assertStatus(200);
}

Database Service Testing

This test case will try to create a database record and retrieve it to check whether the database is working properly or not.

use RefreshDatabase;

public function test_database()
{
    \App\Models\User::factory()->create([
        'email' => '[email protected]',
    ]);

    $this->assertDatabaseHas('users', [
        'email' => '[email protected]',
    ]);
}

Redis Service Testing

The test_redis() test case will try to check whether the Redis service is properly running or not by trying to write and read some data.

public function test_redis()
{
    $input = 'redis secret';

    \Illuminate\Support\Facades\Cache::store('redis')
        ->put('secret', $input, 3);

    $output = \Illuminate\Support\Facades\Cache::store('redis')
        ->get('secret');

    if (\Illuminate\Support\Facades\Cache::store('redis')->has('secret')) {
        $this->assertEquals($input, $output);
    } else {
        $this->assertFalse(true);
    }
}

Memcached Service Testing

The following test case will check whether Memcached is working properly.

public function test_memcached()
{
    $input = 'memcached secret';

    \Illuminate\Support\Facades\Cache::store('memcached')
        ->put('secret', $input, 3);

    $output = \Illuminate\Support\Facades\Cache::store('memcached')
        ->get('secret');

    if (\Illuminate\Support\Facades\Cache::store('memcached')->has('secret')) {
        $this->assertEquals($input, $output);
    } else {
        $this->assertFalse(true);
    }
}

MailHog Service Testing

Add this test case to check the MailHog service.

public function test_mail()
{
    \Illuminate\Support\Facades\Mail::mailer('smtp')
    ->send('welcome', [], function($message) {
        $message->to('[email protected]')
            ->subject('Test Mail'); 
    });

    $this->assertTrue(true);
}

Minio Service Testing

Add the following test case to check the Minio service.

public function test_minio()
{
    $input = 'minio secret';

    \Illuminate\Support\Facades\Storage::disk('s3')
        ->put('example.txt', $input);

    $output = \Illuminate\Support\Facades\Storage::disk('s3')
        ->get('example.txt');

    $this->assertEquals($input, $output);
}

So ultimately, the final ConfigTest.php file should be something similar to this.

class ConfigTest extends TestCase
{
    use RefreshDatabase;

    public function test_app()
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }

    public function test_database()
    {
        \App\Models\User::factory()->create([
            'email' => '[email protected]',
        ]);

        $this->assertDatabaseHas('users', [
            'email' => '[email protected]',
        ]);
    }

    public function test_redis()
    {
        $input = 'redis secret';

        \Illuminate\Support\Facades\Cache::store('redis')
            ->put('secret', $input, 3);

        $output = \Illuminate\Support\Facades\Cache::store('redis')
            ->get('secret');

        if (\Illuminate\Support\Facades\Cache::store('redis')->has('secret')) {
            $this->assertEquals($input, $output);
        } else {
            $this->assertFalse(true);
        }
    }

    public function test_memcached()
    {
        $input = 'memcached secret';

        \Illuminate\Support\Facades\Cache::store('memcached')
            ->put('secret', $input, 3);

        $output = \Illuminate\Support\Facades\Cache::store('memcached')
            ->get('secret');

        if (\Illuminate\Support\Facades\Cache::store('memcached')->has('secret')) {
            $this->assertEquals($input, $output);
        } else {
            $this->assertFalse(true);
        }
    }

    public function test_mail()
    {
        \Illuminate\Support\Facades\Mail::mailer('smtp')
        ->send('welcome', [], function($message) {
            $message->to('[email protected]')
                ->subject('Test Mail'); 
        });

        $this->assertTrue(true);
    }

    public function test_minio()
    {
        $input = 'minio secret';

        \Illuminate\Support\Facades\Storage::disk('s3')
            ->put('example.txt', $input);

        $output = \Illuminate\Support\Facades\Storage::disk('s3')
            ->get('example.txt');

        $this->assertEquals($input, $output);
    }
}

Before running the tests, we need to do a few more things. First, install the Amazon S3 Storage Driver league/flysystem-aws-s3-v3 package by running the following command.

composer require --with-all-dependencies league/flysystem-aws-s3-v3 "^3.0"

Now everything is almost ready, so start all the Laravel projects one by one by using the sail up -d command.

./vendor/bin/sail up -d

Sometimes, Laravel might not immediately recognize all the configurations we made due to configuration caching. So run the artisan optimize:clear command to clear all the caches.

./vendor/bin/sail php artisan optimize:clear

Then create a Minio bucket. So, log in to the Minio dashboard in the browser using the port you mentioned in the FORWARD_MINIO_CONSOLE_PORT. For example, http://localhost:8901. Default Minio username is “sail” and password is “password”. Now add a new bucket called “local”. Laravel Sail Minio Dashboard Create Bucket

Laravel Sail Minio Dashboard Create Bucket

Finally, run the tests.

./vendor/bin/sail php artisan test

Conclusion

In this post, mainly we learned how to run multiple Laravel projects on the same PC using the Laravel Sail. After that, we learned to write test cases to check whether all the services are properly running or not. What do you think about this post? Let us know your thoughts about this post in the comments section below.

Written By

A web guy. Currently, he works as a backend developer. In his free time, he writes about PHP, Laravel, WordPress and NodeJS.