Heroku provides a good base level guide to get a Laravel running on its platform, however, it really stops at the bare minimum.
We’ve got 10+ (micro-ish) services running in production on Heroku and over the years we’ve fine tuned our setup to ensure that these apps are performant and our DevOps is as seamless as possible.
There’s a bit of scope, so we’ll break it down into key areas. Follow along in the app’s lifecycle:
- When an app is provisioned
- When an app is built
- When an app is released
- When an app is released for the FIRST time
- When an app is running
- When an app is running tests
When an app is provisioned
Here we will tell Heroku what services we require for an app. Assuming that most Laravel apps will store data in a database and have Redis for managing things like queues.
Let’s create an
app.json in the root directory so that Heroku will automatically provision these on our behalf.
When Heroku detects this file it will provision the Postgres and Redis server and set the
REDIS_URL environment variables, ready for us to connect. We also add
scheduler to run Artisan commands on a periodic basis (aka cronjob).
When an app is built
This is where we can do any build related processes.
Heroku supports running build scripts via
composer.json > scripts > compile.
For Laravel, we’ll add the following to composer.json
1. If your routes, events, or views change per environment, move these commands to the
releasesection covered below
2. If you have Route Closures, they will need to be moved to Controllers to cache routes
Vue / React / Webpack Build
Heroku supports running build scripts via
package.json > scripts > build
If we have any JS or CSS to build, we should add Node to our buildpack in
Then add a
build script in your package.json. If you’re using this with Laravel Mix, then simply refer to the production script.
Running this will compile our CSS and JS and create a manifest file. Since these are build artifacts, we should ignore those compiled files. Add the following to your
When an app is released
On every release we want to run our database migrations, invalidate any cache (or warm the cache) and upload new assets to the CDN.
Heroku let’s us do this via a special
release process that we specify in our
Add the following to your Procfile in our root directory:
release: php artisan migrate --force && php artisan cache:clear && php artisan config:cache
php artisan config:cachehere instead of during build because if a slug is
promotedin a pipeline, the build artefacts are directly copied over, which would include the staging environment variables.
When an app is released (the first time)
If this is a brand new app, we might want to load the database with fixture data or setup oAuth clients.
Heroku lets us hook into the first time an app is deployed via the
app.json > scripts > postdeploy setting
For example if we had some fixture data and wanted to setup an oAuth Client in Laravel Passport.
When app is running
Now it’s time to get the app actually running! There are number of different things we need to setup so let’s break them down :)
Web Server (Nginx / Apache)
Our Laravel app is executed by Apache or Nginx. Nginx is generally faster so we need to tell Heroku to use Nginx and serve from the public directory.
Let’s add to our
Procfile in the root directory and add the following
web: vendor/bin/heroku-php-nginx -C nginx.conf public/
Lastly, we need to configure Nginx to rewrite URLs to Laravel. Create
nginx.conf in the root directory and add the following.
This also ensures that the app is accessed via HTTPS.
env variables in the
app.json are set on new apps created. Let’s specify what the defaults should be for new apps created from our repo.
APP_KEY generated by Heroku is a 64 character string however Laravel expects a 32 character string. We can modify our
config/app.php to include support for Heroku’s random value.
In addition we can also dynamically set our
APP_URL if the
HEROKU_APP_NAME variable is set (which happens for review apps or if dyno metadata is enabled).
Lastly, since Heroku provides
DATABASE_URL let’s remove the defaults in
config/database.php, otherwise it overrides what is specified in the URL.
ext-redis to our
composer.json to make our connection with Redis much more efficient.
composer require ext-redis
Traffic is routed through Heroku’s Load Balancer which terminate SSL connections. We need to configure Laravel to trust the headers that Heroku’s load balancer sets.
Add the following to the
worker: php artisan queue:work
Scheduler / Cron
If you have cron jobs to run, you have to configure them manually in the Heroku Scheduler interface.
I would recommend AGAINST using Laravel’s Task Scheduling. Since the scheduler is “best effort”, there’s no guarantee that the job will be run at the exact minute specified, so it won’t match the
every10Minutes() you configure using Laravel.
When an app is running tests
At this point our app should be up and running on Heroku, but we want to run our automated tests using Heroku CI.
First, let’s use the TAP standard output so that Heroku can understand the results of our tests.
composer require gh640/phpunit-tap --dev
Then tell Heroku how to run our test script to use the tap printer. In
test-setup script should usually replicate whatever you’re doing in your
Your Heroku app, continuous integration and review apps should be running like a dream.
For a starter repo — check out https://github.com/morrislaptop/laravel-heroku