Notes on software development

Docker for PHP

Today Docker became an important part of the development process. Docker creates an isolated environment in containers within the operating system. This allows to get a transferable environment - everything in the container can be saved, copied and deployed to another system.

We'll prepare our own local docker environment for PHP development with components:

PHP 7.3

We'll follow important docker rule - one service per container. Also, we'll mount docker volumes to keep code and data on our host machine and use it in containers, so our containers will work for "read-only" purpose and can be stopped, deleted or replaced without losing data.

Install docker

First, we need to install Docker and docker compose

Create folders structure

Create somewhere a directory that will be the root for our build and create the following directories:

    ├── containers         # Docker containers
    │   ├── mysql
    │   ├── nginx
    │   ├── php
    |   └── redis
    ├── logs               # Different logs 
    └── www                # Project files

Create a test project

To test our build, create a config file for nginx in containers/nginx/hosts/hello_dev.conf with content:

server {
    index index.php;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/;

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;

And two PHP files:

www/ - to check PHP and Nginx.


www/ - to check MySql connection from PHP.

    try {
        $dsn = 'mysql:host=docker-mysql;dbname=hellodb;charset=utf8;port=3306';
        $pdo = new PDO($dsn, 'root', 'secret');
        echo "Connection to the MySQL database established successfully";
    } catch (PDOException $e) {
        echo $e->getMessage();

Docker file for PHP

We'll use default images except for PHP because it doesn't include the necessary modules. Create dockerfile containers/php/Dockerfile with the following content:

# Use official image of PHP
FROM php:7.3-fpm

# Install additional modules to PHP image
# Note that for each RUN command creates a new layer in the image, so it's recommended to merge the commands.
RUN apt-get update && apt-get install -y \
        curl \
        wget \
        git \
        libfreetype6-dev \
        libjpeg62-turbo-dev \
        libpng-dev \
        libzip-dev \
        nano \
        vim \
    && docker-php-ext-install -j$(nproc) zip pcntl mysqli pdo_mysql \
    && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
    && docker-php-ext-install -j$(nproc) gd

RUN pecl channel-update

RUN pecl install -o -f redis uopz \
    &&  rm -rf /tmp/pear


# PHP working dir
WORKDIR /var/www/

# Install composer
RUN curl -sS | php -- \
        --filename=composer \
        --install-dir=/usr/local/bin && \
        echo "alias composer='composer'" >> /root/.bashrc && \

# Our php.ini to redefine config values
#ADD my.ini /usr/local/etc/php/conf.d/40-custom.ini

ADD .my.cnf /var/www/

# Run the container
CMD ["php-fpm"]

Docker compose

Now create docker-compose.yml file in the root of our project. In this file, we describe which containers will run, their settings, how they will interact with each other, and so on.

version: '2'
# Our services (containers)
        hostname: docker-nginx
        container_name: docker-nginx
        image: nginx:latest
            - "8080:80"
            - "1443:443"
        # Mount directories - host_dir:container_dir
            - ./containers/nginx:/etc/nginx/conf.d
            - ./www:/var/www
            - ./logs:/var/log/nginx
            - php
        hostname: docker-php
        container_name: docker-php
        build: ./containers/php
        working_dir: /var/www/
            - mysql
            - redis
            - ./www:/var/www
            - ./containers/php/my.ini:/usr/local/etc/php/conf.d/my.ini
        hostname: docker-mysql
        container_name: docker-mysql
        image: mysql:8
            - "13306:3306"
            - ./containers/mysql:/var/lib/mysql
            MYSQL_ROOT_PASSWORD: secret
            MYSQL_DATABASE: hellodb
        hostname: docker-redis
        container_name: docker-redis
        image: redis:alpine
            - ./containers/redis:/data

Run docker-compose

All is left is to execute the docker-compose up -d command from the root of our build and wait a bit. The first launch will take longer because docker needs to download and build images.

Now our containers are running. Let's check it out. Open the browser and go to localhost:8080.

docker phpinfo

Great! Check MySQL connection, run localhost:8080/db.php.

docker mysql

You can also open a site by domain, to do this, add to /etc/hosts the line

Connection refused

In new versions of PHP and MySQL you may see error running this script:

SQLSTATE[HY000] [2002] Connection refused

Just connect to a MySQL container:

docker exec -it docker-php bash 

Then connect to mysql from command line:

mysql -uroot -psecret hellodb

And run commands:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'secret';
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'secret';
ALTER USER 'default'@'%' IDENTIFIED WITH mysql_native_password BY 'secret';


We have four isolated containers which can communicate with each other. Also, we mounted volumes, so can edit code on our host machine and all changes will reflect in containers. You can easily add more hosts, services, and containers if you need.

All samples are available on github.