A minimal and fast Drupal environment (part 2)

Submitted by christophe on Tue, 22/11/2022 - 00:12


In this first post, we are setting up a minimal development environment in a few minutes for fast prototyping, evaluation of a project or testing.

I love solutions like Lando or DDEV, they are shining for complex stacks. But I also like to have other options to run a Drupal website locally. Since 2 years, I find myself going back to the PHP built-in server whenever I can for projects development, because

  • it's very lightweight to use
  • it's fast to start / to switch between different projects
  • it's resource efficient: less CPU / RAM / disk space used
  • network configuration is simplified
  • it can be used to run E2E tests with Playwright, Cypress, ... by spinning up in no time a setup with mock data / SQLite

It allows to cover most of development requirements actually. For other subsystems (e.g. caching with Redis, proxies, ...) I'm generally happy with dev/stage environments for that.

Fabien Potencier is explaining how to use the PHP built-in server in his introduction to Symfony. He's combining it with containers, so a mixed approach is also an option.


What do we need in most projects?

Docker can be slow, especially on macOS, so let's focus on this operating system.

PHP versions

Switching PHP version is a must have, fortunately there is a brew package for that: https://github.com/shivammathur/homebrew-php

Follow instructions from the maintainer. Once installed, switching PHP version is a one-liner.

Example to switch from 8.1 to 8.2:

brew unlink [email protected] && brew link [email protected]
# Check PHP version with
php -v

Dont' forget to restart your server if it's running.

Most extensions/modules required by Drupal (dom, gd, zip, ...) are already bundled, so not much to do there. Others (including e.g. ImageMagick) can be found in this list https://github.com/shivammathur/homebrew-extensions

Quick note for Ubuntu/Debian

Extensions have to be explicitly installed to run multiple PHP versions.


sudo apt install software-properties-common gnupg2 -y add-apt-repository ppa:ondrej/php apt-get update
sudo apt install php8.1 php8.1-fpm php8.1-cli
# Add extensions, here with SQLite
sudo apt install php8.1-curl php8.1-zip php8.1-dom php8.1-gd php8.1-mbstring php8.1-sqlite3
# Then do the same for each version of PHP, e.g. 8.2
sudo apt install php8.2 php8.2-fpm php8.2-cli
sudo apt install php8.2-curl php8.2-zip php8.2-dom php8.2-gd php8.2-mbstring php8.2-sqlite3

Switch PHP version

sudo update-alternatives --config php


The main benefit of using another DB engine than SQLite is to allow to sync other environments database with Drush aliases, ... That might not be necessary most of the time, but it's always nice to have this option.

I'm generally using MySQL, as MariaDB can be slow on macOS in some cases. I have no feedback about other engines so far.

Again, there is a brew package. Install it with

brew install mysql
brew services start mysql


Get the Solr formula (default 8.x is fine, 7.7 is not maintained anymore).

brew install solr

Install Search API Solr and download the Solr configuration from here http://localhost:8888/en/admin/config/search/search-api/server/solr ("Get config.zip") then extract it.

Start Solr with

solr start

With the solr_8.x_config extracted archive, create a core. Example for my_project

solr create_core -c my_project -d solr_8.x_config -n my_project

On settings.local.php (or whatever overrides settings locally) override the connector configuration

$config['search_api.server.solr']['backend_config']['connector_config']['host'] = ''; 
$config['search_api.server.solr']['backend_config']['connector_config']['path'] = '/';
$config['search_api.server.solr']['backend_config']['connector_config']['core'] = 'my_project';
$config['search_api.server.solr']['backend_config']['connector_config']['port'] = 8983;
$config['search_api.server.solr']['backend_config']['connector_config']['http_user'] = '';
$config['search_api.server.solr']['backend_config']['connector_config']['http']['http_user'] = '';
$config['search_api.server.solr']['backend_config']['connector_config']['http_pass'] = '';
$config['search_api.server.solr']['backend_config']['connector_config']['http']['http_pass'] = '';
$config['search_api.server.solr']['name'] = 'Homebrew Solr';

On the Search API index: Rebuild tracking information then re-index.


Install it with

pecl install xdebug

Configure the current version of PHP, get the active ini file path with

 php --ini

Set in the ini file


Mail catcher

There are various solutions out there, I'm currently using Mailhog.

Install it

brew install mailhog 

Start it

mailhog start

Configure your php.ini with

sendmail_path = "/opt/homebrew/bin/mailhog sendmail [email protected]"

If you are using transactional mail services by default on other environments (like Mailgun, Sendgrid, Mandrill, ...) make sure to also override the transport and not set SMTP. Also, be extra cautions if you are using Symfony Mailer as the transport can be overridden by each policy.


Override Symfony Mailer "All" policy to use Sendmail

$config['symfony_mailer.mailer_policy._']['configuration']['email_transport']['value'] = 'sendmail';

If you get Expected response code "220" but got empty code, add

$config['symfony_mailer.mailer_transport.sendmail']['configuration']['query']['command'] = ini_get('sendmail_path') . ' -t';



When to use Apache or Nginx

When the scope of the task is redirects map, server specific configuration, ... it's still possible to do the local setup but that requires extra effort to run and maintain for several virtual hosts, ... and perhaps that's the good moment to think about Docker.

Domain path

For multilingual sites, it happens that the language negotiation is by domain and not by path, I fix it with

$config['language.negotiation']['url']['source'] = 'path_prefix';

Switch between Docker based / PHP built-in

Some settings might differ, like the database connection, ... Most Docker based solutions will come with their own environment variables (that can be printed in the CLI container with printenv). In this case, a simple test of one of the environment variables with getenv() can be used to switch the settings. It could be done in the same settings.local.php or 2 settings files could be loaded based on this test.

Multiple projects

It's possible to run multiple projects at the same time.  Just use another port.

# Defaults to :8888
drush serve
# Start on a new port with e.g.
drush serve :9999


As localhost is used, cookies will be shared (even if it's a different port). Which can be annoying when switching projects. For this, I'm using Firefox Multi-Account Containers.

Images are not displayed

  • Check permissions on the sites/default/files directory
  • Perhaps ImageMagick is configured, possibly do settings override to GD or install ImageMagick
  • Verify Stage File Proxy configuration override (if any)
  • Image Style Warmer or drush image:derive could be handy if Stage File Proxy is not used


Photo by Joe on Unsplash