How to install Composer and Drush in a multi-user configuration

Updated October 2020 to match my current workflow and to make the guide easier to follow.

 

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 (or 9), 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.

Check if Composer is already installed

To check if it's already installed, run composer in your shell. If you get a bunch of output explaining composers various options, it's already installed. On recent versions of Ubuntu, if it's not installed you'll get "Command 'composer' not found, but can be installed with: sudo apt install composer", but I'd recommend following instructions on the Composer website to download it yourself instead of using apt to ensure you have an up to date version, and it won't be available in the package manager on all systems anyway (for example Debian doesn't have a package for it in the standard apt repositories last time I checked).

To check if Composer is installed globally, run which composer - if you're given the location of the executable then it's installed globally (so you can skip the 'install Composer' step), if there's no response to this command but it is installed, then it's probably installed 'locally' in your home directory (so you'll want to consider removing it from your home directory and installing it globally instead).

Installing Composer

The first option on Composer's Getting Started page is to install Composer locally in your home directory. I recommend going for the global install method instead so that we only have to set it up once per server rather than repeating the process for each user on each server.

Installing Drupal

Follow the instructions on the official guide under the Create a project heading. In our workflow, towards the end of development you should eventually have a Composer project for dev and a Composer project for live. You don't need to worry about setting up a live system until later, but it's a good idea to name them appropriately from the outset - ie. the first one you set up is /var/www/projectname-dev and then live will be /var/www/projectname-live - so it's very clear which is which regardless of whether they're on the same server or not.

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 that needs to web-accessible, whereas everything else should be kept private.

We don't have Drush installed yet, so you can set up a database and follow the guide's instructions to complete the installation with the standard web interface, or you can install Drush first and then finish installing Drupal on the command line.

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 Drupal 7 so this could make things complicated!

At time of writing, the drupal/recommended-project package doesn't automatically install Drush for you, so you'll need to cd into the root of your composer project (/var/www/drupal8/ in my case) and run composer require drush/drush. Composer will install a copy of drush that's appropriate to whatever version of Drupal you've just installed.

When it's finished, you should be able to test the 'local' copy of Drush like this - /var/www/drupal8/vendor/bin/drush version - and drush will tell you its version number.

Running Drush from the full path is too tedious for general use, so I installed the Drush Launcher script as recommended by the Drush documentation. Following these steps I removed the old global drush install at /usr/local/bin/drush and replaced 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 copy of Drush, how do we use Drush on our Drupal 7 sites that don't use Composer?

You can skip this section if you aren't intending to maintain any Drupal 7 sites on your server.

Drush Launcher allows us to set a fallback option to be used when your 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 .bashrc in my home directory or /etc/profile.d/. For more info on setting the environment variable, 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 instructions here but with a few differences:

  1. Browse to https://github.com/drush-ops/drush/releases and download the drush.phar attached to the latest 8.x release.
  2. Test your install.
    php drush.phar core-status
  3. Rename to `drush8` 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
  4. Optional. Enrich the bash startup file with completion and aliases.
    drush8 init
  5. Create a file that sets the fallback for Drush Launcher (multi-line command)
    echo '# Based on https://kitson-consulting.co.uk/blog/composer-drush-multi-user-config
    export DRUSH_LAUNCHER_FALLBACK="/usr/local/bin/drush8"' >> drush_launcher.sh
  6. 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 works for all users.

Option C

I'd like to be able to use Composer to keep the global Drush updated easily. One way to achieve this is to...

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 quite glitchy so I switched to option B which is much more stable. To make it easier to tell what's what, I actually made a sym-link pointing to drush-launcher, so now /usr/local/bin contains:

drush - a sym-link pointing to drush-launcher so that we can launch it from anywhere by running drush.
drush8 - a manually downloaded copy of Drush 8 for drush-launcher to use as a fall-back.
drush-launcher - the Drush Launcher script which attempts run a local copy of Drush if you're in a Composer project that has one installed, but falls back to the global drush8 executable if it can't find one.

That's all folks!

 

Also of interest:

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

Add new comment