Simple Drupal Docker Development Environment

Simple Drupal Docker Development Environment cover image

Using Docker as part of your development environment is a smart and progressive move. It lets you, and other developers, easily and consistently setup a local development environment anytime without the hassle of going through multiple steps and documentation, and most of all, provides a high degree of Environment Parity.

I found a lot of great examples on how to leverage Docker for Drupal development, some of which I've used personally, so much so that I've decided to try and come up with the simplest and most straightforward setup I can. The idea is to reuse and extend existing solutions while having minimal custom code as much as possible. In doing so, I hope to provide a simple starting point for myself, and hopefully others as well, to build future projects on.

If you prefer to jump into the code, check out an example Drupal project I created that uses this approach.

Composer template for Drupal projects

This setup assumes that the Composer template for Drupal will be used. The template provides a starter kit for managing your site dependencies with Composer. It also comes with sensible default packages, structure, and tools, such as drush and Drupal console.

Drupal image

For this setup, we'll only need one Dockerfile, which will be used to build the main image for our Drupal container.

FROM drupal:latest

# Install MySQL client so we can use drush from inside the container, among other things.
RUN apt-get update && apt-get install -y mariadb-client git

# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Make a minor adjustment to the DocumentRoot so that it points to where our application codes are.
RUN sed -i 's/DocumentRoot \/var\/www\/html/DocumentRoot \/var\/www\/html\/web/g' /etc/apache2/sites-available/000-default.conf

# Mount point for the source codes.
VOLUME /var/www/html

As I've mentioned earlier, I aim to reuse existing solutions where possible so where better to start than to use the official Docker images for Drupal. This image installs the basic Drupal requirements as well as apply some recommended settings, which means less code for us too.

In summary, this Dockerfile does the following:

  • Use the latest official Docker image for Drupal.

  • Install mariadb-client so that we can connect to the database using the command line from inside the container. This also allows us to use drush commands properly from inside the container.

  • Finally, since we're using the Composer template, Drupal will be located under the web sub-directory so we'll need to make a minor adjustment to the default DocumentRoot that comes with most Debian-based OS.

Docker Compose

Now that we have our basic Dockerfile to define our Drupal container, we then need to define other services that we'll need for our application, such as a database service, by sourcing other containers.

If you haven't come across, Docker Compose basically allows you to piece together multiple containers that work together to form your application. It does so via a YAML file where we define and configure the necessary services that our application needs.

For our setup, our Compose file, named docker-compose.yml, looks like this:

version: '3.3'

services:
  # The database service.
  db:
    image: mariadb:latest
    volumes:
      - ./data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: drupal
      MYSQL_USER: drupal
      MYSQL_PASSWORD: drupal
  # The Drupal service.
  drupal:
    build: .
    depends_on:
      - db
    ports:
      - "8080:80"
    volumes:
      - .:/var/www/html
    restart: always
    environment:
      MYSQL_DATABASE: drupal
      MYSQL_USER: drupal
      MYSQL_PASSWORD: drupal
      MYSQL_HOST: db

From the code, you might have guessed that we have 2 services - one that serves our application(drupal) and a database service (db).

The database service

In the spirit of reusing existing solutions where possible, we use the latest official MariaDB Docker image instead of creating a custom image of the database ourselves. Furthermore, the image already allows us to set certain settings necessary for our application, such as the database credentials, without the need to further extend and customize the image.

Another option worth noting is the volumes option. This lets us persist the database by mounting a path located in the host machine into the container. If the container is destroyed or rebuilt, our database is safe since it resides in the host machine. For this setup, our database files will be located in the data sub-directory of the application root.

The Drupal service

Instead of using an existing image, like what we did with the database service, we want to use our own Dockerfile to build the Drupal container, which we've defined earlier.

You may have also noticed that we defined the environment option in a similar way as what we did in the database service. Being able to set our containers' environment variables is one of the best benefits of using Docker. This allows us to pass the same database credentials we used in our database service into our Drupal service. We can then change the database variable initialization in the settings.php to:

<?php

$databases['default']['default'] = [
  'database' => getenv('MYSQL_DATABASE'),
  'username' => getenv('MYSQL_USER'),
  'password' => getenv('MYSQL_PASSWORD'),
  'host' => getenv('MYSQL_HOST'),
  'prefix' => '',
  'port' => '3306',
  'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',
  'driver' => 'mysql',
];

In most Drupal projects, you wouldn't want to commit the settings.php file since this file will contain sensitive information such as your database credentials along with other non-sensitive settings. By using environment variables and stripping away only the sensitive parts and replacing them with environment variables, you can now safely commit this file and be assured that the rest of your team will have the same settings.

We've used other options in our Composer file such as ports, volumes, and restart. Some may look self-explanatory but you can find out more about them from the Compose file reference.

Networking

You might be curious how can containers communicate with each other? In our case, how can the Drupal service access the database service? According to the docs:

By default Compose sets up a single network for your app. Each container for a service joins the default network and is both reachable by other containers on that network, and discoverable by them at a hostname identical to the container name.

It means that we can use the service names we defined in the Compose file as a hostname. In our drupal service, we pass the hostname db using the environment variable MYSQL_HOST, which is then used in the settings.php.

Setting up a local environment

With our Docker configurations out of the way, any developer can spin up a local development environment with just 2 steps!

1. Run the app

$ docker-compose up

This command will build and launch your application and database containers.

2. Install Drupal

This step only needs to be done once. Once you've installed Drupal, the database will persist even when the containers are destroyed or rebuilt since the database files are located in the host machine in the data directory.

SSH into the Drupal container

First you'll need to know the name of the Drupal container. You can do so by running docker ps command. The name of the container will be indicated under the NAMES column. You'll see an output similar to the one below:

$ docker ps
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                  NAMES
b94ec144233a        drupal-docker_drupal   "docker-php-entrypoi…"   2 hours ago         Up 2 hours          0.0.0.0:8080->80/tcp   drupal-docker_drupal_1
d384c1b920ab        mariadb:latest         "docker-entrypoint.s…"   6 hours ago         Up 2 hours          3306/tcp               drupal-docker_db_1

In the example output above, the name of the Drupal container is drupal-docker_drupal_1. So to SSH into the Drupal container, you'll need to run docker exec -it drupal-docker_drupal_1 /bin/bash.

Once inside the container, you should immediately see yourself in the /var/www/html directory. From here you can either do a fresh install or import a database dump.

Install dependencies via Composer

First of all, install all dependencies via composer by running:

composer install

Fresh install via Drush

To do a fresh install, we can use drush by running:

./vendor/bin/drush si standard --site-name="My Drupal 8 site"

Once done, you'll see an output similar to:

root@b94ec144233a:/var/www/html# ./vendor/bin/drush si standard --site-name="My Drupal 8 site"

 You are about to DROP all tables in your 'drupal' database. Do you want to continue? (yes/no) [yes]:
 >

 [notice] Starting Drupal installation. This takes a while.
 [success] Installation complete.  User name: admin  User password: kaEzmrBwAV

Importing a database dump

To import a database dump, you can run:

./vendor/bin/drush sql-cli < mysql-dump.sql

Finally, to access your local site, go to http://localhost:8080.

Closing

Gone are the days when a developer has to go through multiple lines of documentation and steps in order to set up a local development environment. In some cases, this can even take a full day. With the options we have today, such as Docker, setting up a local development environment has never been easier.

My original aim was a bare minimum setup that can be easily changed to suite the needs of a project and, so far, this is what I can come up with. With this setup, I hope that one can easily setup a local Drupal development environment anytime.

While I liked working with Docker, another viable option that I've used before and liked is DrupalVM, which uses Vagrant. I plan to write about this approach too so stay tuned!