The Neuronen Schmiede concerns itself with topics related to software development. One focus are robust web applications.

Deploy Laravel on Shared Hosting

Permalink

Sometimes you have to deploy a Laravel application to a shared hosting webspace. Most of the time that implies some crucial limitations. If this does not apply to your shared hosting plan consider yourself lucky.

  • It is not possible to create or modify virtual hosts. Your domain is pointing to a specific folder on the webspace and the URL path maps one-to-one to the folder structure. If you can create subdomains the same limitations apply.

  • You have to use a specific version of PHP and cannot install additional extensions.

  • The only way to access the webspace is via the web interface of the hoster or FTP. There is no SSH or rsync available.

In order to operate a Laravel application on such a shared hosting plan you have to cover three areas. The first one is getting the application to run on the webspace. Then you should automate the deploy process so you can deploy new versions with a single command. In the end the only thing left to do is running migrations.

Setting up Laravel on shared hosting

First of all make sure PHP 5.4 or greater is available and the MCrypt extension is enabled. If that is not the case you can stop right now, sorry you are out of luck. Create a database for your application and gather the credentials for the database and the FTP login.

This article only covers how to run Laravel directly on the domain or a subdomain. So if you want to run it in a subfolder like http://mydomain.com/my-app/ you have to do it by yourself. Let's say you have a folder named my-app/ on your webspace and you would like to put the Laravel application inside that folder. To make that work the domain or subdomain you are using has to point to my-app/public/. (It is okay if this public folder does not exist yet, we will create it in a second.) If the domain or subdomain is not pointing to the public folder Laravel will not run!

Now it is time to add the database credentials to the environment file. During development I'm using Postgres1 while in production it is MySQL. To allow this I not only put the credentials into the environment file but also which database driver Laravel should use. Laravel running in the production environment uses the .env.php file to get the environment variables, now it is your turn to fill it out.

.env.php

<?php
return array(
  'ENCRYPTION_KEY' => 'a-very-long-random-string',
  'DATABASE' => 'mysql',
  'DATABASE_USER' => 'user',
  'DATABASE_PASSWORD' => 'secret',
  'DATABASE_NAME' => 'my-app-production',
  'MIGRATION_TOKEN' => 'another-very-long-random-string'
);

For the time being ignore the MIGRATION_TOKEN variable, we will use it later to run the migrations. The next code listing shows you how Laravel uses these environment variables to set up the database connection.

app/config/database.php

<?php
return array(
  // Lots of comments and other stuff

  'default' => $_ENV['DATABASE'],
  'connections' => array(
    'mysql' => array(
      'driver'    => 'mysql',
      'host'      => 'localhost',
      'database'  => $_ENV['DATABASE_NAME'],
      'username'  => $_ENV['DATABASE_USER'],
      'password'  => $_ENV['DATABASE_PASSWORD'],
      'charset'   => 'utf8',
      'collation' => 'utf8_unicode_ci',
      'prefix'    => '',
    ),
    'pgsql' => array(
      'driver'   => 'pgsql',
      'host'     => 'localhost',
      'database' => $_ENV['DATABASE_NAME'],
      'username' => $_ENV['DATABASE_USER'],
      'password' => $_ENV['DATABASE_PASSWORD'],
      'charset'  => 'utf8',
      'prefix'   => '',
      'schema'   => 'public',
    )
  ),

  // More comments and stuff

After modifying these two files we can upload the Laravel application to the webspace. Open the FTP program of your choice, enter the credentials and upload the entire application folder to my-app. As you can see the public folder is now also there.

Deploy Laravel with a single command

Deploying web applications by hand via a FTP program is a bad practice, you should not do that. Opening a FTP program, selecting the application files and uploading them to the correct location is a slow and error-prone process. We can do better by using a deploy script.

Such a deploy script usually uses SSH and/or rsync to do the work but in our case these technologies are not available on shared hosting. Thankfully there is a small FTP client called LFTP which we can control from the command line, install LFTP and come back when you are finished.

Our deploy script is a little bash script that uses LFTP to upload the files to the webspace. The only thing you have to do is run ./deploy in the command line to deploy the Laravel application. Let us start by creating the deploy script and make it executable.

Create the deploy script and make it executable

touch deploy
chmod +x deploy

This file does several things, in the beginning it makes sure there is a .environment file present and that it defines the necessary environment variables. If that is not the case the script aborts and prints an error message.

If all needed variables are set it proceeds to run the LFTP command. First LFTP connects to the webspace and starts uploading the relevant files and folders needed by Laravel. It then makes sure the app/storage folder is writeable by Laravel. Before finishing the deployment the script runs the migrations, the next part explains how running the migrations works.

deploy

#!/bin/bash

command -v lftp >/dev/null 2>&1 || { echo >&2 "LFTP is required."; exit 1; }
test -f ".environment" || { echo ".environment is required."; exit; }

source '.environment';

test ! -z "$FTP_USER" || { echo "FTP_USER variable is required."; exit; }
test ! -z "$FTP_PASSWORD" || { echo "FTP_PASSWORD variable is required."; exit; }
test ! -z "$MIGRATION_TOKEN" || { echo "MIGRATION_TOKEN variable is required."; exit; }

echo "Deployment started";

lftp << EOF

set ssl:verify-certificate no;

open -u $FTP_USER,$FTP_PASSWORD my-domain

put -O /my-app/ .env.php
put -O /my-app/ artisan
mirror -v -R --delete -x .DS_Store -x .gitkeep public/ /my-app/public/
mirror -v -R --delete -X .* -X .*/ -x storage/ app/ /my-app/app/
mirror -v -R --delete -X .* -X .*/ bootstrap/ /my-app/bootstrap/
mirror -v -R --delete -X .* -X .*/ vendor/ /my-app/vendor/

mkdir -pf /my-app/app/storage/cache/
mkdir -pf /my-app/app/storage/logs/
mkdir -pf /my-app/app/storage/meta/
mkdir -pf /my-app/app/storage/sessions/
mkdir -pf /my-app/app/storage/views/

chmod -Rf 0777 /my-app/app/storage

EOF

curl -X POST http://my-domain.com/migrate/$MIGRATION_TOKEN

echo "Deployment finished";

In case you are wondering how the .environment file looks, here you go. It is very simple and just contains a few variables needed by the deploy script. Make sure to modify the deploy script so it reflects your own webspace structure and the domain or subdomain you are using!

.environment

FTP_USER="user";
FTP_PASSWORD="secret";
MIGRATION_TOKEN="very-long-random-string";

export FTP_USER;
export FTP_PASSWORD;
export MIGRATION_TOKEN;

Running Laravel migrations on shared hosting

Normally running migrations works the same way as it does in development, you just execute php artisan migrate in the command line. But since the shared hosting does not provide SSH access we can not do this. The best solution that I found is using a HTTP request to run the migrations. This is certainly not perfect2 but at least better than using Adminer, phpMyAdmin to create the schema yourself.

To enable us to do this we create a new route in the routes file. To run the migrations it has to be a POST request and include the migration token defined in the environment variables. Be sure to use a long random string as migration token because anybody can make that request. If the token from the request matches the environment variable we tell artisan to run the migrate command.

Add this route to app/routes.php

Route::post('/migrate/{token?}',  array(function($token = null)
{
  if ($token == $_ENV['MIGRATION_TOKEN']) {
    Artisan::call('migrate', array('--force' => true));
  }
  else
  {
     App::abort(403);
  }
}));

That's it, with this setup I am successfully running a Laravel 4.2 application on a Host Europe shared hosting plan.

  1. I wrote about my Laravel 4.2 setup on OS X Yosemite with Postgres and the built in PHP version.

  2. For starters anybody with internet access can run the migrations if the token is known. So make sure it is not. In addition the migrations are limited by the PHP execution time limit like every other HTTP request. So if your migrations take a long time this method will not work.