Guide to Drupal 9 on Heroku
Tags: php, drupal, heroku
Published: 2020-09-25 21:57:42

***In this guide I will:***
* [Explain Why It Made Sense for Us to Use Drupal 9 on Heroku and Why It Might Not Make Sense for You](#background-disclaimer)
* [Create a New Drupal 9 Project That is Ready to Patch](#create-new-project)
* [Install Drupal Locally using PostgreSQL on Lando](#install-drupal-locally)
* Setup a cloud file system module (Flysystem) for system files (in part 2)
* Setup a cloud file system module (Cloudinary) for user contributed files (in part 2)
* Deploy to Heroku (in part 2)

### Some Background on Why We are Using Drupal 9 on Heroku<a name="background-disclaimer"></a>

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](https://www.reddit.com/r/drupal/comments/8b87ky/change_my_view_d8_isnt_the_best_upgrade_path_for/), 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](https://www.fomfus.com/articles/how-to-create-a-drupal-8-project-for-heroku-part-1) 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](https://twitter.com/kristen_pol/status/1306686034399248386) motivated me to write this up.

{% twitter 1306686034399248386 %}

**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 <a name="create-new-project"></a>

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 <a name="install-drupal-locally"></a>

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](https://docs.lando.dev/basics/installation.html).

`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.