*article updated on August 27, 2024
This article was originally published on Conetix by Tim Butler. With his kind permission, we are sharing it here for Codeship readers.
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 are 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 a 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 is simply, what is a Dockerfile? When you run the Docker run command and specify 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 virtualization systems) is that the automatic builds will ensure you have the latest version available. From a security perspective, this is a good thing, as you want to ensure you’re not installing any vulnerable software.
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"]
Let's 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 base this on. This is the multi-layered approach that 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 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.
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 MySQL 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 as simple 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, as in this instance, a file to run commands after the container has been set up.
ENTRYPOINT
ENTRYPOINT ["/entrypoint.sh"]
ENTRYPOINT and CMD are probably the two that will confuse you the most, especially if you’re not a heavy Linux user. In fact, after a casual read of the documentation, they very much seem like the same thing! However, there are 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 counterintuitive. 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-foreground, which 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 has a great blog article that covers the two commands in depth. I highly recommend reading it if you’re after more information.
EXPOSE
The EXPOSE instruction in a Dockerfile is used to declare the network ports that a containerized application listens to during runtime. While it doesn’t make the ports accessible externally by itself, it serves as documentation to indicate which ports the application is designed to use, improving transparency and maintainability. For example, a web server might use EXPOSE 80
to signal that it listens for HTTP traffic on port 80.
To allow external access to the exposed ports, they must be explicitly published using the -p
flag when running the container (e.g., docker run -p 8080:80
). While not mandatory for port functionality, including EXPOSE
in a Dockerfile is considered a best practice for documenting the container's networking requirements.
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 that 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 the 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 regarding apt in order 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 built 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 were 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 (http://:8080), you should see the lovely quark theme installed and running for you.
How to Create 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 (e.g., 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 allows private repos if you buy a subscription through them.
Docker also provides 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.
The Power of a Dockerfile
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 are some other great articles out there that go further into depth with Docker. Here are a couple to keep you going:
As always, if you have any questions, please don’t hesitate to ask in the comments.