Thursday 7 January 2021

Part 2: PHP


Please note that I initially intended this to be a single article, but by the time I had finished the first two sections, it was way too long for a single read, so I've split it into the following sections, each as their own article:

  1. Intro / Nginx
  2. PHP (this article)
  3. PHPUnit
  4. Tweaks I made to my Bash environment in my Docker containers
  5. MariaDB
  6. Installing Symfony
  7. Using Symfony
  8. Testing a simple web page built with Vue.js using Mocha, Chai and Puppeteer
  9. I mess up how I configure my Docker containers
  10. An article about moving files and changing configuration
  11. Setting up a Vue.js project and integrating some existing code into it
  12. Unit testing Vue.js components

As indicated: this is the second article in the series, and follows on from Part 1: Intro & Nginx. I'ts probably best to go have a breeze through that one first.


The aim for this step is to stick an index.php file in the public directory, and have Nginx pass it over to PHP for processing. At the same time I'm going to get it to run composer install for good measure, so I'll add composer.json in there too. Oh, and PHP will end up needing to use PDO to talk to the DB, so I'll add that extension in now as well. Conveniently, Martin's article does all this stuff too, so all I need to do is copy & paste & tweak some files. Hopefully.

First up, docker/php-fpm/Dockerfile:

FROM php:fpm-alpine
RUN apk --update --no-cache add git
RUN docker-php-ext-install pdo_mysql
COPY --from=composer /usr/bin/composer /usr/bin/composer
WORKDIR  /usr/share/fullstackExercise/
CMD composer install ; php-fpm

This is largely the same as Martin's one; I've just got rid of some DB stuff he had in his, and pointed the WORKDIR to where the application code will be. note that /usr/share/fullstackExercise/ is the parent directory that contains the web root (/public), and composer.json, /src and /test etc.

One interesting thing is how composer is… erm… availed to the container: the files are just copied straight from Docker's image, then installed in the container.

Next is a simple config file to configure how Nginx passes PHP stuff onwards (docker/nginx/conf.d/default.conf). This is lock-stock from Martin's acticle, and I am just going "yeah… seems legit…?" (I'm such a pro):

upstream php-upstream {
    server php-fpm:9000;

And we need to stick the PHP stuff back into the Nginx site configuration file too now (docker/nginx/sites/default.conf):

server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    server_name localhost;
    root /usr/share/fullstackExercise/public;
    index index.html;

    location / {
        autoindex on;
        try_files $uri $uri/ /index.php$is_args$args;

    location ~ \.php$ {
        try_files $uri /index.php =404;
        fastcgi_pass php-upstream;
        fastcgi_index index.php;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_read_timeout 600;
        include fastcgi_params;

    location ~ /\.ht {
        deny all;

That's all boilerplate stuff really.

Finally we crank up the php-fpm container via docker/docker-compose.yml:

version: '3'

      context: ./nginx
      - ../public:/usr/share/fullstackExercise/public
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/sites/:/etc/nginx/sites-available
      - ./nginx/conf.d/:/etc/nginx/conf.d
      - ../log:/var/log
      - php-fpm
      - "80:80"
    stdin_open: true # docker run -i
    tty: true        # docker run -t

      context: ./php-fpm
      - ..:/usr/share/fullstackExercise
    stdin_open: true # docker run -i
    tty: true        # docker run -t

The PHP bit is pretty simple here. It just mounts the app directory. The only other new thing in here is that I've set both containers to be able to be shelled into with docker exec.

So. Does any of this stuff work? First I need to shut-down what I've got running:

adam@DESKTOP-QV1A45U:/mnt/c/src/fullstackExercise/docker$ docker-compose down --remove-orphans
Stopping docker_nginx_1   ... done
Removing docker_nginx_1   ... done
Removing network docker_default

And then crank it all back up again. This outputs a lot of stuff, most of which I've elided here, and most of what I've kept I've only done so for the sake of completeness, and showing it ran OK. Note that I'm now running docker-compose with the --detach option. This just gives me my prompt back after the process runs:

adam@DESKTOP-QV1A45U:/mnt/c/src/fullstackExercise/docker$ docker-compose up --build --detach
Creating network "docker_default" with the default driver
Building php-fpm
Step 1/7 : FROM php:fpm-alpine
fpm-alpine: Pulling from library/php
188c0c94c7c5: Already exists
45f8bf6cfdbe: Pull complete
Status: Downloaded newer image for php:fpm-alpine
---> 6bd7d9173974
Step 2/7 : RUN apk --update --no-cache add git
---> Running in 4beacfe86bf2
(1/3) Installing expat (2.2.9-r1)
(2/3) Installing pcre2 (10.35-r0)
(3/3) Installing git (2.26.2-r0)
Executing busybox-1.31.1-r19.trigger
OK: 27 MiB in 34 packages
Removing intermediate container 4beacfe86bf2
---> 9838e800a674
Step 3/7 : RUN docker-php-ext-install pdo_mysql
---> Running in d38de20fec54
(1/29) Installing m4 (1.4.18-r1)
Libraries have been installed in:


Build complete.
Don't forget to run 'make test'.

Installing shared extensions: /usr/local/lib/php/extensions/no-debug-non-zts-20200930/
OK: 27 MiB in 34 packages
Removing intermediate container d38de20fec54
---> 97967ee1a4b2
Step 4/7 : COPY --from=composer /usr/bin/composer /usr/bin/composer
latest: Pulling from library/composer
11c513a1b503: Pull complete
Status: Downloaded newer image for composer:latest
---> 3b2d08ac01d3
Step 5/7 : WORKDIR /usr/share/fullstackExercise/
---> Running in 26dc7beac904
Removing intermediate container 26dc7beac904
---> 0331a636bbe7
Step 6/7 : CMD composer install ; php-fpm
---> Running in d89f5a03182f
Removing intermediate container d89f5a03182f
---> 98b0bf2fc6aa
Step 7/7 : EXPOSE 9000
---> Running in b4eea18c4960
Removing intermediate container b4eea18c4960
---> c3a5321dd671

Successfully built c3a5321dd671
Successfully tagged docker_php-fpm:latest
Building nginx
Successfully built 78383fe534b7
Successfully tagged docker_nginx:latest
Creating docker_php-fpm_1 ... done
Creating docker_nginx_1 ... done

And, again, the last step is to test that PHP running stuff, just via /public/gdayWorld.php for now:


$message = "G'day World";
echo $message;

When I hit http://localhost/gdayWorld.php, I get:

Good enough for me. I mean I should perhaps include a file from /src to make sure that PHP can find it, but I will admit this did not occur to me at the time.

In the next article, Part 3: PHPUnit, I will do as indicated: get PHPUnit working, and backfilling some tests of the code I've written so far.