Tim's Weblog
Tim Strehle’s links and thoughts on Web apps, software development and Digital Asset Management, since 2002.
2019-06-10

First steps: A Symfony 4 PHP app in Docker

I’m currently learning Docker, and finding out how to develop a (new) Symfony 4 (PHP 7) application locally on my Mac and run it within a Docker container, instead of hosting it in a Linux VM in VMware Fusion as I used to do. Docker Desktop and the PhpStorm IDE are already installed.

Docker setup

I want to use official, current Docker images from the Docker Hub. A colleague convinced me (better scalability?) to use Nginx instead of my trusty old Apache Web server. So I need the nginx:latest and php:fpm images.

While I can use the Nginx image as-is, I need to create my own image on top of the PHP standard one since I will want to add PHP extensions as needed, and to run Composer in the container. I ended up with this Dockerfile:

FROM php:fpm

# git and unzip for composer installs
RUN apt-get update && apt-get install -y \
        git unzip \
    --no-install-recommends && rm -r /var/lib/apt/lists/*

# zip for composer installs
RUN apt-get update && apt-get install -y \
        libzip-dev \
    && docker-php-ext-install zip

ENV COMPOSER_HOME /tmp
ENV COMPOSER_VERSION 1.8.5

# "chmod 0777 /tmp/cache" for non-root composer installs
RUN curl --silent --fail --location --retry 3 --output /tmp/installer.php \
  --url https://raw.githubusercontent.com/composer/getcomposer.org/cb19…/web/installer \
 && php -r " \
    \$signature = '48e3…'; \
    \$hash = hash('sha384', file_get_contents('/tmp/installer.php')); \
    if (!hash_equals(\$signature, \$hash)) { \
      unlink('/tmp/installer.php'); \
      echo 'Integrity check failed, installer is either corrupt or worse.' . PHP_EOL; \
      exit(1); \
    }" \
 && php /tmp/installer.php --no-ansi --install-dir=/usr/bin --filename=composer \
  --version=${COMPOSER_VERSION} \
 && composer --ansi --version --no-interaction \
 && rm -f /tmp/installer.php \
 && chmod 0777 /tmp/cache

This docker-compose.yml file defines my Nginx and PHP Docker setup:

version: '3'

services:
    php:
        build:
            context: ./docker/php
        volumes:
            - ./app:/opt/app
            - ./docker/php/zz-log.conf:/usr/local/etc/php-fpm.d/zz-log.conf
    web:
        image: nginx:latest
        depends_on:
          - php
        ports:
            - "8080:80"
        volumes:
            - ./app:/opt/app
            - ./docker/web/default.conf:/etc/nginx/conf.d/default.conf

The context: ./docker/php line refers to the directory where I put the Dockerfile shown above.

My Symfony application source files reside in a local app directory on my Mac, which is empty for now, and mapped via volumes into both containers as /opt/app.

See my Github repo for the two configuration files – a tiny zz-log.conf file that configures PHP log_errors and display_errors settings, and the Nginx default.conf file which contains configuration for Symfony, and hands over PHP requests to the PHP-FPM image.

Nginx listens on port 80 inside the container; on my Mac, I can reach it at localhost:8080.

Thanks to the fantastic PhpStorm Docker support, I can simply click the green “docker-compose up” arrows that show up in docker-compose.yml in the IDE to start the Docker containers (downloading and building images when run for the first time):

PhpStorm IDE screenshot

Creating a Symfony application

PhpStorm lets me “Exec” commands within a Docker container, but I prefer my Mac’s Terminal application. To discover the current container IDs (changing after a restart), I use docker ps:

$ docker ps
CONTAINER ID        IMAGE                           COMMAND                  
0b83693cc0ff        nginx:latest                    "nginx -g 'daemon of…"   …
9fade34cfdb7        symfony-in-docker-example_php   "docker-php-entrypoi…"   …

Now I run composer create-project (as per the Symfony documentation) in the PHP container to create a Symfony application in the local app directory:

$ docker exec --workdir /opt --user daemon:daemon -it 9fade34cfdb7 \
  composer create-project symfony/skeleton app
Installing symfony/skeleton (v4.3.1.1)
  - Installing symfony/skeleton (v4.3.1.1): Downloading (100%)  
  […]

And with that command, my Symfony application is up and running at http://localhost:8080/:

PhpStorm IDE screenshot

I can add more required packages via Composer (I usually add Monolog):

$ docker exec --workdir /opt/app --user daemon:daemon -it 9fade34cfdb7 \
  composer require symfony/monolog-bundle

And use the Symfony console command:

$ docker exec --workdir /opt/app --user daemon:daemon -it 9fade34cfdb7 \
  bin/console about
 -------------------- --------------------------------- 
  Symfony                                               
 -------------------- --------------------------------- 
  Version              4.3.1                            
  […]
 -------------------- --------------------------------- 
  PHP                                                   
 -------------------- --------------------------------- 
  Version              7.3.5                            
  […]
 -------------------- --------------------------------- 

To see Symfony log output on the Docker console output (STDOUT), I change app/config/packages/dev/monolog.yaml to contain path: "php://stdout".

The next step is creating your first page in Symfony!

Thanks

Thanks to the bloggers that helped me figure all this out – Making your dockerised PHP application even better by the Geeky Platypus, My Simple Approach to using Docker and PHP by Paul Redmond, and Structuring the Docker setup for PHP Projects by Pascal Landau.