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

Environment Variables

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

Database Connection

Your application also requires a database, often a SQL database like MySQL or PostgreSQL. Configuration is similar, replace mysql:// by postgresql://.

First, a MySQL or PostgreSQL has to be added to your application. This addon will inject the environment variable SCALINGO_MYSQL_URL or SCALINGO_POSTGRESQL_URL and alias it to DATABASE_URL. We recommend using the alias.

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

Laravel supports database connection strings directly with the 'url' configuration key.

Open the app/config/database.php and modify the pgsql or mysql entry depending on your database. You can remove the commented lines.

    'mysql' => array(
        'driver'    => 'mysql',
        'url'       => env('DATABASE_URL'),
        'charset'   => 'utf8',
        'collation' => 'utf8_unicode_ci',
        'prefix'    => '',
    ),

File Storage

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.

Frontend

When you deploy a Laravel application on Scalingo, the platform detects it and picks the PHP buildpack. This buildpack embeds a Node.js engine and installs the dependencies from your package.json file but will not run the build phase.

You are required to add a .buildpacks file at the root of your project as described in PHP application with Node.js as well as add a build script in your package.json. More information on deploying Node.js applications.

{
  ...
  "scripts": {
    "build": "npm run production", // add this line
    "prod": "npm run production",
    "production": "mix --production"
  }
}

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.

From this point, there are two possibilies, depending on the frequency of the scheduled tasks.

The Tasks Run on a Frequency Lower Than Once Every 10 Minutes

For these cases, we highly recommend to use the Scalingo Scheduler.

Create a cron.json file at the root of your application with a content similar to this:

{
  "jobs": [
    {
      "command": "15 * * * * cd /app && php artisan schedule:run >> /dev/null 2>&1"
    }
  ]
}

In the example above, the task would run at 15 past every hour, but you can specify your own schedule, as long as it respects the 10 minutes interval.

The Tasks Run on a Frequency Higher Than Once Every 10 Minutes

These high-frequency cases require a bit more work.

First, you need to create your own Command subclass because the one provided by Laravel won’t directly suit our needs and constraints. Here is a good starting point:

<?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'));
      }
  }
}

In the example above, you can see that the defined scheduler runs every minute. (If you look closely, you will also see that our scheduler makes use of the one provided by Laravel, schedule:run.)

You now have to instruct Scalingo how to start this new scheduler. To do this, create (or update) a Procfile, like this:

scheduler: php artisan scheduler:daemon

After deploying your application, you must scale the newly created type of containers scheduler at least to 1.

Defining the Scheduled Tasks

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.

In the following example, the method Foo1 will be executed daily and the method Foo2 will be executed hourly:

    protected function schedule(Schedule $schedule)
    {
        $schedule->call(function () { Foo1(); })->daily();
        $schedule->call(function () { Foo2(); })->hourly();
    }

Based on an article on Neon Tsunami.

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"
        ]
    }

Suggest edits

Deploying Laravel on Scalingo