Getting my head around Composer: how to install Drush in a multi-user configuration

I've been putting off using Composer for a long time because it seemed too "trendy" and reminded me too much of the framework movement that HTML9 Responsive Boilerstrap JS parodied so well. However on a recent project I've been migrating a site from Drupal 6 to Drupal 8, and Drupal 8 strongly recommends using Composer from the outset so I thought it was time to take the plunge.

In this article I'll walk through how to set up Composer, Drupal 8, and Drush in a way that means we can have multiple users running the same centralised version of Drush when appropriate (ie. when a Drupal site isn't set up as a Composer project, or a project doesn't include its own copy of Drush) which means that our team can keep using Drush seamlessly without having to install a separate copy of Composer and Drush for each user or each project on our servers. This approach should also make it easy to keep things up to date and change the configuration later on if needed.

There's a helpful outline on how to install Composer and Drupal 8 on Drupal.org but I wanted to write a more detailed trail for future reference, including how I came to decide on this specific configuration.

The first option on Composer's Getting Started page is to install Composer locally inside the directory of each project. I didn't like the idea of having several copies of the same composer.phar file scattered around the server so I followed the instructions under the Installing Globally section.

Now back to the guide on Drupal.org...

I compared the different installation approaches listed and went for drupal-composer/drupal-project because it provides everything that's needed to get started quickly.

After following the instructions I had a Drupal 8 site ready to start working with.
I have a composer project directory at /var/www/drupal8/ and it contains:

composer.json
composer.lock
config/
vendor/
web/

The web directory contains the actual Drupal files that run the site so they need to web-accessible, whereas everything else should be kept private because it's used by Composer to manage dependencies for the project.

So now what about Drush?

For the last few years we've had a global install of Drush (at /usr/local/bin/drush) on each of our servers, and people on our team use this regularly to manage various Drupal 7 sites. Looking at the compatibility table it seems that Drush version 8 is compatible with Drupal 7 but not Drupal 8, while Drush 9 is compatible with Drupal 8 but not 7 so this could make things complicated!

A local instance of Drush 9 should have been installed with the drupal-composer/drupal-project package and we can use it by specifying its path eg.

$ /var/www/drupal8/vendor/bin/drush version
Drush version : 9.7.1

But this is too tedious for general use, so I installed the Drush Launcher script as recommended by the Drush documentation. Following these steps overwrites the global drush install at /usr/local/bin/drush and replaces it with the Drush Launcher script.
Now whenever we're in a Composer project directory (that has Drush installed as a dependency) we can just run Drush commands the shorthand way:

$ cd /var/www/drupal8
$ drush version
Drush version : 9.7.1

But now we have no global Drush, how do we use Drush on all of our Drupal 7 sites that don't use Composer?

Drush Launcher allows us to set a fallback option to be used when the current directory doesn't have Drush installed as a Composer dependency. The fallback option is set by defining an environment variable eg. by adding a line to the .bashrc file in my home directory. For more info on .bashrc and /etc/profile.d/ see this article.

I considered a few different options here before finding a solution that I was satisfied with...

Option A

Use Composer's global option to install a fallback instance of Drush that's available to me system-wide.
If I use composer global require/require drush/drush:8.x it installs the latest version of Drush 8 in /home/ben/.config/composer/.
Then I can set the Drush Loader fallback by adding export DRUSH_LAUNCHER_FALLBACK=/home/ben/.config/composer/vendor/bin/drush to my .bashrc file.

That works for me, but what about the others on my team who are expecting Drush to keep working the way it did before? I don't really want to install and maintain Composer and Drush for each user on each server!

Option B

Install a global instance of Drush 8 again the "old fashioned way" to use as fallback, but give it a different name so we can have it alongside Drush Launcher. To do this I followed the same instructions here but with a few significant changes:

# Browse to https://github.com/drush-ops/drush/releases and download the drush.phar attached to the latest 8.x release.
# Test your install.
php drush.phar core-status
# Rename to `drush` instead of `php drush.phar` and move it to a new location. Destination can be anywhere on $PATH.
chmod +x drush.phar
sudo mv drush.phar /usr/local/bin/drush8
# Optional. Enrich the bash startup file with completion and aliases.
drush init
# Create a file that sets the fallback for Drush Loader (multi-line command)
echo '# Based on https://alephnull.uk/composer-drush-multi-user-config
export DRUSH_LAUNCHER_FALLBACK="/usr/local/bin/drush8"' >> drush_launcher.sh
# Move it to a location where it will be loaded by all users when they log in
sudo chown root:root drush_launcher.sh
sudo mv drush_launcher.sh /etc/profile.d/

This is a relatively elegant option and it applies to all users, but I'd like to be able to keep the global Drush updated easily, and using Composer would make it easier to change major versions later if needed or even set up other PHP utilities in a similar fashion.

Option C

Set Composer's home directory to somewhere central so the packages installed with the global option are accessible to all users. This is based on a Stack Overflow post but expanded.

# Move my composer "home directory" to a centralised location.
# If ~/.config/composer/ doesn't exist, just create an empty directory at /usr/local/composer/
sudo mv ~/.config/composer/ /usr/local/
# Create a file that sets the environment variables for Composer and Drush Loader (multi-line command)
echo '# Based on https://alephnull.uk/composer-drush-multi-user-config
export COMPOSER_HOME="/usr/local/composer"
export DRUSH_LAUNCHER_FALLBACK="/usr/local/composer/vendor/bin/drush"' >> composer_and_drush.sh
# Move it to a location where it will be loaded by all users when they log in
sudo chown root:root composer_and_drush.sh
sudo mv composer_and_drush.sh /etc/profile.d/

Then you'll have to set permissions on the new Composer "home directory" so that users have the appropriate access to use composer global commands to install/update the centrally installed packages. Ideally you'd have a user group on your system for this and grant access only to that group, but that's beyond the scope of this article. In my case I achieved it with:

sudo chgrp -R kitson /usr/local/composer
sudo chmod -R g+wx /usr/local/composer

Log out and log in again to load the new environment variables. Use the env command to see which environment vars have been set. If it looks correct, run composer global require drush/drush:8.x and it should install into the new location, available to users across the system thanks to Drush Loader being in the PATH for all users and locating the correct fallback version of Drush for us.

Conclusion

I was using option C for a while, but drush was being glitchy at some point and I didn't have time to debug properly so I reverted to option B which has less "moving parts". We've been using that for a few months now without any issues.

That's all folks!

Also of interest:

https://www.lullabot.com/articles/switching-drush-versions

Add new comment