Build a Docker-based PHP development environment detailed tutorial, build a dockerphp Development
Nowadays, many developers use Vagrant to manage their virtual machine development environments. Vagrant is really cool, but it also has many disadvantages (most importantly, it occupies too much resources ). After the emergence of container technology, Docker and more types of Docker technology, it becomes easy to solve this problem.
Disclaimer
Due to the working method of boot2docker, the methods described in this article may not work properly in your environment. If you want to share folders in a non-Linux environment to a Docker container, pay attention to more details. In the future, I will write an article specifically to introduce the actual problems encountered.
How can it be regarded as a good development environment?
First, we need to know what is a good development environment. For me, a good development environment must have the following features:
- Free to use. I must be able to delete and create a new environment at will.
- Quick Start. I want to use it immediately when it is working.
- Easy to update. In our industry, things are evolving rapidly and must make it easy for me to update my development environment to a new software version.
Docker supports the above features and even more. You can destroy and recreate the container almost instantly, while updating the environment only requires rebuilding the image you are currently using.
What is the PHP development environment?
At present, Web applications are complex and PHP development environments require many things. To ensure the simplicity of the environment, various restrictions are required.
This time we use Nginx, PHP5-FPM, MySQL to run the Synmfony project.
Pet and Cattle
Another focus we should discuss is: deploy the development environment in multiple containers or single containers. The two methods have their own advantages:
- Single containers are easy to distribute and maintain. Because they are independent, everything runs in the same container, which is like a virtual machine. But this also means that when you want to upgrade something (such as the new version of PHP), you need to re-build the entire container.
- Multiple containers provide better modularization when adding components. Because each container contains a part of the stack: Web, PHP, MySQL, and so on, you can expand each service or add a service separately without recreating everything.
Because I am too lazy and need to place something else in my notebook, here we will only introduce the method of a single container.
Initialize the project
The first thing to do is to initialize a new Symfony project. The recommended method is to use the create-project command of composer. You can install composer on the workstation, but it is too simple. This time we use Docker.
I have previously published an article about Docker commands: make docker commands (well, I lied. I wrote it in this article, then I think it would be better to separate it ).
You can read it anyway. If you do not have the composer command, you can create your own composer alias.
$ alias composer="docker run -i -t -v \$PWD:/srv ubermuda/composer"
Now you can initialize the Symfony project:
$ composer create-project symfony/framwork-standard-edition SomeProject
Handsome! Let's look at some practical work.
Container
Building a self-supplied container that runs the standard Symfony project is quite easy, just install the common Nginx, PHP5-FPM and MySQL-Server, then, the pre-prepared Nginx virtual host configuration file will be thrown in, and some configuration files will be copied.
The source code of this container can be found in the ubermuda/docker-symfony repository on GitHub. Dockerfile is the configuration file used by Docker to build an image. Let's take a look:
FROM debian:wheezyENV DEBIAN_FRONTEND noninteractiveRUN apt-get update -yRUN apt-get install -y nginx php5-fpm php5-mysqlnd php5-cli mysql-server supervisorRUN sed -e 's/;daemonize = yes/daemonize = no/' -i /etc/php5/fpm/php-fpm.confRUN sed -e 's/;listen\.owner/listen.owner/' -i /etc/php5/fpm/pool.d/www.confRUN sed -e 's/;listen\.group/listen.group/' -i /etc/php5/fpm/pool.d/www.confRUN echo "\ndaemon off;" >> /etc/nginx/nginx.confADD vhost.conf /etc/nginx/sites-available/defaultADD supervisor.conf /etc/supervisor/conf.d/supervisor.confADD init.sh /init.shEXPOSE 80 3306VOLUME ["/srv"]WORKDIR /srvCMD ["/usr/bin/supervisord"]
We started by extending the basic image debian: wheezy and then configuring Nginx and PHP5-FPM through a series of sed commands.
Copy codeThe Code is as follows: RUN sed-e's/; daemonize = yes/daemonize = no/'-I/etc/php5/fpm/php-fpm.conf
RUN sed-e's/; listen \. owner/listen. owner/'-I/etc/php5/fpm/pool. d/www. conf
RUN sed-e's/; listen \. group/listen. group/'-I/etc/php5/fpm/pool. d/www. conf
RUN echo "\ ndaemon off;">/etc/nginx. conf
Here we have to do two things. First configure the PHP5-FPM and Nginx so that they can run at the front end so that supervisord can track them.
Then, configure the PHP5-FPM to run Web-Server with the specified user and handle file permissions.
Next, you need to install a set of configuration files. The first is the Nginx virtual host configuration file vhost. conf:
server { listen 80; server_name _; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; root /srv/web; index app_dev.php; location / { try_files $uri $uri/ /app_dev.php?$query_string; } location ~ [^/]\.php(/|$) { fastcgi_pass unix:/var/run/php5-fpm.sock; include fastcgi_params; }}
Because we do not need a domain name, we set server_name to _ (a bit like the $ _ placeholder variable in perl) and set the root directory (document root) to/svr/web, we will deploy the application under/srv, and the rest is the standard mg1_+ PHP5-FPM configuration.
Because a container can only run one program at a time, we need supervisord (or any other process manager, but I prefer supervisord ). Fortunately, this process manager will generate all the processes we need! The following is a short supervisord Configuration:
[supervisord]nodaemon=true[program:nginx]command=/usr/sbin/nginx[program:php5-fpm]command=/usr/sbin/php5-fpm[program:mysql]command=/usr/bin/mysqld_safe[program:init]command=/init.shautorestart=falseredirect_stderr=trueredirect_stdout=/srv/app/logs/init.log
What we need to do here is to define all the services and add a special program: init process. It is not an actual service, but an original STARTUP script running method.
The problem with this startup script is that it usually needs to start some services first. For example, you may need to initialize some database tables, but the premise is that you have to run MySQL first. One possible solution is to start MySQL in the startup script and then initialize the table, then, in order to avoid affecting the supervisord process management, you need to stop MySQL and then start supervisord.
Such a script looks like the following:
/etc/init.d/mysql startapp/console doctrine:schema:update --force/etc/init.d/mysql stopexec /usr/bin/supervisord
It looks ugly. Let's use another method to let the supervisor run it and never restart it.
The actual init. sh script is as follows:
#!/bin/bashRET=1while [[ RET -ne 0 ]]; do sleep 1; mysql -e 'exit' > /dev/null 2>&1; RET=$?doneDB_NAME=${DB_NAME:-symfony}mysqladmin -u root create $DB_NAMEif [ -n "$INIT" ]; then /srv/$INITfi
The script waits for MySQL to start, and then creates a database based on the environment variable DB_NAME. The default value is symfony. Then, find the script to run in the INIT environment variable and try to run it. The end of this article describes how to use these environment variables.
Build and run an image
Everything is ready to go. We also need to build a Symfony Docker image and use the docker build command:
$ cd docker-symfony$ docker build -t symfony .
Now you can use it to run your Symfony project:
$ cd SomeProject$ docker run -i -t -P -v $PWD:/srv symfony
Let's take a look at what these series of options are doing:
- -I starts the interactive mode, that is, STDIO (standard input and output) is connected to your current terminal. It is useful when you want to receive logs or send signals to processes.
- -T creates a virtual TTY for the container, which is usually used together with-I.
- -P tells the Docker daemon to publish all specified ports. In this example, port 80 is used.
- -V $ PWD:/srv mount the current directory to the/srv directory of the container. Mount a directory to make the directory content available to the target mount point.
Now, you still remember the DB_NAME and INIT environment variables mentioned earlier. Why do you use them to customize your environment. Basically, you can use the-e Option of docker run to set the environment variable in the container. The STARTUP script will get the environment variable. Therefore, if your DB name is some_project_dev, you can run the container like this:
$ docker run -i -t -P -v $PWD:/srv -e DB_NAME=some_project_dev symfony
The INIT environment variable is more powerful. It allows you to run the specified script at startup. For example, you have a bin/setup script to run the composer install command and set the database schema:
#!/bin/bashcomposer installapp/console doctrine:schema:update --force
Run it with-e:
$ docker run -i -t -P \ -v $PWD:/srv \ -e DB_NAME=some_project_dev \ -e INIT=bin/setup
Note that the-e option can be used multiple times in docer run and looks cool. In addition, your startup script requires the executable permission (chmod + x ).
Now we send a request to the container through curl to check whether everything works as expected. First, we need to obtain the public port mapped to port 80 of the container by using the Docker port command:
$ docker port $(docker ps -aql 1) 800.0.0.0:49153
Docker ps-aql 1 is a good command to conveniently retrieve the id of the last container. In our example, Docker maps port 80 of the container to port 49153. Let's look at curl.
$ curl http://localhost:49153
You are not allowed to access this file. Check app_dev.php for more information.
When we do not access the dev controller from localhost (Translator's note: container's localhost), we get the default Symfony error message, which is no longer normal, because we do not send curl requests from the container, we can safely remove these rows from the front-end controller web/app_dev.php.
// This check prevents access to debug front controllers that are deployed by accident to production servers.// Feel free to remove this, extend it, or make something more sophisticated.if (isset($_SERVER['HTTP_CLIENT_IP']) || isset($_SERVER['HTTP_X_FORWARDED_FOR']) || !(in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', 'fe80::1', '::1')) || php_sapi_name() === 'cli-server')) { header('HTTP/1.0 403 Forbidden'); exit('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.');}
These rows prevent access to dev controller from any location other than localhost.
Now, curl can work normally, or access http: // localhost: 49153/: in a browser /: