Docker PHP 7 Tutorial (7, 7.1, and higher)


In the previous two videos we have laid the foundation required for running Docker, PHP, and Symfony all together.

We saw how we could quickly create a brand new MySQL database container allowing us to spin up a DB in a matter of seconds.

Then we saw how we could add in a nginx web server container, which would accept incoming requests and then forward them on to somewhere that really handled the PHP part of the job.

That somewhere is here.

However, yet again, things get a little more complicated than you might first expect.

We covered how it's a good idea to separate nginx from PHP. This way we could re-use the majority of the nginx configuration for other projects - WordPress, Node JS apps, Laravel, Rails, whatever. All we need to do is provide a different site configuration, and COPY that into the resulting nginx image.

Well, it's somewhat similar for PHP.

We're going to first create a PHP 7.1 image, and then use that as the base for any PHP applications we might need. Again, this could be Symfony, or WordPress, or some standalone PHP app, or whatever.

It's extra work upfront, but it is easier this way on a day-to-day basis, I have found.

Creating a Custom Docker PHP 7.x Image

Our first task is to create a custom Docker PHP 7.1 image.

At the time of writing / recording, PHP 7.1 is the latest minor release, and PHP 7.1.9 is the latest patch release.

Check on the Docker Hub PHP page for the latest for you.

There are a bunch of versions such as:

  • 7.1.9-cli
  • 7.1.9-fpm
  • 7.1.9-alpine
  • 7.1.9
  • 7.1-cli
  • 7.1.9-zts

As you may have guessed based on all the PHP-FPM hi-jinx we went through in the previous video, the image we need is 7.1.9-fpm.

This image comes with PHP-FPM pre-configured and ready to go. This is why we could add in php:9000 in our nginx configuration.

PHP 7.1 Dockerfile

The Dockerfile for our PHP base image is large:

FROM php:7.1.9-fpm

# Install any custom system requirements here
RUN apt-get update \
  && apt-get install -y --no-install-recommends \
    curl \
    libicu-dev \
    libmemcached-dev \
    libz-dev \
    libpq-dev \
    libjpeg-dev \
    libpng12-dev \
    libfreetype6-dev \
    libssl-dev \
    libmcrypt-dev \
    libxml2-dev \
    libbz2-dev \
    libjpeg62-turbo-dev \
    php-pear \
    curl \
    git \
    subversion \
  && rm -rf /var/lib/apt/lists/*

# Install various PHP extensions
RUN docker-php-ext-configure bcmath --enable-bcmath \
    && docker-php-ext-configure pcntl --enable-pcntl \
    && docker-php-ext-configure pdo_mysql --with-pdo-mysql \
    && docker-php-ext-configure pdo_pgsql --with-pgsql \
    && docker-php-ext-configure mbstring --enable-mbstring \
    && docker-php-ext-configure soap --enable-soap \
    && docker-php-ext-install \
        bcmath \
        intl \
        mbstring \
        mcrypt \
        mysqli \
        pcntl \
        pdo_mysql \
        pdo_pgsql \
        soap \
        sockets \
        zip \
  && docker-php-ext-configure gd \
    --enable-gd-native-ttf \
    --with-jpeg-dir=/usr/lib \
    --with-freetype-dir=/usr/include/freetype2 && \
    docker-php-ext-install gd \
  && docker-php-ext-install opcache \
  && docker-php-ext-enable opcache

# AST
RUN git clone https://github.com/nikic/php-ast /usr/src/php/ext/ast/ && \
    docker-php-ext-configure ast && \
    docker-php-ext-install ast

# ICU - intl requirements for Symfony
# Debian is out of date, and Symfony expects the latest - so build from source, unless a better alternative exists(?)
RUN curl -sS -o /tmp/icu.tar.gz -L http://download.icu-project.org/files/icu4c/58.2/icu4c-58_2-src.tgz \
    && tar -zxf /tmp/icu.tar.gz -C /tmp \
    && cd /tmp/icu/source \
    && ./configure --prefix=/usr/local \
    && make \
    && make install

RUN docker-php-ext-configure intl --with-icu-dir=/usr/local \
    && docker-php-ext-install intl

# Install the php memcached extension
RUN curl -L -o /tmp/memcached.tar.gz "https://github.com/php-memcached-dev/php-memcached/archive/php7.tar.gz" \
  && mkdir -p memcached \
  && tar -C memcached -zxvf /tmp/memcached.tar.gz --strip 1 \
  && ( \
    cd memcached \
    && phpize \
    && ./configure \
    && make -j$(nproc) \
    && make install \
  ) \
  && rm -r memcached \
  && rm /tmp/memcached.tar.gz \
  && docker-php-ext-enable memcached

# Copy opcache configration
COPY ./opcache.ini /usr/local/etc/php/conf.d/opcache.ini

# Install xDebug, if enabled
ARG INSTALL_XDEBUG=false
RUN if [ ${INSTALL_XDEBUG} = true ]; then \
    # Install the xdebug extension
    pecl install xdebug && \
    docker-php-ext-enable xdebug \
;fi

# Copy xdebug configration for remote debugging
COPY ./xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini

# Copy timezone configration
COPY ./timezone.ini /usr/local/etc/php/conf.d/timezone.ini

# Set timezone
RUN rm /etc/localtime
RUN ln -s /usr/share/zoneinfo/Europe/London /etc/localtime
RUN "date"

# Short open tags fix - another Symfony requirements
COPY ./custom-php.ini /usr/local/etc/php/conf.d/custom-php.ini

# Composer
ENV COMPOSER_HOME /var/www/.composer

RUN curl -sS https://getcomposer.org/installer | php -- \
    --install-dir=/usr/bin \
    --filename=composer

RUN chown -R www-data:www-data /var/www/

RUN mkdir -p $COMPOSER_HOME/cache

VOLUME $COMPOSER_HOME

As instructed here, there are other files COPY'd in. Be sure to check out the GitHub repo for a full run down.

Building a Docker PHP, and Symfony Image

Now the above PHP 7.x Dockerfile needs to be built before it can be used.

Ideally we want to store the resulting image file somewhere so that it can be accessed both by ourselves, and if needed by others, without having to complete a build (which takes ages in this case) every time we need to use it. And we will use it a lot, because this is our PHP base image.

For the purposes of this video I will push my image up to Docker Hub.

In reality, I store all my own images in a Private Docker Registry. We will get on to this a little later on, but for now a publicly accessible registry such as Docker Hub is fine for our needs.

If you haven't already done so, please create an account at Docker Hub before continuing. If you would prefer, you can skip this step as you can make use of my image as needed.

Next, create a new repository.

I'm calling mine "php-7".

You can see my repository here.

Next we need to docker build this Dockerfile into a docker image, and then docker push this image up to Docker Hub.

First, let's build the image:

docker build -t codereviewvideos/php-7 .

This will build the image based on the Dockerfile contents, and -t (tag) the image as codereviewvideos/php-7:latest.

You can specify a better tag name, if needed. For base images I rarely do.

The resulting output from this command is incredibly verbose, so I have omitted it for the sake of brevity. Also this command takes a while to run - so feel free to go grab a coffee. Thankfully you don't need to do this very often - only if make a change, for example such as bumping to the latest PHP minor or patch release.

After a short - or long - period, you should hopefully have something akin to this:

...

Step 20/21 : RUN mkdir -p $COMPOSER_HOME/cache
 ---> Running in 8d9573855707
 ---> 57132b991359
Removing intermediate container 8d9573855707
Step 21/21 : VOLUME $COMPOSER_HOME
 ---> Running in aa50c819bc8b
 ---> 5589317017f8
Removing intermediate container aa50c819bc8b
Successfully built 5589317017f8
Successfully tagged codereviewvideos/php-7:latest

This image now resides only, in this case, on my machine.

This image is also quite large:

docker image inspect 5589317017f8 | grep Size
        "Size": 993292369,

993292369 is in bytes = ~993mb.

To share this with my others I need to push this image up from my machine out onto a repository. In this case I will use Dockerhub which is a public registry. In most cases I would use my own GitLab server, making use of its private Registry instead.

docker push codereviewvideos/php-7

The push refers to a repository [docker.io/codereviewvideos/php-7]
58858b9ac822: Pushed 
ff494e26803e: Pushed 
21954226c0a2: Pushed 
86775dacda53: Pushed 
01505d689972: Pushed 
6631144d58cd: Pushed 
4e4c5c63576f: Pushed 
e29936a0ea85: Pushed 
1eabc97271f1: Pushed 
25ef4dbaf88d: Pushed 
02a669b9f23b: Pushed 
13542cf90a94: Pushed 
eb3f743b1b32: Pushed 
6c88c973ebdc: Pushed 
d5c29d26b17f: Pushed 
d2cae5519c63: Mounted from library/php 
d8ff7331f387: Mounted from library/php 
00170637a65c: Mounted from library/php 
0ee3463bd959: Mounted from library/php 
a4dff7f421b9: Mounted from library/php 
a6cdd2fcc357: Mounted from library/php 
57277ee24969: Mounted from library/php 
69bff829a46c: Mounted from library/php 
18f9b4e2e1bc: Mounted from library/php 
latest: digest: sha256:89294691302aeb8c2e91d1502e3dd458a7550366f66969686655a01129e19bfb size: 5340

Depending on your upload speed this part may take some time.

The good news is we now have the base image for all our PHP tasks.

In the next video we will use this image as the basis for a brand new Symfony 3 project.

Code For This Course

Get the code for this course.

Episodes