Summary
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.
Objectives
- Isolate the PHP process inside a container.
- Serve static content from Apache web server.
- Proxy PHP requests from Apache to the PHP container for execution.
- 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' services: postgres: image: postgres:13.3 environment: - POSTGRES_PASSWORD=F6qWcb volumes: - postgresdb:/var/lib/postgresql/data networks: postgresdb: {} restart: always networks: postgresdb: external: true volumes: postgresdb: 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 127.0.0.1:9001:9000 --network postgresdb \ -v /srv/vhosts/drupal9:/opt/drupal \ --user 110:115 \ drupal9
But, it may be easier to write another docker-compose.yml file:
version: '3' services: drupal9: image: drupal9 ports: - "127.0.0.1:9001:9000" networks: postgresdb: {} volumes: - /srv/vhosts/drupal9:/opt/drupal restart: always user: "110:115" networks: postgresdb: 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://127.0.0.1:9001/opt/drupal/web/$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 </Directory> </VirtualHost>
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:
mysitename: 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