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

I've been avoiding 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 here 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 option C - hussainweb/drupal-composer-init - because it provides everything that's needed to get started and uses an interactive wizard for setup, but also seemed to support the most features/options which could be configured later on if I decide to do so later.

After following the instructions for option C, 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!

The next step is to install the right version of Drush in the project directory of the Drupal 8 site. To do this we can simply run the command from the project's directory and Composer installs the latest compatible version like so:
cd /var/www/drupal8/
composer require drush/drush

But now when we try to use Drush, even within the site's directory, our global instance of Drush 8 is still being run from /usr/local/bin/drush instead of the the local instance of Drush 9 that we just installed in the project -
drush version
Drush Version : 8.1.15

We could run the new instance by specifying its path -
/var/www/drupal8/vendor/bin/drush version
Drush version : 9.0.0
- 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:
drush version
Drush version : 9.0.0

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 version 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 one significant change:
# 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`. Destination can be anywhere on $PATH.
chmod +x drush.phar
# NEW LOCATION:
sudo mv drush.phar /usr/local/bin/drush8
# Optional. Enrich the bash startup file with completion and aliases.
drush init

Then I added export DRUSH_LAUNCHER_FALLBACK=/usr/local/bin/drush8 to my .bashrc file (or similar) to tell Drush Launcher which instance to use as a fallback. This is a relatively elegant option and can be applied 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 be installed 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 version of Drush for us.

That's it!

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

Add new comment

(If you're a human, don't change the following field)
Your first name.
(If you're a human, don't change the following field)
Your first name.

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.