Leveraging Linux containers for Migrating Drupal 6 to Drupal 8
Ahnee. Let me start by saying this article/tutorial (artorial, tutarticle!?), this artorial could be titled “Docker for Development, Leveraging Linux containers” and be applied to virtually any stack you want.
I’m using Drupal because I recently began a Drupal 6 (D6) to Drupal 8 (D8) website migration.
Drupal is a free, open-source content management system (CMS) with a large, supportive community. It’s used by millions of people and organizations around the globe to build and maintain their websites.
Both versions run on a LAMP stack but with different versions of PHP. D6 reached it’s end-of-life in early 2016, almost a year before PHP 7 was released. Consequently it requires PHP 5.6 and lower to run.
According to the docs D8 will run on PHP 5.5.9+, but any version less than 7.1 is not recommended. If running Drupal 8 on PHP 5.6 you go, only pain will you find.
So how do you run PHP 5 and PHP 7 simultaneously on the same host? Spin up a pair of VMs? Slip in Nginx and PHP-FPM alongside Apache? The former option is acceptable. The latter borders on sadomasochism.
The answer is, of course, Docker.
This Guy’s Setup
I use Linux as my primary Operating System (OS). Ubuntu 18.04 loaded with the latest packages of Apache 2.4, MySQL 5.7, and PHP 7.2 from Ubuntu’s official repositories.
My Ubuntu host is similar enough to the production environment where D8 is to be deployed that I created an Apache Virtual Host (vhost) and MySQL database then downloaded D8 using a composer template and installed it with Drupal Console.
What is the Drupal Console? The Drupal CLI. A tool to generate boilerplate code, interact with and debug Drupal. From the ground up, it has been built to utilize the same modern PHP practices which were introduced in Drupal 8.
This is where the fun begins. But first I’ll explain the differences between a VM and a Container.
VMs and Containers Compared
There are many VM providers. VirtualBox, QEMU, and VMWare to name a few. A VM contains a full OS and kernel running in isolation (so lonely) from the host. It is indistinguishable from a proper desktop or server.
Before booting, VMs are allocated resources such as RAM and CPU cores. The VM provides a hardware emulation layer between the guest OS and the host, which looks and feels like bare metal as far as the guest OS is concerned.
Because they resemble physical desktops and servers, VMs require significant amounts of the host’s system resources. In contrast to Containers this severely limits the amount of VMs that can run concurrently on a single host. The boot-up and shutdown time is also the same as a physical machine; another significant difference.
Containers offer the advantages of VMs without the overhead. By virtualizing at the kernel level containers share resources with the host. Many more containers can run simultaneously on a single machine compared to VMs.
Containers worry more about resource prioritization rather than resource allocation. In other words, a container says “When will you run this process for me niijikiwenh?” rather than “How much CPU do I have to run this process?”.
Finally, starting up or shutting down a Container is super fast *whoooooosh*. Because Containers share a fully loaded kernel with the host, they can be started, perform a task, then shut down within milliseconds. Containers are the mayflies of the tech world. On the flip side, they can last until an act of God brings them down along with your house.
I messed with Docker years ago but only recently gave it a prime time slot in my regularly scheduled programming.
Docker makes it easier to create, deploy, and run an application in a lightweight and portable Container by packaging it up with it’s dependencies and shipping them out as an image.
I’ve only skimmed the surface of Docker and don’t fully understand how it works under the hood. I’m also anxious to check out a competitor such as Canonical’s LXD or CoreOS/Redhat’s Rkt. All in good time.
Docker loads an image containing an OS and the software needed to do a job into a container. In other words, an image contains your applications runtime environment.
Creating an image is rather painless, depending on the complexity of your requirements. You write a set of instructions in YAML saved as a Dockerfile, then run docker build. Our tutorial requirements are simple and can be met with pre-existing images pulled from Docker Hub, a Docker image registry service.
While I can find an image which contains Apache, PHP, and MySQL all together, we’re going to follow best practices and separate the web server from the database into 2 containers where they will communicate through an internal subnet created by Docker.
Finally, containers are designed to be disposable, with the ability to run as a single instance on a single host, or to be scaled as multiple instances distributed over server clusters. By default, data is written to a containers writable layer and will be disposed of along with the container.
Volumes and Bind Mounts are Dockers two options for persisting data. I can, and maybe will, write an entire post to fully explain them. But to keep it brief I will say Volumes are managed by Docker, isolated from the host, can be mounted into multiple containers, and stored on remote hosts or in a cloud provider.
Bind Mounts are a file or directory on the host machine mounted into a container. They are a good option to share configuration data, source code, and build artifacts during development. In production, your build artifacts are best copied directly into the image, configuration in the environment, and source code unnecessary.
Volumes are recommended for storing data used and generated by containers. Bind mounts depend on the host machine’s directory structure, hampering container portability.
In this tutorial we will get by with a bind mount.
That’s Docker so far as I understand it. I hope you find it beneficial and are encouraged to begin developing with Docker. I invite you to join in on the fun below and follow the step-by-step instructions to get down and dirty with Docker.
Let’s setup Drupal 6 within containers in Ubuntu. If you are not using Ubuntu don’t fret, the only step you need to change is “Install Docker”. In that case refer to https://docs.docker.com/install/#supported-platforms for instructions to install Docker on your OS.
If you catch any mistakes or see room for improvement please contact me. Otherwise, wacka wacka.
sudo (or root) — Required to install and run Docker. To run docker commands without sudo or root you must add your user account to the docker group.
Table of Contents
- Install Docker
- Add user to docker group
- Start Docker
- Pull MySQL image
- Start container
- Download Drupal 6
- Pull Apache/PHP image
- Enable mod_rewrite
- Allow Overrides
- Start container with a bind mount
- Install Drupal
Open a terminal and ensure your package lists are up to date then install Docker (aka Docker Engine):
$ sudo apt update
$ sudo apt install docker.io -y
<heaps of output>
Processing triggers for systemd (237-3ubuntu10.3) ...
Docker Engine is comprised of three major components:
- dockerd (Server) — a daemon that is a long-running background process
- docker (Client) — a command line interface
- REST API — specifies interfaces that programs can use to communicate with the daemon
Kick-start the aforementioned long-running background process:
$ sudo systemctl start docker
Optionally, tell systemd to start docker on system boot:
$ sudo systemctl enable docker
Docker is now installed and ready for use. Check if docker is running:
$ systemctl is-active docker
Pull MySQL image
Now that you have docker running you can pull your first image. Start with MySQL version 5.6 (without :5.6 specified, :latest is implied):
$ sudo docker pull mysql:5.6
5.6: Pulling from library/mysql
802b00ed6f79: Pull complete
30f19a05b898: Pull complete
3e43303be5e9: Pull complete
94b281824ae2: Pull complete
51eb397095b1: Pull complete
3f6fe5e46bae: Pull complete
b5a334ca6427: Pull complete
115764d35d7a: Pull complete
719bba2efabc: Pull complete
284e66788ee1: Pull complete
0f085ade122c: Pull complete
Status: Downloaded newer image for mysql:5.6
Start the DB container, options are explained below:
$ sudo docker run -d \
-e MYSQL_ROOT_PASSWORD=drupalroot \
-e MYSQL_DATABASE=drupal6 \
-e MYSQL_USER=drupal \
-e MYSQL_PASSWORD=drupal6pass \
- -d — Start the container as a background process.
- --name —Will be referenced during Drupal install. A random name will be assigned if one isn’t provided.
- -e — Set’s an environment variable. MySQL will be configured with values passed in by the environment.
Output (will differ):
Download Drupal 6
D6 is available for download from the official Drupal site packaged as a gzipped tarball. You can grab it with wget:
$ cd ~
$ wget https://ftp.drupal.org/files/projects/drupal-6.38.tar.gz
$ tar -xzf drupal-6.38.tar.gz
Verify drupal-6.38 exists in your home directory:
$ if test -d ~/drupal-6.38; then echo “It exists”; fi
Pull Apache/PHP image
Now pull a docker image of Ubuntu 14.04 LTS with Apache 2, PHP 5, and Composer from https://hub.docker.com/r/nimmis/apache-php5/:
$ sudo docker pull nimmis/apache-php5
Using default tag: latest
latest: Pulling from nimmis/apache-php5
c2c80a08aa8c: Pull complete
6ace04d7a4a2: Pull complete
f03114bcfb25: Pull complete
99df43987812: Pull complete
9c646cd4d155: Pull complete
5c017123b62e: Pull complete
8f95d9abec41: Pull complete
c46de42c66c3: Pull complete
9a19620cecad: Pull complete
5c62abdf642f: Pull complete
Status: Downloaded newer image for nimmis/apache-php5:latest
DocumentRoot and Incoming Port
The containerized Apache’s default DocumentRoot is /var/www/html, which we will bind mount to the D6 files in ~/drupal-6.38.
Because I already have Apache on the host I have to bind the container’s port 80 to something else. I’m using 10080 but you can choose almost any other free port.
$ sudo docker run -d \
-p 10080:80 \
-v ~/drupal-6.38:/var/www/html \
An explanation of what’s between run and nimmis/apache-php5:
- -d — Daemonize, run in background.
- -p 10080:80 — Bind host port 10080 to container port 80.
- -v ~/drupal-6.38:/var/www/html — Bind host directory to container directory.
- — name="drupal-app" — Name the container instance for convenience.
- --link="drupal-mysql" — Link to the MySQL container so Drupal can communicate with the database.
Open http://localhost:10080 in a browser (xdg-open is a program that will open a file or URL in the preferred application as set in your OS):
$ xdg-open http://localhost:10080
Tada! The Drupal 6 installation page should be open in a browser, served from within a set of Docker containers.
To complete the installation use the database name (drupal6), username (drupal), and password (drupal6pass) as set in the Start MySQL step. Under Advanced Options, set the Database host to the name of your MySQL container, drupal-mysql.
When you have finished with Drupal 6 shut down the containers and delete them from the host.
Stop the containers:
$ sudo docker container stop drupal-app drupal-mysql
Remove the containers:
$ sudo docker container rm drupal-app drupal-mysql
Verify the containers have been deleted:
$ sudo docker container ls
That’s it, move along. Baamaapii.
Bonus: docker group
To display a list of groups you belong to is simple:
roosta adm cdrom sudo dip plugdev lpadmin sambashare
Add your user account to the docker group:
$ sudo usermod -aG docker $USER
You must log out then log back in before it takes effect.