Deploying Laravel on Scalingo

Detection

When a PHP application is deployed, we’re looking at the composer.json file to know if it is using a particular framework. For Laravel, we’re looking if the Composer dependencies contain laravel/framework. If so, your app is deployed as a Laravel application.

{
  "require": {
    "laravel/framework": "~7.2",
    // ....
  }
}

During the deployment process you’ll see the following output, mentioning that the framework has correctly been detected.

-----> Detected Laravel App
-----> Setting up Laravel App

Configuration

A Laravel application requires the APP_KEY environment variable to be defined with a random string. The artisan script can generate one for you:

scalingo env-set APP_KEY=$(php artisan --no-ansi key:generate --show)

Then, you will need to set the APP_URL environment variable to your main domain (for instance, https://my-app.osc-fr1.scalingo.io), otherwise you may end up with 404 errors.

scalingo env-set APP_URL=https://my-app.osc-fr1.scalingo.io

Your application also requires a database, often a SQL database like MySQL or PostgreSQL. This article will be using MySQL, but it should be identical with a PostgreSQL database.

First, a MySQL has to be added to your application. This addon will inject the environment variable SCALINGO_MYSQL_URL and its alias DATABASE_URL.

DATABASE_URL=mysql://user:pass@my-db.mysql.dbs.com:30000/my-db

To use this URL in your application, add at the top of your app/config/database.php file the following lines. They decompose the URL into the different fields and let you configure your application.

$url = parse_url(getenv("DATABASE_URL"));

$host = $url["host"];
$username = $url["user"];
$password = $url["pass"];
$database = substr($url["path"], 1);

Then find the mysql entry in the database.php file and modify it like:

    'mysql' => array(
        'driver'    => 'mysql',
        'host'      => $host,
        'database'  => $database,
        'username'  => $username,
        'password'  => $password,
        'charset'   => 'utf8',
        'collation' => 'utf8_unicode_ci',
        'prefix'    => '',
    ),

Your application may need to write files on the filesystem. On Scalingo, the filesystem is ephemeral and you shouldn’t use it to store important files. The best practice is to use an object storage such as Amazon S3. The Laravel explanation on how to configure the file storage is here. The configuration for this is located in the file config/filesystems.php.

Migrations

Artisan is the command-line interface included with Laravel. It provides a number of helpful commands that can assist you while you build your application.

Migrations are like version control for your database, allowing your team to modify and share the application’s database schema. Migrations are typically paired with Laravel’s schema builder to build your application’s database schema. If you have ever had to tell a teammate to manually add a column to their local database schema, you’ve faced the problem that database migrations solve.

First Migration

Creating a migration with Laravel is done with the following command on your workstation.

./artisan make:migration create_plants

The file is created at the location database/migrations/date_yourmigration. Edit it to write the content of the migration.

public function up()
{
    Schema::create('plants', function($table) {
        $table->increments('id');
        $table->string('common_name', 100);
        $table->string('latin_name', 100);
        $table->text('description');
    });
}

public function down()
{
    Schema::drop('plants');
}

To run locally the migration to see if the syntax is right, run the following command:

./artisan migrate

If everything went well, add this file to your Git repository and deploy the application on the platform. Once the application has been deployed, apply the migration to your production database:

scalingo --app my-app run php artisan migrate

For every migration file, a down method should be written in the case we want to rollback database migrations:

scalingo --app my-app run php artisan migrate:rollback

Another Example: Alter a Database Table

The previous example created a ‘table creation’ migration, here is an example of table alteration.

public function up()
{
    Schema::table('plants', function($table) {
        $table->unique('latin_name', 'plants_unique_latin_name');
    });
}

public function down()
{
    Schema::table('authors', function($table) {
        $table->dropUnique('plants_unique_latin_name');
    });
}

As previously, running the migration on the hosted application once deployed:

scalingo --app my-app run php artisan migrate

For more examples, refer to the official Laravel documentation.

Apply Migrations Automatically after Deployment

So far the action to apply migrations on the production database was manual. It is possible to automate it by using a postdeploy hook All you have to do is to create a Procfile file at the root of your project with the following content:

postdeploy: php artisan migrate --no-interaction --force

That’s it! If a deployment is a success, the command applying migrations will be automatically run.

Laravel Queues

Handling Laravel queues requires you to start another process in your application. Add the following line to the Procfile of your application:

queues: php artisan queue:work --queue=high,default

The arguments of the --queue option must be customized to suit your needs. After deploying your application, you must scale the newly created type of containers queues at least to 1.

Laravel Tasks Scheduler

The Laravel PHP framework includes a command scheduler for asynchronous tasks. However, Laravel’s documentation states you need to add a cron entry which is not possible on Scalingo.

This documentation page guides you to let your application use the Laravel command scheduler on Scalingo. The following command must be added to your app. It calls the Laravel scheduler every minutes. It requires you to start another process in your application.

<?php

namespace App\Console\Commands;

use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;

class SchedulerDaemon extends Command
{
  /**
   * The name and signature of the console command.
   *
   * @var string
   */
  protected $signature = 'schedule:daemon {--sleep=60}';

  /**
   * The console command description.
   *
   * @var string
   */
  protected $description = 'Call the scheduler every minute.';

  /**
   * Execute the console command.
   *
   * @return mixed
   */
  public function handle()
  {
      while (true) {
          $this->line('<info>[' . Carbon::now()->format('Y-m-d H:i:s') . ']</info> Calling scheduler');

          $this->call('schedule:run');

          sleep($this->option('sleep'));
      }
  }
}

Don’t forget to add the following line to the Procfile of your application:

scheduler: php artisan schedule:daemon

To define the jobs the scheduler will run, check the Laravel documentation, it consists in modifying the schedule method in the file app/Console/Kernel.php.

Example:

    protected function schedule(Schedule $schedule)
    {
        $schedule->call(function () {
            // Do something
        })->daily();
    }

Based on an article on Neon Tsunami.

A Cron Example

A recurring need of PHP application is to execute multiple tasks at regular interval, in a cron-like fashion. This section explains how to set this up on a Scalingo application.

You first need to configure the Laravel Tasks Scheduler. Once this step is achieved, you need to list the tasks you want to execute.

This is done in the schedule method of the App\Console\Kernel class. Here is an example of two tasks scheduled at regular interval. The method Foo1 will be executed daily and the method Foo2 will be executed hourly.

<?php

namespace App\Console;

use DB;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    [...]

    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        $schedule->call(function () { Foo1(); })->daily();
        $schedule->call(function () { Foo2(); })->hourly();
    }
}

The Laravel documentation about this feature is available here.

Configure Passport

Laravel Passport provides a full OAuth2 server implementation for Laravel.

Usage on Scalingo requires two steps:

  • transfer the information generated during passport:install into the production container via env variables
  • configure composer to export some env variables into files and run artisan passport:keys at each deployment

To transfer the information, use the CLI env-set command:

# Install passport, this will output <id> and <secret>
php artisan passport:install

# Set data into container env variables
scalingo env-set \
  "OAUTH_PRIVATE_KEY=$(cat storage/oauth-private.key)" \
  "OAUTH_PUBLIC_KEY=$(cat storage/oauth-public.key)"
  PASSPORT_CLIENT_ID=<id> \
  PASSPORT_CLIENT_SECRET=<secret> \
  # If mix app (with vue front)
  MIX_OAUTH_CLIENT_ID=<id> \
  MIX_OAUTH_CLIENT_SECRET=<secret>

# Remove these files as they should only be used in the deployed environment not in dev
rm storage/oauth-private.key storage/oauth-public.key

To configure composer to export some env keys into files and run artisan passport:keys at each deployment, update the scripts element of your composer.json to integrate the values (post-install-cmd, post-create-project-cmd) below:

    "scripts": {
        "post-autoload-dump": [
            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
            "@php artisan package:discover --ansi"
        ],
        "post-root-package-install": [
            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
        ],
        "post-install-cmd": [
            "[ -n \"$OAUTH_PUBLIC_KEY\" ] && echo \"$OAUTH_PUBLIC_KEY\" > storage/oauth-public.key",
            "[ -n \"$OAUTH_PRIVATE_KEY\" ] && echo \"$OAUTH_PRIVATE_KEY\" > storage/oauth-private.key"
        ],
        "post-create-project-cmd": [
            "@php artisan key:generate --ansi"
        ]
    }

mode_edit Suggest edits