Creating A Custom Docker Image For Your Drupal Website

Previously I shared how to get started with Docker and Docker Compose to start hosting your own projects, but what if the project you want to host is your own custom code? If you want to host your own code in a Docker Image that means you will need to create your own Docker Image for that. I will discuss how to do this with your own Drupal site, but for all other things you'd want to create a Docker Image for Docker has more than enough documentation out there to help you do it for whatever you'd need.

Into To Dockerfiles

What even is a Dockerfile? A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. It's a plain text file that on each line (called a layer in Docker terminology) is a different command Docker needs to run to build the image.

A very basic Dockerfile example is the following:

FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py

Each instruction creates one layer:

  • FROM creates a layer from the ubuntu:18.04 Docker image.
  • COPY adds files from your Docker client’s current directory.
  • RUN builds your application with make.
  • CMD specifies what command to run within the container.

On the first line we have a FROM this is important and very useful, you don't need to create everything from scratch but can start with a base and build your image on top of that. The colon 18.04 refers to what tag of Ubuntu we are using, which is a lot of times used to choose a version number or a flavor of the image. So we are starting with Ubuntu version 18.04. Then COPY all files in the current path on our system into the /app path in our image, this is used to take your code and put it into the image. After that we RUN a make command in the /app directory which will build whatever is needed and defined in that makefile. Finally we run CMD which is executing a bash command of python /app/app.py which is starting up our code making it run.

That's a very basic sample but shows you everything you need to build upon making more complicated Dockerfiles. Whenever you're starting see if there is a good base to build upon, if you cannot find one Ubuntu server is probably a decent starting place.

Creating A Dockerfile

Luckily for us trying to build a custom image for Drupal there is a Drupal Docker image that already has everything we need installed to run Drupal. Much like the Ubuntu Image you can use the tag of what version of Drupal you need to run. The image itself doesn't have any code for Drupal in it, it's just a setup server with everything needed to run a Drupal site. Depending on your needs you can probably get away with doing a FROM on Drupal COPY in your files and then CMD apache2-foreground and be done. The Dockerfile I normally run does a little more than that as we found we like to modify the image a little bit more. Our finished Dockerfile is below (Also available as a Gist):

FROM drupal:8.7

RUN apt-get update && apt-get install -y libxml2-dev imagemagick mysql-client --no-install-recommends

RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli

RUN { \
    echo 'memory_limit = 196M'; \
    echo 'display_errors = Off'; \
    echo 'post_max_size = 64M'; \
    echo 'file_uploads = On'; \
    echo 'upload_max_filesize = 64M'; \
    echo 'max_file_uploads = 20'; \
} > /usr/local/etc/php/conf.d/codekoalas-settings.ini

RUN sed -i 's/\/var\/www\/html/\/var\/www\/html\/docroot/g' /etc/apache2/sites-available/000-default.conf

RUN rm -rf /var/www/html/*

COPY ./ /var/www/html

RUN apache2-foreground

What this Dockerfile does layer by layer

  • FROM Drupal:8.7 to get a base server that has PHP and other dependencies needed to run a Drupal site
  • RUN running an apt-get update and install of a couple extra packages we run into needing beyond what the base Drupal image gives us.
  • RUN that command tells Docker to install and enable mysqli, that's needed if you want to run any Drupal or Drush commands on your server ever, the command line needs to know how to talk to mysql.
  • RUN We are creating a php conf file with some settings we like over the default php settings. If you ever need larger file uploads or more memory you could edit this line right here.
  • RUN We are taking the default Apache site conf and changing the path from /var/www/html to /var/www/html/docroot as we build all of our sites in docroot.
  • RUN We are deleting any files currently in /var/www/html as Apache normally gives you a plain index.html and we don't want that.
  • COPY Copying our Drupal site into /var/www/html
  • RUN starting Apache so the site will be served.

Once you have that file created you should run the docker build command locally to test it and see what happens. The Docker Build command will be docker build -t user/project:optionaltag . ran from inside your directory with the Dockerfile. That -t part is you naming your image, like how we are calling drupal in the from layer we need to name our image so we can reference it later when telling Docker to spin it up. After running that command if it doesn't fail you should be good to go!

From there you could move that build off to a pipeline by setting up some pipeline config with your repo or you could create a cron that pulls your repo and rebuilds every so often, whatever works for you. You can see the Dockerfile we run for our Drupal sites at Code Koalas on our Github repo Koality Drupal.

Spinning up your image

In my last post I showed you how to get started with Docker Compose to host your projects. Using that knowledge to get your new site setup using that image your new docker-compose.yaml file will look like the following:

version: '2'
services:
  nginx-proxy:
    container_name: nginx-proxy
    image: jwilder/nginx-proxy:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - /home/joshfabean/letsencrypt/certs:/etc/nginx/certs
      - /etc/nginx/vhost.d
      - /usr/share/nginx/html
      - ./nginx-proxy/nginx.tmpl:/app/nginx.tmpl
  letsencrypt:
    image: jrcs/letsencrypt-nginx-proxy-companion
    container_name: letsencrypt
    volumes_from:
      - nginx-proxy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /home/joshfabean/letsencrypt/certs:/etc/nginx/certs:rw
  mydrupalsite:
    container_name: mydrupalsite
    image: fabean/mydrupalsite:latest
    environment:
      - VIRTUAL_HOST=mydrupalsite.joshfabean.com
      - LETSENCRYPT_HOST=mydrupalsite.joshfabean.com
      - DATABASE_NAME=default
      - DATABASE_USERNAME=user
      - DATABASE_PASSWORD=user
      - DATABASE_HOST=db
    restart: always
    volumes:
      - ./mydrupalsite:/var/www/html/docroot/sites/default/files

The main thing to notice is on the line for image we put in what we typed in on the docker build command in the -t section. If you have an image with that name locally it will just pull that image, if not it will check other docker registries your are logged into or dockerhub for that image.

Wrapping Up

Building your own docker images isn't that hard and is super powerful. You are now no longer restricted to only hosting pre-built services in Docker but now you can host anything you can dream up in Docker. If you have questions or need more help Docker has great documentation or you can reach out to me on Twitter and hopefully I can point you in the right direction.