loading...

Guide to Drupal 9 on Heroku

kreynen profile image kreynen Updated on ・6 min read

In this guide I will:

Some Background on Why We are Using Drupal 9 on Heroku

After a decade of using a utility knife of a CMS that was Drupal 5, 6 and 7 to create solutions for hundreds of different use cases, I really thought I had created my last Drupal site after DrupalCon Nashville back in 2018. At the time I managed a development team responsible for more than ~1000 Drupal 7 for the University of Colorado, but I couldn’t get excited about a complete rewrite of the Web Express install profile and all the custom modules and configuration that made offering Drupal as a Service cost effectively. I begged someone to change my views on Drupal 8 in Nashville, but left Nashville disappointed and disillusioned about who Drupal was for.

Instead of continuing to shake my first at the sky, I took an opportunity to transition to a new role at the University of Colorado focusing on CRM/Salesforce solutions. In my new role with my new team, suggest that a logical path forward for this team for the Drupal 7 Commerce solution they had been using to process millions of donors in donations each year was to host smaller applications that integrated tightly with Salesforce on Heroku. Heroku can be expensive, but it provides a nice mix of preconfigured buildpacks and devops build, test and review tools perfect for small development teams without dedicated system admin or devops teams.

As many of you might have guessed, despite the freedom Heroko and modern javascript front ends offers, most projects still require basic CMS features. We didn't need everything Drupal offers, but we still wanted to enable non-technical SME to edit content and manage media assets. We used Contentful integrated with Cloudinary on a few projects. While Contentful is great to rapidly model content, we ran into limitations when building sophisticated, contextual forms. Staff who edited content in multiple applications found the process of jumping from app to app cumbersome and confusing. Contentful was a great Ron Popeil style “set it an forget it” style solution for simple apps, but the work required to build coherent UX frontend for multiple applications using Contentful as a decoupled backend was similar to work required to use an open source CMS solution as the backend. After evaluating several of the newer CMS solutions designed specifically for a decoupled implementation like Strapi, we ended up looking at Drupal again and I'm glad we did. Drupal 9 addressed many of the issues we had with Drupal 8 and is much easier to use with an Enterprise hosting environment like Heroku.

Because we were already hosting other applications on Heroku, we thought we’d at least try running the current release of Drupal 9 on Heroku. With a few exceptions, it worked surprising well. Federico Martínez’s posts on getting Drupal 8 running on Heroku really helped, but there are some important changes between versions 8 and 9. I’ve also taken Federico's approach further with a local deployment experience Drupal developers would be more familiar with.

Kristen Pol’s recent tweet about the issues she had when attempting to use Drupal 9 on traditional Drupal hosts like Pantheon and Acquia motivated me to write this up.

DISCLAIMER: If you aren’t running other applications on Heroku, this is unlikely to your best or most cost effective solution for hosting Drupal. While Kristen ran into issues with Platform.sh, I personally think this is best option if you are running Drupal and other modern, PHP applications. If you are only running Drupal, I'd wait to see what Pantheon has to offer with Drupal 9.1.

Create a new Drupal 9 Project That is Ready to Patch

If you aren’t familiar with using Composer for package management, it’s similar to npm. Assuming you have Composer installed, typing these commands into your terminal will quickly copy these projects and all their dependencies to you local machine.

composer create-project drupal/recommended-project drupal-on-heroku
cd drupal-on-heroku
composer require drush/drush
composer require drupal/core-composer-scaffold
composer require cweagans/composer-patches

At this point we’ve told Composer what code we want to include in the project. Including cweagans/composer-patches gives use the ability to patch core and contrib which we will need to get Flysystem and Cloudinary working in Drupal 9.

Install Drupal Locally using PostgreSQL on Lando

In Federico’s example, he used php -S 127.0.0.1:8888 -t web to spin up a local PHP server and install Drupal using the UI. We’re going to install Drupal using Lando and Drush instead.

If you haven't already installed Lando, follow Lando official guide for your platform first.

lando config

? From where should we get your app's codebase? current working directory
? What recipe do you want to use? drupal9
? Where is your webroot relative to the init destination? web
? What do you want to call this app? drupal-on-heroku

Before we start Lando, we need to make a few changes in the .lando.yml and create a defaults.env that we’ll use to mimic Heroku’s Config Vars.

Open .lando.yml with your editor of choice. If you followed the prompts correctly, you should see something like this.

name: d9-test
recipe: drupal9
config:
  webroot: web

Replace that with the following configuration.

name: d9
recipe: drupal9
config:
  webroot: web
  php: 7.4
  xdebug: true
services:
  postgres:
    type: postgres:12
    portforward: true
    creds:
      database: drupal9
events:
  post-start:
    # Sleep for a few seconds since Drush load appears to be a race condition.
    - sleep 5
    - drush status
    # Turn on dev modules excluded from config export.
    - drush en config dblog devel devel_generate update webprofiler --debug
    - drush cr
excludes:
  - vendor
  - web/core
  - web/private
env_file:
  - defaults.env

Create defaults.env in the project root directory with your editor of choice. Add the following variable.

DATABASE_URL=postgres://postgres:@postgres:5432/postgres

Create or modify your .gitignore to add defaults.env. That isn't as important for this step, but it will be important that you do not reveal your Cloudinary or S3 configuration in future once those have been added to the defaults.env.

We are finally ready to run lando start.

You will likely get this error.

[Symfony\Component\Console\Exception\CommandNotFoundException]                                                                                                                       
  Command pm:enable was not found. Drush was unable to query the database. As a result, many commands are unavailable. Re-run your command with --debug to see relevant log messages.  

Run lando info to confirm that the default Postgres port is used on your local configuration.

Now run Run drush si –db-url=pgsql://postgres:@postgres:5432/drupal.

After the install is finished, we need to update the settings.php to parse the database values from the environmental variables. Like many could hosts, Heroku can swap your applications between database instances as needed. We this happens, database credentials in the Config Vars are automatically updated. The database connection credentials are also different in each Review App, staging and production instance. As a result, hard coding these values in a settings.php file (or letting the Drupal install do this) is going to cause problems.

Open web/sites/default/settings.php in your editor of choice and update the database credentials used during the install to this.

// Add database credentials from parsed environmental variable.
// This also works for Lando since it uses the same DATABASE_URL variable.
$parsed_creds = parse_url(getenv('DATABASE_URL'));

// Need to correct scheme based on Drupal naming conventions.
$scheme = $parsed_creds['scheme'] == 'postgres' ? 'pgsql' : $parsed_creds['scheme'];
$databases['default']['default'] = [
  'database' => explode('/', $parsed_creds['path'])[1],
  'username' => $parsed_creds['user'],
  'password' => $parsed_creds['pass'],
  'prefix' => '',
  'host' => $parsed_creds['host'],
  'port' => $parsed_creds['port'],
  'namespace' => 'Drupal\\Core\\Database\\Driver\\' . $scheme,
  'driver' => $scheme,
];

Congratulations! You now have a local copy of Drupal 9 running with a Heroku friendly configuration that can be deployed on Heroku. In the next part, I will walk through how to configure Flysystem and Cloudinary with Heroku's Addons for those services.

Discussion

pic
Editor guide