Here's some feedback on my first real use of Docker where I used it to easily assemble a development environment for the Drupal 8 redesign of the drupal.fr website as well as the Drupalcamp base.
As a recent Docker user, please feel free to contact me or post a comment to correct me if needed.
The environment will include:
- an Apache server with PHP 7
- a MySQL server
- a Redis server
- a SolR server (drupal.fr only)
- a Varnish server
- a mail catcher
What is Docker?
Docker is a virtualisation solution based on Linux Containers (LXC) (EDIT: Docker now uses https://runc.io instead of LXC, thanks for your feedback :) ). It allows applications to be run in isolated containers.
Containers do not contain an operating system. They rely on the Linux kernel installed on the host machine, hence the fact that it is not possible to emulate a Windows or Mac system in a Docker container.
To build a container, simply write the build steps in a Dockerfile and then build a Docker image from that Dockerfile.
At each step, Docker records the result of the step in a binary with a checksum, as well as the identifier of the previous step in order to link the binaries together. The steps are called "Layers". This avoids the need to rebuild stages if they have remained unchanged in the Dockerfile. A Docker image is based on another image that already contains much of what you need. Alternatively, you can always start from the scratch image, which is an empty image from which the images of the various Linux distributions, for example, are derived. It all depends on the ratio of control to preparation time that you want to devote to making your images.
Once the image is ready, it can be run. Anything done in this container will be lost when it is destroyed.
It is then possible to push these Docker images to the docker hub https://hub.docker.com or to a locally installed Docker registry. Or you could very well just keep them on your machine.
Docker Compose
Docker Compose is an overlay to Docker. It allows you to easily orchestrate a set of containers together.
For example, with Docker alone, the command to launch several containers with all the options that go with them, link them together, etc. is several lines long and quite unreadable.
It's always possible to put it in a versioned script. But with Docker Compose, it's possible to put all these instructions in a docker-compose.yml file (easily overloaded) that's easier to read and simply run the "docker-compose up" command.
Moreover Docker Compose will:
- create a subnet for each docker-compose.yml file (or for each project if there is one docker-compose.yml file per project),
- Place containers in this subnet and give them aliases in this subnet,
- name the containers to be easily accessible (otherwise Docker gives a random name to the container if no name is specified)
- if a container already exists, Docker Compose knows whether to destroy it and rebuild another if the image has changed or whether it can restart the existing container.
Installing on Debian
Installation instructions for Docker Engine: https://docs.docker.com/engine/installation/linux/debian
Installation instructions for Docker Compose: https://docs.docker.com/compose/install
The docker-compose.yml file
version: "2"
services:
web:
image: florenttorregrosa/docker-drupal-dev:php7
depends_on:
- mysql
- redis
- mail
- solr
volumes:
- .:/project
networks:
default:
ipv4_address: 172.18.0.2
varnish:
image: florenttorregrosa/docker-varnish:latest
depends_on:
- web
environment:
VCL_CONFIG: /varnish-drupal/varnish.vcl
volumes:
- ./conf/varnish:/varnish-drupal
networks:
default:
ipv4_address: 172.18.0.3
mysql:
image: mysql:5.6
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_USER: drupal
MYSQL_PASSWORD: drupal
MYSQL_DATABASE: drupal
volumes:
- ./data/db:/var/lib/mysql
networks:
default:
ipv4_address: 172.18.0.4
solr:
image: solr:6.1.0-alpine
volumes:
- ./conf/solr:/solr-drupal/conf
entrypoint:
- docker-entrypoint.sh
- solr-precreate
- drupal
- /solr-drupal
networks:
default:
ipv4_address: 172.18.0.5
redis:
image: redis:3.2.1-alpine
networks:
default:
ipv4_address: 172.18.0.6
mail:
image: djfarrelly/maildev
networks:
default:
ipv4_address: 172.18.0.7
networks:
default:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.18.0.0/16
gateway: 172.18.0.1
Customising IPs
It is not mandatory to fix the subnet as well as the IP addresses of the containers. What is generally done (from what I've seen) is port forwarding.
Personally, I prefer to avoid port forwarding so that I can easily run several environments with an identical configuration but on different subnets (Drupalcamp and drupal.fr base).
In fact, Docker comes with a default subnet accessible on 172.17.0.1 and if you do a port redirect, you'll be able to access your container's port on 172.17.0.1:port_redirected. Except that port redirects aren't really my thing, especially if I want to be able to use XDebug easily.
So I fixed the subnet as well as the IP addresses to avoid having to inspect the subnets to find out which IP to access the site on. Because the startup order of the containers in your docker-compose.yml is not fixed, even though Docker Compose starts the containers in the right order based on their indicated dependencies.
Web container
Initial image in order to have PHP 7 and Apache: https://hub.docker.com/_/php
Image in order to have an environment with Drush, Composer and necessary PHP libraries : hub.docker.com/r/florenttorregrosa/docker-drupal
Image to have an environment with development tools like Xdebug, Coder and the Drupal Console : hub.docker.com/r/florenttorregrosa/docker-drupal-dev
Images forked from https://github.com/icilalune then which I slightly modified.
We mount a volume on the project folder. And we expect the site to be in a www subfolder relative to the project folder.
Mounting a volume means that the folder on the host machine will be accessible in the container. Example:
volumes : - .:/project
Signifies that the current folder will be mounted in the /project folder in the container.
Mounting a volume on the sources makes it easy to make changes in the sources and for these changes to be taken into account in the container without having to rebuild it.
For containers intended for production, the sources can be added to the container and only the files folder can be mounted on a volume, for example.
Mysql container
https://hub.docker.com/_/mysql
In order to keep the database data, we mount a volume. And we define environment variables.
It would be nice to have a version with Alpine, which is an ultra-light Linux distribution.
Redis container
https://hub.docker.com/_/redis
Use of the Redis module. Modification of the settings.php file in order to use this cache backend.
I had to compile PhpRedis in the web container, as at the moment PhpRedis is not in the packages for PHP 7. An alternative to PhpRedis is the predis library, but the Drupal 8 version of the Redis module does not yet support it.
I also looked at the MemCached side but at Drupal 8 level, the modules to support it are still in the development phase and you would also have had to compile or go through detours to have PHP memcache with PHP 7. What's more, the official Redis container is more widely used than the Memcached one, so I preferred to go with Redis.
SolR container
We mount a volume in order to make the SolR configuration used accessible in the container. To obtain the SolR configuration, I retrieved the default configuration and added the one provided by the Search API Solr Search module.
In order to create a "core" SolR, we run the solr-precreate command provided in the SolR image, specifying to it the name of the core to be created, in this case drupal, and the location of the configuration files.
I would have liked to mount a volume in order to keep the index data as for the MySQL container, but, I don't know if this was due to my tests, it posed rights problems and the core didn't create well.
Varnish container
hub.docker.com/r/florenttorregrosa/docker-varnish
Mix between https://github.com/million12/docker-varnish which I find good entry point level for interacting with Varnish and https://github.com/nlhkh/docker-varnish to see how to install Varnish on Alpine.
We mount a volume in order to add its own Varnish conf in the container and we indicate in the container environment variables the path to the Varnish conf file.
I used the Varnish configuration shown on the Varnish Purge module where the thing to change is the host where you just specify "web".
At the Drupal site level, as the Varnish module wasn't yet ready for Drupal 8, I used the Purge and Varnish Purge modules (which is actually called varnish_purger beware).
But I think I either misconfigured the modules or misconfigured the Varnish as the Varnish caches were not being flushed by Drupal. In any case in the HTTP headers I constantly had a "Purge-Cache-Tags" to MISS. I'll have to get back to that.
MailContainer
https://hub.docker.com/r/djfarrelly/maildev
RAS. Very nice interface to view intercepted mails. En TODO, peut être voir les options de manière à avoir un relay de paramétré et faire reprendre aux mails leurs envoi initial.
Next steps
I find the images for the web server a bit large (between 800 and 900 MB). I'd love to see the use of Alpine to reduce this size.
In addition, I'd like to separate PHP from Apache so that I have different containers for PHP and the web server. The Web server could then be changed more easily to use Nginx for example.
For production use, I'd take a look at Swarm or Traefik.
Documentation:
Acknowledgements:
Thanks to garphy and Anaethelion for their introduction of Docker and Docker Compose at Drupalcamp Nantes 2016, with a conference on Saturday and the Docker workshop on Sunday. This allowed me to have a base docker-compose.yml file
Note 10/06/2018: There has been a reorganisation of my Docker images. This reorganisation is detailed in the article Reorganisation of my Docker images and implementation of automated builds. Links in this article pointing to deleted Git or Docker repositories have been removed.