SoFunction
Updated on 2025-03-09

Docker builds its own PHP development environment

1. Preface

1.1 Why use Docker?

Is there such a scenario? You have started a project that needs to build an environment when developing locally, and also need to build an environment when putting it online. If you want to secretly play with it, you need to build an environment. If you don’t have it, it won’t work, because you have a lot of dependencies on the environment. At this time, if you have Docker, you just need to install a Docker on the machine, put the written Dockerfile, and then complete this task automatically with one line of commands. It is convenient and efficient, wouldn’t it be very cool?

1.2 Preparation

Next, this article introduces how to build a PHP development environment. It will use zPhal-dockerfiles as an example. This is a set of Dockerfiles I prepared for my blog system.

Now, whether it is Windows, Mac or Linux, Docker can be very well supported, including Windows systems. Docker for Windows is actually quite good under Win 10 systems, but it is more memory-intensive.

Through the Docker command line, we can do many things, pulling images, running containers, executing commands in containers, etc., but now, we need to write Dockerfiles files in a simpler and more crude way, and then manage these files through docker-compose to simplify the operation process.

What is a Dockerfile?

Dockerfile is a script composed of a series of commands and parameters. These commands are applied to the basic image pulled and finally create a new image. Through Dockerfile, we can create a mirror you need, which contains the software you want to install, which is equivalent to customizing the expansion to install, the commands to be executed in advance, and then executing them with one click, greatly simplifying the operation process.

To build an environment according to this article, you need:

First, let’s learn about Docker and some basic operations of Docker, as well as what docker-compose is.
Then I need to install Docker and docker-compose, and I will use docker-compose to manage my Dockerfiles.
Note that writing Dockerfiles is alive, not dead, and everyone writes Dockerfiles, depending on your needs.

Docker's official documentation is very clear. Although it is in English, it basically has everything. If you have any questions, it is very wise to translate the document: Docker Documentation.

2. Start writing

Next, we will take zPhal-dockerfiles as an example. You can click the link to see the complete one. The following is just a clip.

2.1 Preview

First, let’s take a look at the Dockerfile project I created. I roughly divided it into the following directory (of course, this is determined by myself, and it is not required to type your file in this way):

zPhal-dockerfiles
app/
 
 
data/
 .gitignore
files/
 mysql/
 /
  
 Dockerfile
 nginx/
 /
  
  
 Dockerfile
 
 php/
 pkg/
  .gitignore
 Dockerfile
 
 
 
 redis/
 Dockerfile
 
logs/
.gitgnore

In this project, I use PHP, MySQL, Nginx, Redis, Composer, Phalcon expansion, etc.

In general, we have three processes to do this: write the Dockerfile of each software; write the configuration file; and process all Dockerfiles through docker-compose, including throwing the configuration configuration file into the image that the Dockerfile file will build.

2.2 Write Dockerfile

2.2.1 PHP

Here is PHP's Dockerfile:

FROM php:7.2-fpm

MAINTAINER goozp "gzp@"
Set the time zone

ENV TZ=Asia/Shanghai

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

Update installation dependency packages and PHP core expansion

RUN apt-get update && apt-get install -y \
git \
libfreetype6-dev \
libjpeg62-turbo-dev \
libpng-dev \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install -j$(nproc) gd \
&& docker-php-ext-install zip \
&& docker-php-ext-install pdo_mysql \
&& docker-php-ext-install opcache \
&& docker-php-ext-install mysqli \
&& rm -r /var/lib/apt/lists/*

Copy the pre-downloaded expansion package from the host machine

COPY ./pkg/ /home/
COPY ./pkg/ /home/

Install PECL expansion, here we are installing Redis

RUN pecl install /home/ && echo "extension=" > /usr/local/etc/php//

Install third-party extensions, here is Phalcon extensions

RUN cd /home \
&& tar -zxvf  \
&& mv cphalcon-* phalcon \
&& cd phalcon/build \
&& ./install \
&& echo "extension=" > /usr/local/etc/php//

Install Composer

ENV COMPOSER_HOME /root/composer
RUN curl -sS /installer | php -- --install-dir=/usr/local/bin --filename=composer
ENV PATH $COMPOSER_HOME/vendor/bin:$PATH
RUN rm -f /home/ \
rm -f /home/ 
WORKDIR /data
Write Permission
RUN usermod -u 1000 www-data

The first line defines the basic image, here we use the fpm version of PHP 7.2, here the second line defines a maintainer.

Next, the time zone is defined, and this sentence is defined in each Dockerfile, mainly to make the time of all containers synchronize with the host. In fact, we can define it in the file as follows:

services:

php-fpm:

volumes:

  - /etc/localtime:/etc/localtime:ro
However, when running in non-Linux systems, such as Windows, we cannot get /etc/localtime. In order to be more compatible with all platforms, I wrote the time synchronization to the Dockerfile.

Next, install some expansions. In fact, the installation expansion process is similar to the installation and expansion process we install PHP expansion in Linux with our bare hands. It is worth mentioning that Composer is. I installed Composer directly in the php-fpm image. In fact, the official also provides Composer image. Pulling the Composer image to execute can also achieve the purpose, because we use Composer just to execute the Composer command to manage our packages. If Composer is a container alone, we can also turn off the container when not using it. But here, I directly installed Composer into the php-fpm image, mainly because my project installed some PHP expansions. When writing the file, I defined the dependencies of extension, so when Composer executes, it will check whether these dependencies are installed in the environment. If I use Composer directly, I need to install the expansions I use into the image, which is much more troublesome, so I did this directly in the PHP image. In fact, there is no difference, it depends on how you use it.

2.2.2 Nginx

Here is Nginx's Dockerfile:

FROM nginx:1.12
set timezome

ENV TZ=Asia/Shanghai

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

This is much simpler, I only set one time. Because I don't need to install anything else, I can use the official image directly.

Of course, we need to modify the configuration file, just write the configuration file in advance, and finally throw the configuration file in the file. This will be discussed below, including PHP configuration files and MySQL configuration files, which are the same.

2.2.3 MySQL

Here is MySQL's Dockerfile:

FROM mysql:5.7
set timezome

ENV TZ=Asia/Shanghai

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

There is nothing special about MySQL, and it directly uses the official image.

2.2.4 Redis

The following is Redis, and the official image is also used directly:

FROM redis:3.2
set timezome

ENV TZ=Asia/Shanghai

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

2.3 Writing configuration files

How to deal with configuration files? I classify the configuration files, place the PHP configuration files in the PHP directory, and the Nginx configuration is in the Nginx directory. As for whether to create a new subfolder, it depends on the situation, such as folders.

Take the Nginx configuration file as an example. First of all, the Nginx directory is like this:

nginx/

/

   

   

Dockerfile


In addition, there is also a subfolder to store all domain name configuration files. Those who have built PHP environments under Linux should be familiar with it. These configuration files are files that we will pass into the container at that time, and we will not use these files on the host.

Therefore, the most important thing to note is that the paths appear in the configuration file are the paths of the environment in the container, not the paths of the host. Each container has an operating environment, which is a miniature system, and these paths are the paths in the container. We can synchronize files by mounting and communicating in the container. Starting the container on the command line also requires the mount file path. Now, we also use docker-compose to solve the mount step.

Here is an example configuration file:

server {
listen 80 default;
index  ;
server_name localhost docker;

root /data/www;
index   ;
location / {
 try_files $uri $uri/ /;
}

location ~ \.php {
 include fastcgi_params;
 fastcgi_pass php-fpm:9000;
 fastcgi_index ;
 fastcgi_param SCRIPT_FILENAME /data/www/$fastcgi_script_name;
}
} 

In root /data/www, the /data/www path is the path of the Nginx container at that time, not the path of the currently operating host, so we have to mount the location where the web program is placed to this path.

2.4 Writing

At the same level as PHP, Nginx and other directories, we create a file. When we execute docker-compose-related commands, we will automatically find this file and execute it according to the contents inside.

Following the example of Nginx above, let’s talk about mount first, because this is the most important step. In, the part of Nginx:

build: ./nginx
depends_on:
 - php-fpm
links:
 - php-fpm:php-fpm
volumes:
 - ../app:/data/www:rw
 - ./nginx/:/etc/nginx/:ro
 - ./nginx/:/etc/nginx/:ro
 - ../logs/nginx:/var/log/nginx
ports:
 - "80:80"
 - "8080:8080"
 - "443:443"
restart: always
command: nginx -g 'daemon off;'

There is a volumes parameter, which is the relevant configuration of the directory we want to mount. The first thing we mount.../app to /data/www, which is also the default listening root defined in our configuration file. The APP directory is a directory in our host machine. By mounting this way, we can directly put our project file into the APP, and Docker will help you transfer it to the /data/www directory in the container.

Other parameters:

build defines where your Dockerfile is. If you don't write a Dockerfile, you can use the images parameter to define the official image, such as image:mysql:5.7;
depends_on means that it will rely on other images, such as Nginx depends on php-fpm, without which I can't play Nginx;
links define connections, for example, to connect to the php-fpm container, that is, php-fpm:php-fpm, followed by an alias;
Ports means port mapping, 80:80 means mapping port 80 to port 80 of the host;
restart restart, restart: always means restart will be automatically restarted;
command is an automatic command;
……
There are many parameters, please refer to the official documentation for more.

Here is a complete file:

version: '3.2'
services:
php-fpm:
build: ./php/
ports:
 - "9000:9000"
links:
 - mysql-db:mysql-db
 - redis-db:redis-db
volumes:
 - ../app:/data/www:rw
 - ./php/:/usr/local/etc/php/:ro
 - ./php/:/usr/local/etc/:ro
 - ../logs/php-fpm:/var/log/php-fpm:rw
restart: always
command: php-fpm

nginx:
build: ./nginx
depends_on:
 - php-fpm
links:
 - php-fpm:php-fpm
volumes:
 - ../app:/data/www:rw
 - ./nginx/:/etc/nginx/:ro
 - ./nginx/:/etc/nginx/:ro
 - ../logs/nginx:/var/log/nginx
ports:
 - "80:80"
 - "8080:8080"
 - "443:443"
restart: always
command: nginx -g 'daemon off;'

mysql-db:
 build: ./mysql
 ports:
 - "3306:3306"
 volumes:
 - ../data/mysql:/var/lib/mysql:rw
 - ../logs/mysql:/var/lib/mysql-logs:rw
 - ./mysql/:/etc/mysql/:ro
 environment:
 MYSQL_ROOT_PASSWORD: 123456
 MYSQL_DATABASE: zphaldb
 MYSQL_USER: zphal
 MYSQL_PASSWORD: zphal123
 restart: always
 command: "--character-set-server=utf8"
redis-db:
 build: ./redis
 ports:
 - "6379:6379"
 volumes:
 - ../data/redis:/data
 restart: always


3. Use

How do we use this set after writing it?

3.1 Use the built environment

First, enter the directory of the project Dockerfiles, here is the files directory:

cd zPhal-dockerfiles/files

wget /get/redis-3.1. -O php/pkg/

wget /phalcon/cphalcon//v3.3.1 -O php/pkg/
Then download the PHP expansion package we will use.

Execute the command:

docker-compose up
Docker will automatically build images through written content and start containers.

If it's OK, it can be enabled in daemon mode next time you start, and all containers will run in the background:

docker-compose up -d
Close the container:

You can close the container and delete the service like this:

docker-compose down
Using docker-compose is basically that simple, use stop, start and other commands to manipulate container services. And more work is to write Dockerfiles and files.

3.2 Using Composer

What should we do when we want to use Composer? We have installed Composer in php-fpm.

Use docker-compose to operate:

docker-compose run --rm -w /data/www/zPhal php-fpm composer update
-w /data/www/zPhal is the working area in php-fpm, and the zPhal project is also mounted inside, and we can run Composer directly in the container.

Or enter the host APP directory and use the Docker command:

cd zPhal-dockerfiles/app

docker run -it --rm -v `pwd`:/data/www/ -w /data/www/zPhal files_php-fpm composer update

4. Things to note

Pay attention to the mount path.
When the build fails, pay attention to whether an error is reported in the container.
Accelerate mirroring. If the process download image is very slow, you can use the domestic acceleration image service.