You are here

First steps with Docker - 4

logoPrevious article

Setting up a Drupal server

After having played a little bit with Docker, it's time to try something more serious: I'll try to set up a Drupal web server, relying on following containers:

  • an Apache container
  • a MySQL container
  • a data container for Drupal web site files
  • a data container for Drupal MySQL database

In order to have data-only containers with minimal disk space overhead, I'll build them on a Busybox image. But this will require additional containers, used for initialization (there could be some other solutions but, being a Docker beginner, I didn't find a good one).

The working directory I'll use, on my Linux development machine, is /root/dockerTests/. [I upgraded my Mac to Yosemite, and it seems that this had a bad side effect of docker2boot, so I switched back to Linux, before spending some time on cleaning Docker configuration on the Mac.]

So, let's go!

Build of Apache container

  • create the subdirectory /root/dockerTests/apache2
  • inside this directory, create following Dockerfile:
#DOCKER-VERSION 1.3.0

# We'll rely on Ubuntu 14.04 LTS.
FROM ubuntu:14.04

# Install required software.
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \
    apache2 \
    libapache2-mod-auth-mysql \
    libapache2-mod-php5 \
    nano \
    php5 \
php5-gd \ php5-mcrypt \ php5-mysql # Activate Drupal web site. ADD drupal.conf /etc/apache2/sites-available/ RUN a2dissite 000-default RUN a2ensite drupal # Expose port 80 to the host machine EXPOSE 80 ENTRYPOINT ["/usr/sbin/apache2ctl"] CMD ["-D", "FOREGROUND"]
  • create following drupal.conf file:
<VirtualHost *:80>
    DocumentRoot /var/www/drupal/

    LogLevel warn
    ErrorLog /dev/stdout
    CustomLog /dev/stdout combined
</VirtualHost>
  • build the Apache container:
docker build -t pascalbod/apache2:v1 .

Build of Drupal web site container

  • create the subdirectory /root/dockerTests/drupalweb
  • inside this directory, create following Dockerfile:
#DOCKER-VERSION 1.3.0

# Minimal image.
FROM busybox:ubuntu-14.04

# Declare volume for web site directory.
VOLUME /var/www/drupal

CMD ["true"]
  • build the container:
docker build -t pascalbod/drupalweb:v1 .

Initialization of Drupal web site container

  • run the Drupal web site container:
docker run -d --name drupalweb pascalbod/drupalweb:v1
  • create the subdirectory /root/dockerTests/drupalwebinit
  • inside this directory, create following Dockerfile:
#DOCKER-VERSION 1.3.0

# We'll rely on Ubuntu 14.04 LTS.
FROM ubuntu:14.04

# Install required software.
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \
    wget

# Script used at run time to initialize Drupal web site container.
ADD run.sh /run.sh
RUN chmod u+x /run.sh

CMD ["/run.sh"]
  • create following run.sh file:
#!/bin/bash

# Download Drupal
echo "=> Downloading Drupal."
wget http://ftp.drupal.org/files/projects/drupal-7.33.tar.gz
tar -xvzf drupal-7.33.tar.gz
rm drupal-7.33.tar.gz

# Move files to intended location
echo "=> Moving files."
cd drupal-7.33/ mv * .htaccess .gitignore /var/www/drupal/.
cd ..
rmdir drupal-7.33/

# Prepare settings.php and file permissions
echo "=> Preparing settings.php and file permissions."
cd /var/www/drupal/ cp sites/default/default.settings.php sites/default/settings.php chmod a+w sites/default/settings.php chmod a+w sites/default

echo "=> Done."
  • build container:
docker build -t pascalbod/drupalwebinit:v1 .
  • run it, to initialize Drupal web site container:
docker run -d --volumes-from drupalweb pascalbod/drupalwebinit:v1
  • to get some information about how this run went, first get the container id, using docker ps -a. Then, display logs: docker logs <containerId>.

Build of Drupal database container

  • create the subdirectory /root/dockerTests/drupaldb
  • inside this directory, create following Dockerfile:
#DOCKER-VERSION 1.3.0

# Minimal image.
FROM busybox:ubuntu-14.04

# Declare volume for database directory.
VOLUME /var/lib/mysql

CMD ["true"]
  • build the container:
docker build -t pascalbod/drupaldb:v1 .

Build of Drupal database initialization container

  • create the subdirectory /root/dockerTests/mysqlinit
  • inside this directory, create following Dockerfile:
#DOCKER-VERSION 1.3.0

# We'll rely on Ubuntu 14.04 LTS.
FROM ubuntu:14.04

# Install required software.
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \
    mysql-client \
    mysql-server

# Set up MySQL configuration so that we can access it from outside.
ADD my.cnf /etc/mysql/conf.d/my.cnf
# Add script that initializes MySQL data directory.
ADD run.sh /run.sh
RUN chmod a+x /run.sh

# Expose MySQL port to the host machine.
EXPOSE 3306
  • create following my.cnf file:
[mysqld]
bind-address=0.0.0.0
  • create following run.sh file:
#!/bin/bash
if [ ! -d /var/lib/mysql/mysql ]; then
    # Setup MySQL data directory.
    echo "=> Creating MySQL data directory."
    mysql_install_db --datadir=/var/lib/mysql
fi

echo "=> Running MySQL Server."
exec mysqld_safe --datadir=/var/lib/mysql
  • build the container:
docker build -t pascalbod/mysqlinit:v1 .

Initialization of Drupal database container

  • run the Drupal database container:
docker run -d --name drupaldb pascalbod/drupaldb:v1
  • create the subdirectory /root/dockerTests/drupaldbinit
  • inside this directory, create following Dockerfile:
#DOCKER-VERSION 1.3.0

# We'll rely on Ubuntu 14.04 LTS.
FROM ubuntu:14.04

# Install required software.
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \
    mysql-client \
    mysql-server

# Script used at run time to initialize Drupal database container. ADD run.sh /run.sh RUN chmod u+x /run.sh CMD ["/run.sh"]
  • create following run.sh file:
#!/bin/bash

echo "=> Initializing MySQL data."
mysql_install_db

echo "=> Starting MySQL server."
/usr/bin/mysqld_safe &

echo "=> Waiting for MySQL server."
while (true); do
    echo "     waiting for 3 s..."
    sleep 3s
    mysql -uroot -e "status" > /dev/null 2>&1 && break
done

echo "=> Setting MySQL root password."
PASS='pw0drupal0'
mysql -uroot -e "update mysql.user set Password=password('$PASS') where user = 'root';"

echo "=> Creating drupal database and user."
mysql -uroot -e "CREATE DATABASE drupal CHARACTER SET utf8 COLLATE utf8_general_ci;"
PASS='pw1drupal1'
mysql -uroot -e "CREATE USER 'drupal'@'%' IDENTIFIED BY '$PASS';"
mysql -uroot -e "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, \
    CREATE TEMPORARY TABLES ON drupal.* TO 'drupal'@'%';"

echo "=> Done."                                                                                           
  • build the container:
docker build -t pascalbod/drupaldbinit:v1 .
  • run it to initialize Drupal database container:
docker run -d --volumes-from drupaldb pascalbod/drupaldbinit:v1

Build of MySQL container

  • create the subdirectory /root/dockerTests/mysql
  • inside this directory, create following Dockerfile:
#DOCKER-VERSION 1.3.0

# We'll rely on Ubuntu 14.04 LTS.
FROM ubuntu:14.04

# Install required software.
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \
    mysql-client \
    mysql-server

# Set up MySQL configuration so that we can access it from outside.
ADD my.cnf /etc/mysql/conf.d/my.cnf

# Expose MySQL port to the host machine.
EXPOSE 3306

ENTRYPOINT ["mysqld_safe"]
    • create following my.cnf file:
[mysqld]
bind-address=0.0.0.0
  • build the container:
docker build -t pascalbod/mysql:v1 .

Run of MySQL container

  • the Drupal database container has been started earlier, for initialization (see above) so we don't need to start it here.
  • start the MySQL container with Drupal database:
docker run -d --volumes-from drupaldb --name db pascalbod/mysql:v1

Set up of Drupal web site

At this stage, we have a running Drupal web site container, a running Drupal database container and a running MySQL container. Now let's start Apache container:

docker run -d -p 23001:80 --volumes-from drupalweb --link db:db pascalbod/apache2:v1

Now, Drupal configuration web page is available at <myHost>:23001 (I chose a random port, for configuration duration).

When asked for database information, click on ADVANCED OPTIONS, and set database host to db, the link alias we provided to above command.

End of Drupal configuration

A few additional adjustements should be performed: some file ownership settings, addition of some Drupal modules, etc. One way to do this is to use the exec command on target container:

docker exec -i -t <containerId> bash

This creates a new bash session in the container, which I use to end Drupal configuration.

Conclusion

I'm quite sure the way I used Docker to set up my Drupal configuration is not an optimal one. I tried to use one container per service, and one container per type of persistant data. This design choice seems to comply with some Docker container patterns. But the way I implemented it is quite complex. It wasn't so easy to set up. What's more, I wonder whether that's the best answer to my needs. I'll spend some more time using Docker over the next few months, and I'll discover a lot more than I know today Smile. This should allow me to optimize this complex set up... As Chinese people say: 熟能生巧 (skill comes from practice).

05-Jan-2015 - Update: see this article for the Drupal configuration I finally chose, for a live Drupal site.