Continuing on in our series of Docker articles, we’ll be covering what Dockerfiles look like and how you can write your own. If you’re not up to speed yet, here’s our first three articles to get you started:
In our last article, we went through the process of deploying a WordPress based system. This was a clean, vanilla system, meaning that there weren’t any modifications made to WordPress. While this provides a great starting point, it’s not overly practical when it comes to typical development workflow. Most seasoned developers have set of tools, default plugins and default settings they load with every new build. In this article, I’ll show you what can be done to modify the existing system and speed up your development.
What is a Dockerfile?
So, our first question simply is, what is a Dockerfile? When you ran the docker run command and specified WordPress, Docker uses this file to build the image itself. The Dockerfile is essentially the build instructions to build the image. The advantage of a Dockerfile over just storing the binary image (or a snapshot / template in other virtualisation systems) is that the automatic builds will ensure you have the latest version available. This is a good thing from a security perspective, as you want to ensure you’re not installing any vulnerable software.
Here’s the official WordPress Dockerfile:
FROM php:5.6-apache RUN a2enmod rewrite # install the PHP extensions we need RUN apt-get update && apt-get install -y libpng12-dev libjpeg-dev && rm -rf /var/lib/apt/lists/* && docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr && docker-php-ext-install gd RUN docker-php-ext-install mysqli VOLUME /var/www/html ENV WORDPRESS_VERSION 4.2.2 ENV WORDPRESS_UPSTREAM_VERSION 4.2.2 ENV WORDPRESS_SHA1 d3a70d0f116e6afea5b850f793a81a97d2115039 # upstream tarballs include ./wordpress/ so this gives us /usr/src/wordpress RUN curl -o wordpress.tar.gz -SL https://wordpress.org/wordpress-${WORDPRESS_UPSTREAM_VERSION}.tar.gz && echo "$WORDPRESS_SHA1 *wordpress.tar.gz" | sha1sum -c - && tar -xzf wordpress.tar.gz -C /usr/src/ && rm wordpress.tar.gz && chown -R www-data:www-data /usr/src/wordpress COPY docker-entrypoint.sh /entrypoint.sh # grr, ENTRYPOINT resets CMD now ENTRYPOINT ["/entrypoint.sh"] CMD ["apache2-foreground"]
Lets pull this apart piece by piece.
FROM
FROM php:5.6-apache
The first part is the FROM command, which tells us what image to based this off. This is the multi-layered approach which makes Docker so efficient and powerful. In this instance, it’s using the php:5.6-apache Docker image, which again references a Dockerfile to automate the build process. If you look at the PHP Dockerfile, you’ll see that it does all of the work of building the PHP environment with Apache for you. You can use this to start any PHP based project too, so you’ll see it referenced by other systems like Joomla, Magento and similar too.
RUN
RUN a2enmod rewrite # install the PHP extensions we need RUN apt-get update && apt-get install -y libpng12-dev libjpeg-dev && rm -rf /var/lib/apt/lists/* && docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr && docker-php-ext-install gd RUN docker-php-ext-install mysqli
The next set of calls are the RUN commands. This is what runs within the container at build time. From the calls, you can see that ModRewrite is enabled (RUN a2enmod rewrite) and it then installs the php-gd and libraries required to run it. This isn’t standard in the default PHP 5.6 build, as it’s designed to be as small as possible. There’s also the mysqli PHP extension, which again is another dependency for WordPress.
VOLUME
VOLUME /var/www/html
This is the default volume we overrode in the last article. Specifying it in the Dockerfile allows it to be externally mounted via the host itself or a Docker data container. If it’s not defined here, then it’s not possible to access outside of the container.
ENV
ENV WORDPRESS_VERSION 4.2.2 ENV WORDPRESS_UPSTREAM_VERSION 4.2.2 ENV WORDPRESS_SHA1 d3a70d0f116e6afea5b850f793a81a97d2115039 # upstream tarballs include ./wordpress/ so this gives us /usr/src/wordpress RUN curl -o wordpress.tar.gz -SL https://wordpress.org/wordpress-${WORDPRESS_UPSTREAM_VERSION}.tar.gz && echo "$WORDPRESS_SHA1 *wordpress.tar.gz" | sha1sum -c - && tar -xzf wordpress.tar.gz -C /usr/src/ && rm wordpress.tar.gz && chown -R www-data:www-data /usr/src/wordpress
This sets the environment variables, which can be used in the Dockerfile and any scripts that it calls. These are persistent with the container too, so they can be referenced at any time. In the case of the WordPress Dockerfile, we can see that it sets the version number, the upstream version number and then the SHA1 hash of the download.
Then, with the environment variables set, there’s another RUN command to download the corresponding WordPress tarball, check it against the hash (to ensure it correctly downloaded), untar the files to /usr/src and set the ownership of the files so that Apache can access them.
COPY
COPY docker-entrypoint.sh /entrypoint.sh
The COPY command is simply as it sounds. It can copy a file (in the same directory as the Dockerfile) to the container. You can do this for things like custom configuration files or like in this instance, a file to run commands after the container has been setup.
ENTRYPOINT
ENTRYPOINT ["/entrypoint.sh"]
ENTRYPOINT and CMD are probably the two which will confuse you the most, especially if you’re not a heavy Linux user. In fact, with a casual read of the documentation they very much seem like the same thing! However, there’s some subtle differences and they can both be used at the same time.
If the ENTRYPOINT isn’t specified, Docker will use /bin/sh -c as the default. However, if you want to override some of the system defaults, you can specify your own entrypoint and therefore manipulate the environment. The ENTRYPOINT command also allows arguments to be set from the docker run command as well, whereas CMD can’t.
CMD
CMD ["apache2-foreground"]
Hopefully, if the ENTRYPOINT part makes sense CMD will now fall into place. CMD is the default argument passed to the ENTRYPOINT. In the case of the WordPress Dockerfile, it seems a bit counter-intuitive. If you look at the bottom of the entrypoint.sh file, you’ll see exec “$@”. This means that after the BASH script has run, the final command is to execute the arguments passed to the script. In this case, CMD is set to apache2-foregroundwhich therefore starts WordPress.
Don’t worry too much if ENTRYPOINT and CMD still seem very confusing, in most instances you can simply use the existing configurations (as you’ll see below). If you’re at the point where you need to write your own CMD and ENTRYPOINT commands, CenturyLink Labs have a great blog article which covers the two commands in depth. I highly recommend reading it if you’re after more information.
Further commands
As this was a specific example, not all of the commands available were used. Have a read through the Dockerfile reference in the official Docker documentation to go over all of the available commands.
Modifying a Dockerfile
Now that you have a basic understanding, we can now modify the files to suit our own needs. Let’s get a copy of the official WordPress Dockerfile and make a few modifications. If you’ve got git installed, clone the existing repo so that we can modify it:
git clone https://github.com/docker-library/wordpress.git
In the “apache” directory, you’ll see the Dockerfile for WordPress. Since WP-CLI is a popular plugin which makes manipulating WordPress from the command line easy, let’s add it to the Dockerfile. Here’s what we need to add:
RUN curl -o /bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar RUN chmod +x /bin/wp
The wrapper around the wp-cli is to ensure it runs as the www-data user, otherwise it will run as the root user. For this, we also need to install sudo command available as well. Here’s the code for this:
RUN apt-get update && apt-get install -y sudo less
We also want to quickly delete any of the files in regards to apt to keep the container as lean as possible, which can be done with the following:
RUN apt-get clean RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
We now need to build the image. This can be done from the command line and it will create a local Docker image for us. Since it’s not from a repo, we tag the build so that we can easily reference it. Here’s the build command we’ll use:
docker build -t wordpress-with-cli .
The period at the end of the command specifies the directory to build, which in this case is the directory we’re in. With the freshly build image, we can now fire up an instance of this and give our WP-CLI a go. All we need to do is reference the tag name we used (wordpress-with-cli) when creating the container. Here’s the command:
docker run --link db-server:mysql -d -e -p 8080:80 --name wpcli-demo WORDPRESS_DB_NAME=wordpressdemo -e WORDPRESS_DB_USER=conetixdemo -e WORDPRESS_DB_PASSWORD=Xumkos26 wordpress-with-cli
Note: This assumes you have the database container configured as per our previous article. If you’re copying the command verbosely, you’ll need to ensure the database container has been created and started.
We can now call this from the command line just as if it was a local install. Here’s an example to install a new theme and activate it:
docker exec cli-demo wp theme install quark docker exec cli-demo wp theme activate quark
The output from the WP-CLI should be displayed, and you should see it going through the process of downloading the theme and then activating it. If you point your browser at the site (https://<boot2dockerip>:8080), you should see the lovely quark theme installed and running for you.
Creating a new Dockerfile
While modifying an existing Dockerfile works, it also means you need to keep updating it every time the original changes. Because of the layered nature, we can do this quite easily. The changes we put in the file above can now be converted into a standalone Dockerfile and simply reference the parent WordPress layer. Here’s what the Dockerfile would look like:
FROM wordpress:latest # Add WP-CLI RUN apt-get update && apt-get install -y sudo less RUN curl -o /bin/wp-cli.phar https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar RUN curl -o /bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar COPY wp-su.sh /bin/wp RUN chmod +x /bin/wp-cli.phar RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
As you can see, we simply referenced the WordPress image with the FROM command and then added our modifications. Anytime we build a new container, it’ll always use the latest version of WordPress. You could create builds tied to the latest subversion (eg 4.2) as well, so that you can still have version stability.
I’ve also uploaded the Dockerfile for the wp-cli to GitHub and to the Docker Hub Repository with the package name wordpress-with-wp-cli. This means that anyone can now use this image without having to first create the Dockerfile, just like you would with any of the other images available. You can also upload images to the public repository for free, or Docker allow private repos if you buy a subscription through them.
Docker also provide the source code to run your own private repository. It comes as the core system only, meaning that there’s no authentication or web interface available by default. These items need to be added on, since you would be customising them for your own environment. We’ll be covering this in a future article as well, so that you have all the pieces to the Docker ecosystem.
Conclusion
Hopefully now that you’ve seen and played with a Dockerfile, you’ve got a bit of an understanding as to what they look like and how they work. This is where Docker really starts to shine, it’s more than just a container based system. It’s the toolsets and the ease of configuration which really make it the success it is.
There’s some great articles already which go further into depth with Docker, here’s a few to keep you going:
- https://kimh.github.io/blog/en/docker/gotchas-in-writing-dockerfile-en/
- https://crosbymichael.com/dockerfile-best-practices.html
- https://docs.docker.com/articles/dockerfile_best-practices/
Like always, if you have any questions then please don’t hesitate to ask in the comments below.