Installing Drupal with Composer

Although there are different ways to get Drupal onto a server, the most reliable method for modern projects is to use Composer. Composer doesn't just download Drupal's core files — it also manages the PHP packages, modules, and other dependencies Drupal needs.

This means that when we move the project to a different server, instead of copying every dependency one by one, we can recreate them all from the composer.json and composer.lock files.

Drupal's official documentation also recommends Composer and the drupal/recommended-project structure for new projects.

Before You Start the Installation

First, we can check that PHP and Composer are installed on the server.

To see the PHP version:

bash
php -v

For Composer:

bash
composer --version

we can use this.

For example, if we get an output like:

text
Composer version 2.8.12

this means Composer is available.

Drupal 11 requires Composer 2.7.0 or newer.

Creating the Drupal Project

To create a new Drupal project:

bash
composer create-project drupal/recommended-project my-drupal-site

we can use this command.

Here,

text
drupal/recommended-project

is Drupal's recommended project template.

text
my-drupal-site

is the project directory that will be created.

Once the command finishes, a structure similar to the following is created:

text
my-drupal-site/
├── composer.json
├── composer.lock
├── vendor/
└── web/

Drupal's executable web files live inside the web directory. Files that visitors don't need direct access to, such as vendor, stay outside the web root. This is one of the main reasons Drupal recommends the recommended-project template.

Why Is the Web Root the web Directory?

In classic PHP projects, the web server's document root can be the project directory itself.

In Drupal's recommended Composer structure, however, the web server's document root should be:

text
/var/www/my-drupal-site/web

In other words, we point the Nginx or Apache configuration to:

text
/var/www/my-drupal-site/web

instead of:

text
/var/www/my-drupal-site

This way, files such as:

text
composer.json
composer.lock
vendor/

aren't directly accessible through the web.

Installing a Specific Drupal Version

We might not always want to install the latest version.

For example, if the project needs to start on a specific Drupal version, we can specify the version in the Composer command:

bash
composer create-project drupal/recommended-project:11.2.0 my-drupal-site

In this case, Composer creates the project based on the specified Drupal version.

If we want to start with the current stable version without pinning a specific version:

bash
composer create-project drupal/recommended-project my-drupal-site

is enough. Drupal's official installation documentation also uses this command as the basic installation method.

What Is the composer.json File For?

After installation, there is a composer.json file at the project root.

This file defines which packages the project needs.

For example:

json
{
  "require": {
    "drupal/core-recommended": "^11",
    "drupal/core-composer-scaffold": "^11"
  }
}

In this example, the project tells Composer it needs the Drupal 11 core packages.

Later, when we add a new Drupal module to the project, Composer updates this file automatically.

Why Does composer.lock Matter?

While composer.json specifies which packages can be used, composer.lock keeps the exact package versions used at installation.

For example, when two developers set up the same project on their own machines, running:

bash
composer install

Composer installs the versions recorded in composer.lock as closely as possible.

This is why the composer.lock file needs to be added to Git in Drupal projects.

Its purpose is to ensure the same dependency versions are used across different development, testing, and production environments.

The Difference Between composer install and composer update

These are two of the most commonly confused commands when using Composer.

bash
composer install

installs the versions recorded in the existing composer.lock file.

For example, when we bring the project to a new server via Git, we typically proceed like this:

bash
git clone [email protected]:project/drupal.git
cd drupal
composer install

composer update, on the other hand, can look for newer allowed versions of dependencies and can change the composer.lock file.

This is why it isn't right to run:

bash
composer update

out of habit on a production server.

An update should first be done in the development environment, the resulting composer.lock change should be tested, and only then transferred to production through deployment.

How Do You Add a Drupal Module?

Adding a contributed module to a Composer-managed Drupal project is quite simple.

For example, to add the Pathauto module:

bash
composer require drupal/pathauto

we can use this.

Composer downloads the required package and updates the project's composer.json and composer.lock files. Drupal's official Composer documentation also recommends adding contrib modules this way.

However, this doesn't automatically enable the module inside Drupal.

If we're using Drush, we can also run:

bash
vendor/bin/drush en pathauto

So:

bash
composer require

adds the package to the project,

bash
drush en

enables the module inside Drupal.

Development Packages

Some packages are only needed in the development environment.

For example, the Devel module can be added like this:

bash
composer require --dev drupal/devel

Here,

text
--dev

indicates that the package isn't required for production.

When:

bash
composer install --no-dev

is used in production, development dependencies aren't installed.

Drupal's official documentation also recommends using --no-dev in production installations.

Installing an Existing Project on a New Server

Let's say we're bringing a Drupal project from Git to a new server.

For example:

bash
git clone [email protected]:project/site.git
cd site

It's normal if the project's vendor directory isn't in Git.

To recreate the required dependencies:

bash
composer install

we run this.

In production:

bash
composer install --no-dev

can be used.

Composer reads the composer.lock file during this process and redownloads the packages the project needs.

That's why keeping the vendor directory in the Git repository usually isn't necessary in most Composer-based projects.

Why Does the vendor Directory Matter?

Having only the web/core directory isn't enough for Drupal to run.

Symfony components and other PHP dependencies live inside vendor.

For example, if Composer isn't run after pulling Drupal's code via Git, an error like:

text
vendor/autoload.php not found

can appear.

In that case, the usual fix is running:

bash
composer install

Drupal's official documentation specifically states that dependencies need to be installed with Composer in codebases obtained via Git.

Updating Drupal Core with Composer

One of Composer's key advantages is that updates can be done in a controlled way.

When we only want to update Drupal core packages, for example:

bash
composer update drupal/core-recommended \
  drupal/core-composer-scaffold \
  drupal/core-project-message \
  --with-all-dependencies

can be used.

After updating, Drupal's database updates also need to be run.

If we're using Drush:

bash
vendor/bin/drush updatedb

and then to clear the cache:

bash
vendor/bin/drush cr

we can run this.

Composer updates the package files; updatedb runs the database updates Drupal's modules need. These are two different operations.

Common Composer Errors

A significant portion of the errors encountered during installation come from the PHP version or missing PHP extensions.

For example, if we see an error like:

text
requires php >=8.x

the PHP version on the server might not meet the requirement of the package we're trying to install.

Similarly, errors like:

text
ext-gd is missing

or:

text
ext-xml is missing

indicate that the relevant PHP extension isn't present on the system.

So checking only:

bash
php -v

isn't always enough.

To see the active PHP extensions:

bash
php -m

we can use this.

Another important point is that the PHP used by the web server can differ from the PHP version in the terminal. Especially on servers with multiple PHP versions installed, Composer CLI uses whichever PHP version is on its own path.

Should We Run Composer as Root?

Running:

bash
sudo composer install

on the server might seem to solve permission issues at first glance, but it can cause the project files to end up owned by root.

Afterwards, when the web server or the deployment user tries to access these files, new permission problems can arise.

That's why it's healthier to run Composer commands with the regular user who owns the project whenever possible.

For example, before running:

bash
cd /var/www/my-drupal-site
composer install

like this, you should make sure the directory's owner and permission structure are correct.

A Quick Installation Summary

If we're starting a simple Drupal project from scratch, the basic process can be as short as this:

bash
composer create-project drupal/recommended-project my-drupal-site

cd my-drupal-site

composer require drush/drush

After that, we can set the web server's document root to:

text
my-drupal-site/web

and move on to the Drupal installation screen with the database information we prepared earlier.

Conclusion

Composer isn't just a package-downloading tool in modern Drupal projects. It lets us centrally manage which versions of core, contributed modules, and PHP dependencies are used.

Starting a new Drupal project with:

bash
composer create-project drupal/recommended-project my-drupal-site

lets us use Drupal's recommended project structure.

Going forward, managing module additions, core updates, and deployment through Composer makes it easier for development, testing, and production environments to run on the same dependencies as much as possible.

Latest update: 18.09.2026 13:35