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 containers/nginx/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.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) 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.
Great! Check MySQL connection, run localhost:8080/db.php.
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.