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 Scalingo for MySQL® or Scalingo for PostgreSQL® database 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.
Optimize: php artisan optimize
It is common to call php artisan optimize
during a deployment to optimize the application.
You can do this by writing a short script start.sh
that optimizes the application and starts the server:
#!/bin/bash
php artisan optimize
php artisan serve --host=0.0.0.0 --port=$PORT
Then, in your Procfile
call this script using the web
process type:
web: bash start.sh
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 schedule: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"
]
}
Custom Nginx Configuration
You may want to customize the Nginx behaviour for a specific path. The use case for instance is to protect with a password the access to a specific path. In this case one should update the composer.json
to include:
"extra": {
"paas" : {
"nginx-includes": ["./nginx.conf"]
}
}
And add a nginx.conf
at the root of your repository containing:
location ^~ /my/protected/path {
auth_basic Restricted;
auth_basic_user_file /app/.htpasswd;
try_files $uri /index.php =404;
fastcgi_pass php;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}