Installation with Composer and DDEV
|
This content was generated with the assistance of AI and should be verified against the official Drupal documentation before being relied on in production. This sub-section’s Drupal bibliography lists the reference material consulted while preparing these pages. |
This page takes a developer, an administrator or a site builder from an empty directory to a running Drupal site whose configuration is under version control. It builds the codebase of the fictitious Meridian Museum, a regional science museum whose site grows across this sub-section into exhibitions, events and a collection of objects. The steps are: create the codebase with Composer, run it in DDEV, install with the interactive installer or Drush, understand what the installer wrote, protect the site with trusted host patterns, log in for the first time and export the configuration.
The page was written against Drupal 11.4.x (11.4.0 was released on 2026-07-01), Drush 13 and 14, Composer 2 and PHP
8.3 or later, and checked on 2026-10-30. Drupal 11.4 runs on PHP 8.3, 8.4 and 8.5. Drupal 12.0.0 is scheduled for the
week of 2026-12-07 and Drupal 10 reaches its end of life on 2026-12-09, so a new site starts on Drupal 11 (see
Versions, Release Cycle and Support and
Preparing for Drupal 12). The general mechanics of Composer (the
composer.json file, the lock file, autoloading and repositories) are explained in
Composer and Packages; this page covers only what is specific
to Drupal.
The big picture
Four things are separate and the installer connects them:
-
the codebase: PHP files owned by Composer (core, contributed modules, themes, libraries) plus the project’s own custom code, all in Git
-
the database: content and, by default, the active configuration
-
the files: uploaded media in
sites/default/files, never in Git -
the configuration export: the YAML representation of the active configuration, in Git, that makes the site reproducible
Installing Drupal means creating the codebase, giving the installer a database, and letting it write
settings.php, create the tables, install the chosen profile and create user 1. Everything that follows (modules,
content types, roles) is configuration that should end up in the export directory.
Requirements
Check the requirements before creating anything; the installer refuses to continue when they are not met.
| Component | Drupal 11.4 |
|---|---|
PHP |
8.3, 8.4 or 8.5 with the PDO extension, one database driver, GD, XML, mbstring and OpenSSL (OpenSSL is required by
the Update Manager). |
MariaDB |
10.6 or later (Drupal 12: 10.11 or later), with InnoDB as the primary storage engine |
MySQL |
8.0 or later |
PostgreSQL |
16 or later (Drupal 12: 18 or later), with the |
SQLite |
3.45 or later, suitable for evaluation and tests only |
Web server |
Apache 2.4.7 or later or nginx. Support for IIS was removed in 11.0, and Windows production hosting is deprecated from 11.3 |
Composer |
Composer 2 |
Drupal 12 requires PHP 8.5, with argon2 support in PHP. On the database side the Drupal 12 requirements page and the 12.0.0 beta release notes give different PostgreSQL minimums, so confirm the value against the final release notes before choosing a database for a site meant to move to 12. The full table, with hosting advice, is on Requirements and Hosting.
Step 1: create the codebase with Composer
Composer is the only supported way to add or update Drupal code. Uploading a module or theme from the administration
interface was removed in 10.4 and 11.0, and updating from the interface in 11.2. Core, every module, every theme and
every library are therefore Composer packages, and the site’s composer.json and composer.lock are part of the
project.
The starting point is the project template drupal/recommended-project:
composer create-project drupal/recommended-project:^11.4 meridian
cd meridian
The command downloads the template, resolves its requirements and creates a project directory. The layout is the important part:
meridian/
├── composer.json the project's dependencies (commit)
├── composer.lock exact versions installed (commit)
├── vendor/ Composer packages, outside the web root (do not commit)
├── recipes/ recipes installed with Composer (see the recipes page)
└── web/ the document root, the only directory the web server exposes
├── index.php
├── autoload.php
├── core/ Drupal core (installed by Composer, do not commit)
├── modules/ contrib/ and custom/ (custom code is committed)
├── profiles/
├── themes/
├── libraries/
└── sites/
└── default/
├── default.settings.php
└── default.services.yml
Placing index.php and core/ in web/ and keeping vendor/ and composer.json one level above means a
misconfigured web server cannot serve the Composer packages or the project’s metadata. That is the reason for the
layout, and it is why the web server’s document root, and the --docroot option of DDEV below, must be web.
What the template contains
The template is deliberately small. An abridged excerpt of its composer.json shows the moving parts:
{
"name": "drupal/recommended-project",
"type": "project",
"require": {
"composer/installers": "^2.3",
"drupal/core-composer-scaffold": "^11.4",
"drupal/core-project-message": "^11.4",
"drupal/core-recommended": "^11.4"
},
"conflict": {
"drupal/drupal": "*"
},
"minimum-stability": "stable",
"prefer-stable": true,
"extra": {
"drupal-scaffold": {
"locations": {
"web-root": "web/"
}
},
"installer-paths": {
"web/core": ["type:drupal-core"],
"web/libraries/{$name}": ["type:drupal-library"],
"web/modules/contrib/{$name}": ["type:drupal-module"],
"web/themes/contrib/{$name}": ["type:drupal-theme"],
"recipes/{$name}": ["type:drupal-recipe"]
}
}
}
-
drupal/core-recommendedinstalls Drupal core and pins its dependencies to the tested versions, so two installs of the same version behave identically. From 11.4 it no longer pins Guzzle, Twig or the Symfony polyfills, so Composer may select newer releases of those three packages inside their allowed ranges. -
drupal/core-composer-scaffoldcopies the files that live outsidecore/but come from it:index.php,.htaccess,robots.txt,web.config,default.settings.phpanddefault.services.yml. The project can append to or replace these files in thedrupal-scaffoldsection ofcomposer.json; for example the Meridian project appends a line torobots.txt(a customisedrobots.txtshould addDisallow: /search?from 11.4). -
composer/installersand theinstaller-pathsmap send each package type to its directory: modules toweb/modules/contrib, themes toweb/themes/contrib, libraries toweb/libraries, and recipes torecipes/. Custom code goes inweb/modules/customandweb/themes/custom, which are not touched by Composer. -
drupal/core-project-messageprints the next-step message at the end ofcreate-project.
The template does not include Drush or the development tools. Both are added per project, and on every machine that runs the project, with Composer:
composer require drush/drush
composer require --dev drupal/core-dev
Source: Drush: Install
drush/drush is a production dependency because deployment scripts call it. drupal/core-dev brings PHPUnit, PHPCS
with the Drupal standards, PHPStan and the other tools used by the testing and coding-standards pages. Never install
require-dev packages on a production server: deploy with composer install --no-dev --optimize-autoloader.
Drush is installed only through Composer, per project, so the project always uses the Drush release that matches its
Drupal release: Drush 13 serves Drupal 10.2 and later and Drupal 11, Drush 14 serves Drupal 11.3 and later and Drupal 12,
and both need PHP 8.3 or later.
drupal/legacy-project is abandoned
Older material also describes drupal/legacy-project, a template that kept the Drupal 8.7 layout with index.php and
core/ at the project root, next to composer.json. The Drupal 11.4.0 release notes mark it as abandoned and say to
use drupal/recommended-project instead. Do not start a project from it, and move an existing one to the
web/ layout when convenient; the layout it produced exposes composer.json, composer.lock and the vendor
directory to whatever the web server is configured to serve.
Other ways to download Drupal
The User Guide describes three methods in chapter 3: a Composer download, a manual download of an archive from
drupal.org and the one-click installers of some hosts. A manual archive contains no vendor/ metadata for the
contributed code a real site needs and cannot be updated safely, so it is suitable only for a throwaway evaluation.
composer create-project drupal/cms creates a Drupal CMS site, which is ordinary Drupal plus a curated set of recipes;
see Drupal CMS and Site Templates. For the Meridian project the
plain recommended-project gives full control.
Step 2: run it in DDEV
DDEV is the local environment recommended by the Drupal community. It runs the web server, PHP, the database and Mailpit
(a mail catcher) in Docker containers and gives the project a .ddev.site address with a local HTTPS certificate. The
Docker concepts behind it are in Compose Fundamentals; the
environment as a whole is the subject of
Project Layout and Local Development, and containers for CI
and production of
Docker and Containers.
The official DDEV quickstart for a Composer-based Drupal 11 site is:
mkdir -p meridian && cd meridian
ddev config --project-type=drupal11 --docroot=web
ddev start
ddev composer create-project drupal/recommended-project
ddev composer require drush/drush
ddev drush site:install --account-name=admin --account-pass=CHANGE_ME -y
ddev launch
Source: DDEV Quickstart: Drupal
One detail matters. ddev composer create-project runs Composer inside the web container, with the PHP version of the
project, so the lock file is resolved against the PHP version the site will run on. A project directory created with
the host’s Composer, as in the first step above, works equally well: run ddev config inside it and then ddev start.
The ddev config options used here:
| Option | Meaning |
|---|---|
|
Selects DDEV’s Drupal 11 behaviour: the right nginx configuration, PHP settings and
database settings file. DDEV also offers |
|
The directory the web server serves. It must be |
|
Optional. The DDEV default is PHP 8.4, which Drupal 11.4 supports. Use 8.3 or 8.5 to match production. |
|
Optional. The DDEV default is MariaDB 11.8, which satisfies Drupal’s MariaDB 10.6 minimum.
Use |
The configuration DDEV writes
ddev config stores the settings in .ddev/config.yaml. The file below is what the Meridian project commits, with the
defaults written out so a colleague sees them:
name: meridian
type: drupal11
docroot: web
php_version: "8.4"
webserver_type: nginx-fpm
database:
type: mariadb
version: "11.8"
composer_version: "2"
nodejs_version: "22"
corepack_enable: false
additional_hostnames:
- collection.meridian
Source: DDEV: Configuration options
Commit .ddev/config.yaml. DDEV creates .ddev/.gitignore for its own temporary files. additional_hostnames adds
names under ddev.site; it matters later for the trusted host patterns.
DDEV also manages the database connection. On ddev start it writes web/sites/default/settings.ddev.php, which
defines $databases for the db container, and adds an include of it to settings.php, guarded so that it does nothing
outside DDEV (IS_DDEV_PROJECT). The file carries the #ddev-generated marker, is regenerated on every start and must
not be committed. Setting disable_settings_management: true in config.yaml, or ddev config
--disable-settings-management, stops DDEV from touching the settings files. Because the database connection comes from
settings.ddev.php, ddev drush site:install needs no --db-url.
Everyday DDEV commands
ddev start # start the containers
ddev describe # URLs, database credentials, Mailpit address
ddev launch # open the site in the browser
ddev drush status # Drush through the container
ddev composer require drupal/pathauto
ddev export-db --file=/tmp/meridian.sql.gz
ddev stop
Source: DDEV: Command line usage
Run drush and composer through ddev so they use the container’s PHP, extensions and database host. Running the
host’s composer works for the codebase but resolves dependencies against the host’s PHP version.
Step 3: install Drupal
There are two equivalent installers. The interactive installer is a web wizard useful to learn the steps and for a
hosting account without a shell. drush site:install is the tool for scripts, CI and every environment that is created
more than once.
The interactive installer
With DDEV running, ddev launch opens the site. With no settings.php and no database tables, Drupal redirects to
/core/install.php. The screens, in order, are:
-
Choose language. The installer downloads the translation files for any language other than English. Choose English for Meridian; Spanish is added later (see Multilingual).
-
Choose profile. Standard, Minimal or the demo profile (see the next section).
-
Verify requirements. The installer checks PHP, extensions, directory permissions and the database driver, and proceeds on its own when all is well. If a check fails, it lists the problem; fix it and reload.
-
Set up database. The database type, name, user, password, host and port. In DDEV these are
db,db,db,dband 3306 (shown byddev describe); the installer may already have the values because ofsettings.ddev.php. The Advanced options hold the table prefix, which should stay empty. -
Install site. A progress bar while the modules of the profile are installed and the tables are created.
-
Configure site. The site name, the site email address (the sender of system mail), the username, password and email of user 1, the default country and the time zone, and whether to receive update notifications. The password chosen here protects the most powerful account of the site.
-
A success page leads to the front page.
If the installer ends with a message that sites/default and settings.php are writable, make them read-only, as
described under the file permissions heading below.
drush site:install
The Drush command performs the same steps without the browser. With the project’s own DDEV environment:
export MERIDIAN_ADMIN_PASS="$(openssl rand -base64 18)"
ddev drush site:install standard \
--site-name="Meridian Museum" \
--site-mail=web@meridian-museum.test \
--account-name=admin \
--account-mail=web@meridian-museum.test \
--account-pass="$MERIDIAN_ADMIN_PASS" \
--locale=en \
-y
Source: Drush: site:install
The shell expands the variable before ddev runs, so the password never appears in a file. Without --account-pass
Drush generates a random password and prints it once; either way the login for later work is drush uli, below. The
important arguments and options of drush site:install (alias drush si) are:
| Argument or option | Meaning |
|---|---|
|
The name of an installation profile or the path to a recipe directory. It defaults to |
|
A Drupal-10-style database URL such as |
|
User 1. The name defaults to |
|
The site name and the sender address of system mail. |
|
The default language, as a short code. The language files must already exist. |
|
The directory under |
|
Install from the YAML files in the configuration sync directory. See below. |
|
A form value, written after the profile name, that disables update status emails during the installation. |
Pass the database URL, and above all the account password, from the environment or a secrets store in CI. A password on
the command line is visible in the process list and in shell history. The Drush option defaults can also be set in
drush/drush.yml (committed) with environment-specific values left out.
Installing from existing configuration
A team member who clones the project, or a CI job that builds a copy, should not click through a wizard and then import a configuration set. If the configuration export already exists, install directly from it:
ddev drush site:install --existing-config \
--account-name=admin --account-pass="$MERIDIAN_ADMIN_PASS" -y
Source: Drush: site:install
The install profile named in the exported core.extension is used, and every module and configuration object in the
sync directory is installed. A fresh site gets a new UUID, whereas an export carries the UUID of the site it came from;
the installer takes the UUID from the export in this mode, which is what makes a later config:import succeed. The
general workflow, including Config Split, is covered in
Configuration Management.
Installation profiles
A profile is a bundle of core and contributed modules, themes and configuration for one kind of site, and the installer runs it at the end of the "Install site" step. Core ships these profiles:
| Profile | What it installs |
|---|---|
Standard |
The general-purpose starting point: the modules to build and manage a site (for example Node, Taxonomy, Field UI, Views, Media, the administration toolbar and CKEditor 5 with the Basic HTML and Full HTML formats), the Olivero front-end theme and the Claro administration theme. The default profile of both installers. |
Minimal |
Only the modules needed to start a site. It installs no content types and few modules, and is used as the
base of tests and of custom profiles. Add what the site needs with Composer and |
Demo: Umami Food Magazine |
A complete demonstration site with content for a fictitious food magazine. Useful to see what a finished Drupal site looks like; never use it as the base of a real site. |
What changed in 11.4: Standard creates no Article and Page
For ten years every Standard installation contained two content types, Basic page (machine name page) and
Article (article), with their fields, the front page view that lists promoted articles, the tags vocabulary and
comments on articles. Drupal 11.4.0 removes the Article and Page content types from the Standard installation profile
and from the Standard recipe. The release announcement adds that the Standard profile is leaner and that comments
are disabled by default. A new 11.4 site therefore starts with no content types to add content to.
The consequences are practical:
-
Tutorials, videos and books written before 11.4 say "go to Add content and create an Article". On a fresh 11.4 site that page offers nothing until a content type exists. Books written for Drupal 10, such as Drupal 10 Masterclass (2023) and Drupal 10 Development Cookbook (2023), describe the Standard profile as it was before 11.4. Sites installed on Drupal 10 keep their types.
-
Code and recipes that depend on
node.type.articleornode.type.pageexisting break on new sites. Site templates that applied the core Article recipe needed a release that no longer depends on it. Functional tests that relied on the profile’s types must create their own content types. -
An existing site is not affected: the removal changes what a new installation creates, not what is already in the database or in an exported configuration.
For Meridian this is not a loss. The museum needs exhibition, event, news and basic pages with their own fields,
not a generic Article, so the site builder creates them explicitly in
Content Types and Fields, or applies the meridian_events recipe from
Recipes. Where a site does want the old types, create them by hand or from configuration; a
site template decides for itself which content types it provides.
Source: Drupal 11.4.0 release notes
Source: Drupal 11.4.0 announcement
Standard as a profile and as a recipe
Since the recipe system arrived in 10.3, the same contents exist in two forms. standard is an installation profile
that the installer runs; core/recipes/standard is a recipe that can be applied to an installed site, or used as the
argument of drush site:install. Installing extensions from a recipe is roughly twice as fast in 11.4. The longer
treatment, including recipe commands and the absence of an update path for an applied recipe, is on the
Recipes page.
What the installer writes
The installer changes three things in web/sites/default/, and one decision is the developer’s.
settings.php
The scaffold provides default.settings.php, a long file of documented settings. The installer copies it to
settings.php and appends the values it learned. After an installation settings.php contains, among the comments, lines
like these (the database values are those of DDEV; the salt is a random string generated per site):
$databases['default']['default'] = [
'database' => 'db',
'username' => 'db',
'password' => getenv('MERIDIAN_DB_PASSWORD'),
'prefix' => '',
'host' => 'db',
'port' => '3306',
'isolation_level' => 'READ COMMITTED',
'driver' => 'mysql',
'namespace' => 'Drupal\\mysql\\Driver\\Database\\mysql',
'autoload' => 'core/modules/mysql/src/Driver/Database/mysql/',
];
$settings['hash_salt'] = getenv('MERIDIAN_HASH_SALT');
$settings['config_sync_directory'] = '../config/sync';
$settings['trusted_host_patterns'] = [
'^meridian\.ddev\.site$',
'^collection\.meridian\.ddev\.site$',
];
The installer itself writes literal values for the database password and for hash_salt; the example above reads them
from the environment, which is how a real project keeps them out of Git (see
settings.php and Environments). Each setting matters:
-
$databasesis the connection, with the driver namespace and autoload path of the database module; since 10.2 the MySQL, PostgreSQL and SQLite drivers are modules in core. -
$settings['hash_salt']is mixed into one-time login links, form tokens and other hashes. It is generated by the installer. It must be secret and must be identical on every web server of the same site; it is never committed. -
$settings['config_sync_directory']is the directory holding the configuration export. The installer defaults to a directory insidesites/default/filesnamed with a random string, which is public-by-location and wrong for a real project. The Meridian project points it outside the web root,../config/sync, so that the export is versioned and not downloadable. -
$settings['trusted_host_patterns']is described in the next section. -
$settings['file_private_path'], not set here, is the private file directory; see File System and Private Files.
services.yml
The installer also creates sites/default/services.yml as a copy of default.services.yml if it does not exist. It
holds service container parameters that cannot be set elsewhere: the session cookie settings, the CORS configuration, the
Twig development switches, and others. A fragment that a developer machine commonly overrides is shown below:
parameters:
twig.config:
debug: true
auto_reload: true
cache: false
security.enable_super_user: true
Twig debugging belongs in a development-only file (see
Twig Templates and Debugging), never in the services file of a
production site. security.enable_super_user: false disables the unconditional privileges of user 1 (available since
10.3), a hardening step for production covered in
Security Hardening.
The files directory
The installer creates sites/default/files for public uploads, with a .htaccess that blocks script execution. The files directory is not in Git; back it up with the database. The temporary
directory defaults to the system temporary directory.
File permissions after the installation
The installer makes sites/default and settings.php writable so it can write them. Drupal’s status report warns until
they are read-only, because a writable settings.php lets a compromised PHP process change the database credentials.
chmod 555 web/sites/default
chmod 444 web/sites/default/settings.php web/sites/default/services.yml
Composer needs write access to web/sites/default when it updates default.settings.php. Either run chmod u+w
web/sites/default before composer update, or set $settings['skip_permissions_hardening'] = TRUE; in the
environments where updates run, which stops Drupal from re-tightening the permissions. In DDEV it is simplest to leave
the directory writable locally and apply the hardening in the production deployment.
Trusted host patterns
A web request carries a Host header, and Drupal uses it when it builds absolute URLs: in password-reset emails, in
redirects and in the canonical links of pages. An attacker who can send a forged Host header can poison those links
(HTTP Host header attacks). $settings['trusted_host_patterns'] is a list of regular expressions without delimiters. A
request whose host matches none of them receives HTTP 400 with the message "The provided host name is not valid for this
server".
// Production and staging, escaped dots, anchored on both ends.
$settings['trusted_host_patterns'] = [
'^meridian-museum\.org$',
'^www\.meridian-museum\.org$',
'^staging\.meridian-museum\.org$',
];
// A Docker or DDEV environment: the host name comes from the environment.
if (getenv('IS_DDEV_PROJECT') === 'true') {
$settings['trusted_host_patterns'][] = '^meridian\.ddev\.site$';
}
Four rules prevent the usual mistakes:
-
Anchor every pattern with
^and$. An unanchored pattern such asmeridian-museum\.orgalso matchesmeridian-museum.org.evil.example. -
Escape dots as
\.; an unescaped dot matches any character.preg_quote()does the escaping for values read from the environment. -
Include every name the site is reached by, including additional DDEV hostnames, the load balancer’s health-check host and the internal name of a reverse proxy. A health check that sends a host the patterns do not list gets a 400, which load balancers report as a failed check or a 502 (a known problem behind Kubernetes ingresses).
-
When the setting is absent, no check happens. The status report shows "Trusted Host Settings" as not enabled and recommends configuring it; treat that line as a production blocker, even though a local site works without it. Add the setting while installing, not months later.
Behind a reverse proxy or CDN, the real client address and protocol come from reverse_proxy settings, not from the
trusted host list; both belong in the hardening checklist. Since 11.3, proxies and CDNs in front of a site must also
pass the HTMX request headers (HX-Request, HX-Redirect) to Drupal.
First login and the first export
Logging in with drush uli
The password chosen in the installer is for emergencies. The way to log in during development is a one-time link:
ddev drush user:login # alias: drush uli, link for user 1
ddev drush uli --uid=2 admin/content
ddev drush uli --name=curator --no-browser
Source: Drush: user:login
The command prints a one-time login URL and opens it in the browser. Without an argument the link is for user 1. It accepts
--name, --uid or --mail to choose another account and an optional path to open after login. The URL is built from
the --uri option (-l), which defaults to https://default; inside DDEV the container knows the project address, but
in CI or in a script set --uri=https://meridian.ddev.site or the DRUSH_OPTIONS_URI environment variable, or the link
points at the wrong host. The link is valid for a limited time and works once, and a login link is as sensitive as the
password: do not paste it in a chat or an issue. If logged out, drush uli also works when the password is lost.
User 1 is the first user created by the installation and holds unconditional privileges: it skips every access check.
Use it for the installation and for emergencies, create a separate administrator role for daily work (see
Users, Roles and Permissions), and, on production, set the
security.enable_super_user container parameter to false as described above.
First look at the site
After the first login, open Reports then Status report (/admin/reports/status). A new site normally shows only
these warnings: a missing trusted host setting, writable settings.php, and perhaps a missing cron run. Fix them now.
Then open Extend (/admin/modules) to see the installed modules, Appearance for the themes and Configuration for the
administration categories. The user interface is covered in
Admin UI Navigation and Admin Themes.
Registration is set to "Administrators only" on new installs since 11.1, and /user/logout requires a CSRF token since
10.3, so a new site is not open to self-registration. Leave it that way until there is a reason.
The first configuration export
The final step turns the installation into a reproducible project. The configuration that the installer and the profile created is exported to the sync directory and committed with the code:
mkdir -p config/sync
ddev drush config:export -y
git add composer.json composer.lock config .ddev/config.yaml
git status
Source: Drush: config:export
config:export (alias cex) writes one YAML file per configuration object to the directory named by
$settings['config_sync_directory']: system.site.yml, core.extension.yml (the installed modules and themes, and the
profile), and the roles, text formats and fields. Opening the files is the quickest way to understand what a profile did.
system.site.yml also holds the site UUID. A configuration import is accepted only by a site with the same UUID,
so every copy of the Meridian site (a colleague’s DDEV, the CI job, staging, production) must be created from the same
export, using drush site:install --existing-config, or have its UUID set to the project’s with drush config:set
system.site uuid. Installing a second copy from scratch and importing the first copy’s configuration fails with an
error about mismatched UUIDs.
The day-to-day flow is: change the site in the administration interface, run drush config:export, review the diff in
Git and commit; other environments run drush deploy, which applies updatedb, config:import, the cache rebuild
and the deploy hooks. See Configuration Management and
Deployment and CI/CD for the full flow, and
Drush for the other commands.
Common errors and pitfalls
| Symptom | Cause and fix |
|---|---|
404 or the web server’s default page on first launch |
The document root is not |
"Failed opening required vendor/autoload.php" |
|
The installer cannot write |
|
The installer starts again after a successful installation |
|
"The provided host name is not valid for this server" |
A request host is not covered by |
|
Drush does not know the site URL. Pass |
Composer cannot write |
Drupal made the directory read-only. Use |
|
The target was installed from scratch rather than from the export. Install
with |
"Add content" lists no content types on 11.4 |
The Standard profile no longer creates Article and Page. Create the content types the site needs. |
A tutorial’s |
Obsolete. Start from
|
Composer refuses to install Drush, or Drush fails to bootstrap |
The Drush major does not match the Drupal release. Use Drush 13 or 14 (see the compatibility notes above). |
Version notes and corrections of older material
Older books and tutorials describe an installation that no longer matches Drupal 11.4:
-
Drupal 10 Development Cookbook (2023) and Drupal 10 Masterclass (2023) target Drupal 10.0 and 10.1, PHP 8.1, MariaDB 10.3 or MySQL 5.7 and Drush 12. The minimums are higher today: PHP 8.3, MariaDB 10.6, MySQL 8.0 and Drush 13 or 14. Drupal 10 itself ends on 2026-12-09, so a new site is started on 11. Both books also assume that Standard creates Article and Page, which stopped in 11.4.
-
A Composer example in Drupal 10 Masterclass sets the
platform.phpvalue ofcomposer.jsonto 8.0.7, below the minimum of Drupal 10 itself. Leaveplatformunset or set it to the PHP version of production (8.3 or later). -
Drupal 10 Development Cookbook uses the DDEV setup with MariaDB 10.3. DDEV now defaults to MariaDB 11.8, which Drupal 11 accepts; keep the version aligned with production.
-
Tutorials that install
drupal/drupalordrupal/legacy-project, or that unpack an archive, or that upload a module from the user interface, describe methods that are unsupported (the UI upload and update were removed in 11.0 to 11.2) or abandoned (11.4). -
The installer screens in the books are the same as in 11.4; the changes are in what the profiles provide. The list of further corrections is on Where Older Books and Tutorials Are Outdated.
Further points for the 11.4 line:
-
Drupal 11.4.0 also adopts the Symfony Runtime component for the bootstrap, so
index.phpbecomes simpler. A site that has its own front controller script keeps working until at least Drupal 12. The same release introduces an experimental native command line tool,./vendor/bin/dr, whose maintainers describe a transitional period for Drush; the installation in this page uses Drush, which is the supported tool for Drupal 11. -
drupal/core-recommendedstopped pinning Guzzle, Twig and the Symfony polyfills in 11.4, which makes acomposer update "drupal/core-*" --with-all-dependenciesthe correct update command for core. -
Drupal 12 changes the installation requirements to PHP 8.5, Symfony 8 and argon2id password hashing by default. A site installed on 11.4 now is ready for that move; Drupal 12 refuses to upgrade from 11.2 or earlier.
Checklist for the Meridian Museum installation
-
Create the project with
composer create-project drupal/recommended-project:^11.4 meridian. -
Configure DDEV with
--project-type=drupal11 --docroot=weband commit.ddev/config.yaml. -
ddev composer require drush/drushandddev composer require --dev drupal/core-dev. -
Install with
ddev drush site:install standard(or the Minimal profile) with the password from an environment variable. -
Set
config_sync_directoryoutside the web root and settrusted_host_patterns. -
Create the content types, since Standard no longer provides Article and Page.
-
Log in with
ddev drush uli, review the status report, makesettings.phpread-only in the deployed environments. -
Run
ddev drush config:export, commitcomposer.lockandconfig/sync.