DDEV Multisite

Run multiple sites in DDEV with a shared Drupal codebase and contrib modules.

Why?

With a multisite, the sites all share the core and contrib code.

This means that all sites are updated at the same time.

This could be a convenience, if everything is well tested, but could cause headaches when a change for one site disrupts the others.

You may also want to run a multisite for local development, where you can have a collection of modules and quickly experiment with ideas.

Drupal Multisite

A Drupal multisite is a single Drupal installation that hosts multiple websites.

https://www.drupal.org/docs/getting-started/multisite-drupal

By default, Drupal looks for a settings.php file in sites/default/settings.php.

To set up a multisite, $sites in /sites/sites.php is an associative array pointing host names to a subdirectory in /sites.

To add a new site, create the directory and add the site to /sites/site.php.

You will need a working host name and database.

sites.php

if (str_ends_with($http_host, '.mantra.ddev.site')) {
  $sites[$http_host] = explode('.', $http_host)[0];
}

Set $sites['SITE.mantra.ddev.site'] = 'SITE'.

Add additional hostnames with:

$sites += [
  'www.drupalarchitect.info' => 'drupalarchitect',
];

sites/SITE/settings.php

Include shared.settings.php, and set any site specific settings.

include "{$app_root}/sites/shared.settings.php";

$settings['trusted_host_patterns'][] = '^score\.andrewsclasses\.com$';

sites/SITE/settings.local.php

Use for any site + environment specific settings.

Not checked into git.

sites/development.settings.php

Set database and other settings for lando/ddev.

Incorporates example.settings.local.php

Incorporates settings.ddev.php.

# Local dev hash.
require "{$app_root}/sites/development.hash.php";

# Local host names.
if (in_array($local_env, ['ddev', 'lando'])) {
  $settings['trusted_host_patterns'] = [
    "^{$site}\.mantra\.*\.site$",
  ];
}

# Database settings
$databases['default']['default'] = array (
  'database' => '',
  'username' => '',
  'password' => '',
  'prefix' => '',
  'host' => '',
  'port' => '3306',
  'driver' => 'mysql',
);
# Lando/DDEV.
switch ($local_env) {
  case 'lando':
    $host = $site == 'default' ? 'database' : $site;
    $databases['default']['default']['host'] = $host;
    $databases['default']['default']['database'] = $site;
    $databases['default']['default']['username'] = $site;
    $databases['default']['default']['password'] = $site;
    break;
  case 'ddev':
    $databases['default']['default']['host'] = 'db';
    $databases['default']['default']['database'] = $site;
    $databases['default']['default']['username'] = $site;
    $databases['default']['default']['password'] = $site;
    break;
}

sites/shared.settings.php

/**
 * Get site name from path.
 */
$site_path_split = explode('/', $site_path);
$site = array_pop($site_path_split);

/*
 * Default settings.
 *
 * https://git.drupalcode.org/project/drupal/-/blob/11.x/core/assets/scaffold/files/default.settings.php?ref_type=heads
 */
$default_settings = $app_root . '/core/assets/scaffold/files/default.settings.php';
if (is_readable($default_settings)) {
  require $default_settings;
}

/*
 * File paths.
 */
$settings['file_assets_path'] = 'assets';
$settings['config_sync_directory'] = "../config/{$site}";
$settings['file_private_path'] = "../private/{$site}";

/*
 * Filefield temp path - used for uploaded files before saving with adjusted name.
 */
$config['filefield_paths.settings']['temp_location'] = "private://filefield_paths_temp";

/*
 * Local environment.
 */
$local_env =
  getenv('IS_DDEV_PROJECT') ? 'ddev' : (
  getenv('LANDO_INFO') ? 'lando' : ''
);

/*
 * Prod urls.
 *
 * Additional hostnames defined in sites/SITE/settings.php.
 */
if (!$local_env) {
  $settings['trusted_host_patterns'] = [
    "^{$site_path}\.mantra\.network$",
  ];
}

/*
 * Development settings.
 */
if ($local_env) {
  $development_settings = $app_root . '/sites/development.settings.php';
  if (is_readable($development_settings)) {
    require $development_settings;
  }
}

/*
 * Include site local settings.
 */
$local_settings = $app_root . '/' . $site_path . '/settings.local.php';
if (is_readable($local_settings)) {
  require $local_settings;
}

DDEV Config

.ddev/config.yaml

Set additional_hostnames to a wildcard subdomain.

additional_hostnames: ['*.mantra']

This will set up wildcard certs on DDEV so the browser doesn't show a SSL warning.

Databases

A basic option is to use a database prefix in each site's settings.php.

However, this could cause problems with DDEV's import-db command if remote sites aren't also prefixed.

Another option is to use the site name slug as the database name.

A new database can be created with drush @ddev.SITE sql:create, but aliases need to be set up first.

Drush Aliases

Create /drush/sites/ddev.site.yml.

'*':
  root: /var/www/html
  uri: https://${env-name}.mantra.ddev.site

This will set up a @ddev.SITE drush wildcard alias that works for all local sites.

When the alias is used, the uri matches a definition in sites.php to determine the site directory.

Drush use @site

You can tell drush to use a site, so the alias doesn't have to be used in subsequent calls.

drush site:set @site or drush use @site.

To set this up in DDEV, add this environment variable in .ddev/config.yaml:

web_environment:
 - DRUSH_SHELL_PID=PERMANENT
Resources