How I use docker with drupal
Why use docker for drupal?
As you may have seen from my earlier posts, I've been making use Docker a lot recently for neatly packaging the dev environments of projects I'm working on.
I've already espounded the advantages of docker in other posts, but I'd thought it might be useful to share (and document for my own benefit), how I've been running my own personal drupal projects using docker. It's also useful to document and explain the reasoning behind a particular setup and note things I found that didn't work for me.
To quickly recap, I'm using docker in my own projects to:
- Switch dev machines easily. I frequently move between Mac OS, Windows and Linux between different machines at home and work, so this minimises the differences in running projects between them.
- Fix software versions easily (e.g. mysql, php etc.)
- Set up a new machine quickly. It's surprising how often you need to either setup a new machine or re-install an old one. Getting dev projects set up on these machines is now a hassle free process.
- More easily deploy (in future). I hope to test some container based hosting systems in the future, which would mean my dev and live environments can be identical.
There are already some great pre-built drupal images out there on the docker registry, but partly because none quite fitted my workflow, and partly to better understand how it works, I setup my own.
When you first start building docker containers, it's very tempting to build a project container in the same way as a traditional VM. My first attempt was like this, with a
Dockerfile containing an installation of php, mysql and the frontend tools I was working on. This worked, but was slow, and wasn't using many of the advantages of separating services that docker offers. My second attempt, and the setup I am using now, uses docker-compose to define separate containers for each of the services my drupal project requires.
Splitting services and using docker-compose was a big advantage. It was much easier to do environment-based debugging, and to setup and maintain different containers. It also made it super easy to update software versions independently. Using images from the docker hub made this very easy.
I can roughly split the main components of the project into the following services:
- php Needed to run drupal
- mySQL database backend for drupal
- nodeJS Used for compiling my front-end assets and some build scripts
So it's clear I need at least a separate container for each of these systems. Here's how I split these using docker compose We can elaborate a little on the requirements for each of these:
- Based on the
php-apacheimage in the docker registry. I chose this over a pre-built drupal image, as I wanted to know exactly what was being installed and how it worked.
- I needed more than the very basic php image, since drupal would require some extra php extensions such as imagemagick. These were setup in a local dockerfile which extends the
- The default
upcommand starts an apache server exposed to the containers host on port 80
- I also want to be able to run drush commands. I considered moving this to it's own container, but it requires the exact same environment and database setup as drupal, so didn't make sense to make another container for it.
docker-compose up drupalto start a local server
docker-compose run drupal drush pm-update
drupal: build: docker/drupal/ volumes: - ./project:/var/www/html links: - db ports: - "8000:80"
FROM php:5.6-apache # Install modules RUN apt-get update && apt-get install -y \ libfreetype6-dev \ libjpeg62-turbo-dev \ libmcrypt-dev \ libpng12-dev \ && docker-php-ext-install iconv mcrypt pdo_mysql mbstring \ && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ && docker-php-ext-install gd RUN a2enmod rewrite # To avoid wierd double iconv error RUN rm /usr/local/etc/php/conf.d/ext-iconv.ini # Install drush RUN mkdir -p /usr/src/drush WORKDIR /usr/src/drush RUN curl -OL https://github.com/drush-ops/drush/archive/6.6.0.tar.gz RUN tar -xvf 6.6.0.tar.gz --strip-components=1 RUN rm 6.6.0.tar.gz RUN chmod u+x ./drush RUN ln -s /usr/src/drush/drush /usr/bin/drush WORKDIR /var/www/html
- Uses the community base mysql image with no changes
- Environment variables set in the docker-compose config determine the database settings
- The default
upcommand starts the DB server, which is exposed to the host on port 3306
- I also added a second container base don the same image to be able to use the mysql client tools to easily access the DB on the command line. It links to the server container and environment variables are used to make supply authentication.
- I also mount my db_dumps folder as a volume into into the mysql tools container to allow easy db dumps or imports from the host machine
docker-compose up dbTo run the DB server
docker-compose run mysqltools mysqlfor a mysql command line
docker-compose run mysqltools mysqldumpto dump to the mounted project folder on the host.
db: image: mysql:5.5 environment: MYSQL_ROOT_PASSWORD: 'root' MYSQL_DATABASE: 'grahamgilchrist' ports: - "3306:3306" mysqltools: image: mysql:5.5 volumes: - ./db_dumps:/db_dumps working_dir: /db_dumps links: - db environment: MYSQL_DATABASE: 'grahamgilchrist' MYSQL_HOST: db
- Uses my own custom nodeJS container which install packages within the container at build time.
- Packages are installed from
package.jsonin the project root
- GruntJS is installed as a global node package using a custom
Dockerfilein the project
- The default
upcommand starts the
grunt watchdevelopment task which auto-recompiles frontend assets as they change
- I also added another container for installing bower packages needed by frontend tasks. This is based on a community image in the docker registry. The bower container mounts the current directory and installs packages directly into the mounted project folder from the host.
docker-compose up gruntstarts the grunt watch command
docker-compose run grunt grunt sassRuns the grunt sass task
docker-compose run bower install
grunt: build: docker/grunt command: grunt watch volumes: - ./:/usr/src/app bower: image: blinkmobile/bower:1.3.12 environment: bower_allow_root: true bower_analytics: false working_dir: /data volumes: - ./:/data
FROM grahamgilchrist/node-onbuild:0.12 #install grunt-cli RUN npm install -g firstname.lastname@example.org \ && npm cache clear
By and large, I've been happy with how this setup has worked out, but there's a few things I'd still like to improve:
- Add a container for deployment scripts. Currently these still rely on specific programs on the container machine such as rsync, ftp etc., and it would be great to have these in containers also.
- Utilise drush build to build the entire site from a simple modules manifest. This will avoid having to check in a fully-built site, and with docker will allow to build the site from scratch using only a minimal source code repository.