Environment deployment has always been a big issue, both in development and production, but Docker packages development and production in a lightweight way to provide a consistent environment. Greatly improved development deployment consistency. Of course, the actual situation is not so simple, because the configuration of production environment and development environment is completely different, such as log and other problems need to be configured separately, but at least it is more simple and convenient than before, here to take PHP development as an example to explain how Docker layout development environment.

Generally speaking, a PHP project will require the following tools:

  1. Web server: Nginx/Tengine

  2. Web program: phP-fpm

  3. Database: MySQL/PostgreSQL

  4. Cache service: Redis/Memcache

This is the simplest architecture. In the early stage of Docker’s development, Docker was widely abused. For example, multiple services were started within one image, log collection was still in Syslog or other old ways, and the image capacity was huge, even the basic image could reach 80M. This is the opposite of what Docker originally proposed, and Alpine Linux distribution as a lightweight Linux environment, is very suitable for the Docker base image, Docker official also recommends Alpine instead of Debian as the base image, A large number of existing official images will also migrate to Alpine in the future. All images in this article will use Alpine as the base image.

Nginx/Tengine

This part of the author has explained the Tengine Docker practice in another article Docker container Nginx practice, and gives the Dockerfile, because of the preference for Tengine, and the official has been given the alpine image of Nginx, So this is Tengine. The author has uploaded the image to the official DockerHub

Docker pull chasontang/tengine: 2.1.2 _fCopy the code

Get the image, see Dockerfile.

PHP-FPM

PHP 7.0.7-fpm-alpine Dockerfile is available as follows:

FROM alpine:3.4 # persistent/runtime deps ENV PHPIZE_DEPS \ autoconf \ file \ g++ \ GCC \ libc-dev \ make \ pkgconf \ re2c RUN apk add --no-cache --virtual .persistent-deps \ ca-certificates \ curl # ensure www-data user exists RUN set -x  \ && addgroup -g 82 -S www-data \ && adduser -u 82 -D -S -G www-data www-data # 82 is the standard uid/gid for "WWW - data" in Alpine # http://git.alpinelinux.org/cgit/aports/tree/main/apache2/apache2.pre-install?h=v3.3.2 # http://git.alpinelinux.org/cgit/aports/tree/main/lighttpd/lighttpd.pre-install?h=v3.3.2 # http://git.alpinelinux.org/cgit/aports/tree/main/nginx-initscripts/nginx-initscripts.pre-install?h=v3.3.2 ENV PHP_INI_DIR /usr/local/etc/php RUN mkdir -p $PHP_INI_DIR/conf.d #### ENV PHP_EXTRA_CONFIGURE_ARGS --enable-fpm --with-fpm-user=www-data --with-fpm-group=www-data #### ENV GPG_KEYS 1A4E8B7277C42E53DBA9C7B9BCAA30EA9C0D5763 ENV PHP_VERSION 7.0.7 ENV PHP_FILENAME php-7.0.7.tar.xz ENV PHP_SHA256 9cc64a7459242c79c10e79d74feaf5bae3541f604966ceb600c3d2e8f5fe4794 RUN set -xe \ && apk add --no-cache --virtual .build-deps \ $PHPIZE_DEPS \ curl-dev \ gnupg \ libedit-dev \ libxml2-dev \ openssl-dev \ sqlite-dev \ && curl -fSL "http://php.net/get/$PHP_FILENAME/from/this/mirror" -o "$PHP_FILENAME" \ && echo "$PHP_SHA256 *$PHP_FILENAME" | sha256sum -c - \ && curl -fSL "http://php.net/get/$PHP_FILENAME.asc/from/this/mirror" -o "$PHP_FILENAME.asc" \ && export  GNUPGHOME="$(mktemp -d)" \ && for key in $GPG_KEYS; do \ gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \ done \ && gpg --batch --verify "$PHP_FILENAME.asc" "$PHP_FILENAME" \ && rm -r "$GNUPGHOME" "$PHP_FILENAME.asc" \ && mkdir -p /usr/src \ && tar -Jxf "$PHP_FILENAME" -C /usr/src \ && mv "/usr/src/php-$PHP_VERSION" /usr/src/php \ && rm "$PHP_FILENAME" \ && cd /usr/src/php \ && ./configure \ --with-config-file-path="$PHP_INI_DIR" \ --with-config-file-scan-dir="$PHP_INI_DIR/conf.d" \ $PHP_EXTRA_CONFIGURE_ARGS \ --disable-cgi \ # --enable-mysqlnd is included here because it's harder to compile after the fact than extensions are (since it's a plugin for several extensions, not an extension in itself) --enable-mysqlnd \ # --enable-mbstring is included here because otherwise there's no way to get pecl to use it properly (see https://github.com/docker-library/php/issues/195) --enable-mbstring \ --with-curl \ --with-libedit \ --with-openssl \ --with-zlib \ && make -j"$(getconf _NPROCESSORS_ONLN)" \ && make install \ && { find /usr/local/bin /usr/local/sbin -type f -perm +0111 -exec strip --strip-all '{}' + || true; } \ && make clean \ && runDeps="$( \ scanelf --needed --nobanner --recursive /usr/local \ | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \ | sort -u \ | xargs -r apk info --installed \ | sort -u \ )" \ && apk add --no-cache --virtual .php-rundeps $runDeps \ && apk del .build-deps COPY docker-php-ext-* /usr/local/bin/ #### WORKDIR /var/www/html RUN set -ex \ && cd /usr/local/etc \ && if [ -d php-fpm.d ]; then \ # for some reason, upstream's php-fpm.conf.default has "include=NONE/etc/php-fpm.d/*.conf" sed 's! =NONE/! =! g' php-fpm.conf.default | tee php-fpm.conf > /dev/null; \ cp php-fpm.d/www.conf.default php-fpm.d/www.conf; \ else \ # PHP 5.x don't use "include=" by default, so we'll create our own simple config that mimics PHP 7+ for consistency mkdir php-fpm.d; \ cp php-fpm.conf.default php-fpm.d/www.conf; \ { \ echo '[global]'; \ echo 'include=etc/php-fpm.d/*.conf'; \ } | tee php-fpm.conf; \ fi \ && { \ echo '[global]'; \ echo 'error_log = /proc/self/fd/2'; \ echo; \ echo '[www]'; \ echo '; if we send this to /proc/self/fd/1, it never appears'; \ echo 'access.log = /proc/self/fd/2'; \ echo; \ echo 'clear_env = no'; \ echo; \ echo '; Ensure worker stdout and stderr are sent to the main error log.'; \ echo 'catch_workers_output = yes'; \ } | tee php-fpm.d/docker.conf \ && { \ echo '[global]'; \ echo 'daemonize = no'; \ echo; \ echo '[www]'; \ echo 'listen = [::]:9000'; \ } | tee php-fpm.d/zz-docker.conf EXPOSE 9000 CMD ["php-fpm"] ####Copy the code

Add www-data as php-fpm run user, and specify PHP configuration file to /usr/local/etc/ PHP. The next step is to download phP-src and compile and install it. Here you can refer to my previous article on compiling and installing PHP. The parameters are in good order. The installation directory is specified to /usr/local, and then scanelf is used to get a list of dependent runtimes, and the other installation packages are removed. Copy docker-php-ext-configure, docker-php-ext-enable, and docker-php-ext-install to the container for subsequent installation of the extension. Then copy php-fpm.conf to the configuration directory and specify error_log and access_log to the terminal standard output. Daemonize = no indicates that the server process is not running. The EXPOSE 9000 port is used to communicate with other containers, and then CMD [“php-fpm”] runs php-fpm. And the working directory is specified to /var/www/html.

docker-compose

Now that we have the base image, we can use the base image to configure the container, but starting the container with the manual Docker command is cumbersome. But fortunately has been provided by the official docker – compose command to container, only need to write a docker – compose. Yaml file, specific can refer to the official document.

Version: '2' services: php-fpm: image: PHP :7.0.7-fpm-alpine volumes: - "./ SRC :/var/www/html" restart: always tengine: Depends_on: -php-fPM Links: -php-fPM Image: Chasontang/Tengine: 2.2_f Volumes: - "./nginx.vh.default.conf:/etc/nginx/conf.d/default.conf" ports: - "80:80" restart: alwaysCopy the code

The tengine service relies on the php-fpm service. The tengine service relies on the php-fpm service. And link the PHP-FPM service, so that it can communicate with the PHP-FPM container over the network, tengine service based on chasontang/ Tengine :2.1.2_f image, And the nginx. Vh. Default. The conf file mapping to the/etc/nginx/conf. D/default. The conf file. Then look at nginx.vh.default.conf

server { listen 80; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { root html; index index.html index.htm; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php${# proxy_pass http://127.0.0.1; #} location ~ [^/]\.php(/|$) { fastcgi_split_path_info ^(.+? \.php)(/.*)$; fastcgi_pass php-fpm:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; include fastcgi_params; } # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #}}Copy the code

The tengine image actually uses two configuration files, one is /etc/nginx/nginx.conf and all files in the /etc/nginx/conf.d/ directory, /etc/nginx/nginx.conf include /etc/nginx/conf.d/*. Include this directory, that is, do not need to manage the other nginx configuration, just need to replace the default nginx virtual host configuration, or add virtual host configuration on the line.

As you can see above, the default.conf file defines a location match to the URL that contains.php, and then splits it out of the PATH_INFO argument, passing these variables to the php-Fpm service of phP-Fpm :9000.

It is important to note that since Nginx and PHp-FPM are not on the same host, Nginx only does static file processing and routing. The actual PHP file execution takes place in the php-FPM container. So the SCRIPT_FILENAME variable must use the directory in the PHP-FPM container, so it is hardcoded here. Of course, it is possible to have two containers sharing the same data volume, but in my opinion, this is just for the convenience of container choreography and nothing else at all.