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
Nginx
MySQL 8
Redis

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 https://docs.docker.com/install/linux/docker-ce/ubuntu/ and docker compose https://docs.docker.com/compose/install/.

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 hosts/hello_dev.conf with content:

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

    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/hello.dev/index.php - to check PHP and Nginx.

<?php
   phpinfo();

www/hello.dev/db.php - to check MySql connection from PHP.

<?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 pecl.php.net

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

ENV COMPOSER_ALLOW_SUPERUSER 1

# PHP working dir
WORKDIR /var/www/hello.dev

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

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

ADD .my.cnf /var/www/hello.dev/.my.cnf

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

Docker compose

Now create docker-compose 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)
services:
    nginx:
        hostname: docker-nginx
        container_name: docker-nginx
        image: nginx:latest
        ports:
            - "8080:80"
            - "1443:443"
        # Mount directories - host_dir:container_dir
        volumes:
            - ./containers/nginx:/etc/nginx/conf.d
            - ./www:/var/www
            - ./logs:/var/log/nginx
        links:
            - php
    php:
        hostname: docker-php
        container_name: docker-php
        build: ./containers/php
        working_dir: /var/www/hello.dev
        links:
            - mysql
            - redis
        volumes:
            - ./www:/var/www
            - ./containers/php/my.ini:/usr/local/etc/php/conf.d/my.ini
    mysql:
        hostname: docker-mysql
        container_name: docker-mysql
        image: mysql:8
        ports:
            - "13306:3306"
        volumes:
            - ./containers/mysql:/var/lib/mysql
        environment:
            MYSQL_ROOT_PASSWORD: secret
            MYSQL_DATABASE: hellodb
    redis:
        hostname: docker-redis
        container_name: docker-redis
        image: redis:alpine
        volumes:
            - ./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

127.0.0.1	hello.dev

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';

Conclusion

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.

Комментарии