Drupal (or any PHP application) inside a container

Submitted by Erik Wegner on Sun, 06/06/2021 - 22:06


A web server is capable of running multiple web sites. Every web site has its own dependencies and requirements. So you might end up running multiple PHP versions next to each other, which can be done like this. How is isolation done today? Well, we will use containers.


  1. Isolate the PHP process inside a container.
  2. Serve static content from Apache web server.
  3. Proxy PHP requests from Apache to the PHP container for execution.
  4. Source code, web site files and any dynamic files stay in the underlying server file system.

Getting the files

There is an image available which contains a prepared PHP environment and source files.

Let's us this image to create the local file structure:

$ mkdir -p /srv/vhosts/drupal9
$ docker run --rm drupal:9.1.7-php8.0-fpm-alpine tar -cC /var/www/html/ . | tar -xC /srv/vhosts/drupal9

Run a database

This time a PostgreSQL database will be used from a container. Use this docker-compose.yml as a starting point:

version: '3.8'

   image: postgres:13.3
     - postgresdb:/var/lib/postgresql/data
     postgresdb: {}
   restart: always

   external: true

   external: true

This file will use a persistent volume and a network to connect, which must be created prior to running the Postgres service.

$ docker network create postgresdb
$ docker volume create postgresdb
$ docker-compose up -d

Create the database

Connect to the running PostgreSQL instance with the command docker-compose exec postgres psql -U postgres and create the user and database:

create database drupal9db;
create user drupal9user with encrypted password 'pHkJCpUAX6ZwwUQQ';
grant all privileges on database drupal9db to drupal9user;

Modify user and group mapping

The user inside the container should match to the outside file system. Create a system user and a system group and note the user id (e.g. uid=110) and group id (e.g. gid=115). Then create a Dockerfile with the following content to adopt these ids inside the container:

FROM drupal:9.1.7-php8.0-fpm-alpine3.12

RUN addgroup -g 115 -S www-drupal9 && adduser -S www-drupal9 -G www-drupal9 -u 110

Build the container: docker build . -t drupal9

Please note: you have to manually update the image if a new PHP version is released. The Drupal code is managed separately outside the container image.

Run the php process

Now we could start the container and connect to http://localhost:9001 to check some basic access:

docker run --rm --name drupal9 -d -p --network postgresdb \
    -v /srv/vhosts/drupal9:/opt/drupal \
    --user 110:115 \

But, it may be easier to write another docker-compose.yml file:

version: '3'

    image: drupal9
      - ""
      postgresdb: {}
      - /srv/vhosts/drupal9:/opt/drupal
    restart: always
    user: "110:115"

    external: true

Connect the web server to the PHP process

For an Apache process, the following virtual host definition can be a starting point:

<VirtualHost *:80>
 DocumentRoot /srv/vhosts/drupal9/web
 ServerName drupal9.example.com
 ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://$1
 ServerAdmin webmaster@example.com
 CustomLog /var/log/apache2/drupal9-access_log combined
 ErrorLog /var/log/apache2/drupal9-error_log
 <Directory /srv/vhosts/drupal9/web>
  AllowOverride All
  Options FollowSymLinks
  Require all granted

Install modules

Connect to the running container: docker exec -it drupal9 /bin/sh

Execute the commands to install a Drupal module:

export COMPOSER_HOME="$(mktemp -d)";
composer require 'drupal/honeypot:^2.0'

Drush management

Create the file /srv/vhosts/drupal9/drush/sites/self.site.yml with these lines:

  root: /opt/drupal
  uri: http://drupal9.example.com

Update code base

Execute the commands to update the Drupal code, themes and modules:

export COMPOSER_HOME="$(mktemp -d)";
composer update --no-dev
drush @mysitename updb