Using ZendPHP via Docker
This topic describes the procedures for using ZendPHP from a Docker image. ARM images are available where supported.
The Docker ecosystem provides a number of tools for container orchestration. Zend by Perforce provides templates for Docker Compose, Docker Swarm/Stack, and Kubernetes (k8s) demonstrating how to set up a session cluster using ZendPHP, a web server, and Redis. You can download these templates from https://www.zend.com/downloads/zendphp-docker-templates.
If you are familiar with using Docker to install or update applications, use the information in the table below to get started; otherwise, continue reading for more detailed information on using ZendPHP from a Docker image.
Zend Container Registry | https://cr.zend.com |
Login | Login first using docker login -u {order-id} cr.zend.com before using the pull command |
pull command | docker pull cr.zend.com/zendphp/{PHP_VERSION}:{OS}-{OS_VERSION}-{specialties}
|
ARM images have the suffix "-arm64" in their tag names.
Use a pre-built ZendPHP image
Zend provides some pre-built images for Ubuntu, CentOS, and Debian in our container registry.
-
Login to Zend's container registry using the credentials provided to you.
docker login cr.zend.com
-
Pull the image. For example, to pull the FPM specialty for PHP 7.4 on Ubuntu 20.04 using the ARM architecture, use the following command:
docker pull cr.zend.com/zendphp/7.4:ubuntu-20.04-fpm-arm64
Use the docker-ls tool to get a full list of the images Zend provides. If you do not see the one you need, please let us know.
After pulling an image you are now ready to run it. See the Using Docker images section for more information on using images.
Build-argument customizable image
If you would prefer to build your own image, Zend provides a customizable Dockerfile.
-
Download the customizable Dockerfile or you can copy and paste the file from Custom Dockerfile.
-
Build the image using the
docker build
command. The list of build arguments is included at the end. For example:docker build -f Dockerfile.Custom -t zendphp-8.0-fpm --build-arg OS=debian \ --build-arg OS_VERSION=10 --build-arg ZENDPHP_VERSION=8.0 --build-arg BASE_IMAGE=fpm \ --build-arg "ZEND_EXTENSIONS_LIST=mysqli pdo pdo-mysql tidy" \ --build-arg POST_BUILD_BASH=prepare.sh .
For this example to work, you MUST have at least one.sh
script present for the custom image (due to theCOPY *.sh /usr/local/sbin
line); either that, or you should omit it if there are no additionalinstall.sh
tasks. -
After building the image, you can use
docker run
to create the container. For example, to create a container from the image you just built you might use:docker run -dp 3000:3000 zendphp
After building an image you are now ready to run it. See the Using ZendPHP Docker images section for more information on using images.
Using built images
The following sections are relevant to all built images and cover changes that occurred when introducing the ability to run rootless images.
zendphp user and group
Images create the following user and group:
-
User zendphp, with UID 10000
-
Group zendphp, with GID 10001
php-fpm images always run worker processes as the zendphp user.
ZendPHPCustomizeWithBuildArgs.sh
All images include the script ZendPHPCustomizeWithBuildArgs.sh
, which can be invoked in Dockerfile extensions that build FROM ZendPHP base images.
The script uses build arguments to customize the image, including:
-
Setting up the container's system timezone, including in PHP configuration.
-
Setting up ZendPHP package repository credentials (required in order to install additional extensions).
-
Installing additional system packages.
-
Installing pre-packaged PHP extensions from the ZendPHP package repository.
-
Installing PECL extensions.
-
Installing Composer.
-
Installing php-fpm configuration, using either a development or production profile.
-
Running a custom post-build script.
-
Setting up the container to run as the zendphp user (instead of root).
Build arguments
The ZendPHPCustomizeWithBuildArgs.sh
script uses the following Docker build arguments:
- TIMEZONE: Timezone to use within the image. This is set to UTC by default.
- ZENDPHP_REPO_USERNAME and ZENDPHP_REPO_PASSWORD: The username/order ID and password associated with your ZendPHP package repository credentials. When both are specified, the values are used to setup access credentials to the ZendPHP package repository within your image, allowing you to install additional ZendPHP packages.
- SYSTEM_PACKAGES: Space or comma-separated list of additional system packages to install. This is not set by default.
- ZEND_EXTENSIONS_LIST: Space or comma-separated list of additional ZendPHP packages to install (e.g., mysqli, pgsql, and so forth). This is not set by default. Do not include any package prefix. (Packages are prefixed based on the OS, and generally follow the format
php7.4-zend
orphp74zend-
.) - PECL_EXTENSIONS_LIST: Space or comma-separate list of PECL extensions to install using the PECL tool. This is not set by default.
- INSTALL_COMPOSER: Select whether or not to add Composer to the image. It is not set by default. Set it to any non-empty value if you wish to add Composer to the image.
- ZEND_PROFILE: (php-fpm images only) Set to
dev
ordevelopment
to use a development php-fpm configuration instead of production settings. Any values other thandev
ordevelopment
, including no value, enables a production configuration. - POST_BUILD_BASH: Fully qualified path to a shell script, or name of a shell script located under
/usr/local/sbin/
in the container. When present,ZendPHPCustomizeWithBuildArgs.sh
runs the POST_BUILD_BASH script as a final setup step. - RUN_FPM_AS_ZENDPHP_USER and RUN_FPM_AS_ZENDPHP_USER_APPDIR: These settings cause the php-fpm binary manager process to execute under the zendphp user instead of under root. To do this, RUN_FPM_AS_ZENDPHP_USER must be a non-empty value and RUN_FPM_AS_ZENDPHP_USER_APPDIR should point to the application root. When enabled,
ZendPHPCustomizeWithBuildArgs.sh
changes ownership of all files under the specified application directory to the zendphp user. - ZENDPHP_REPO_USERNAME and ZENDPHP_REPO_PASSWORD: Use these arguments to provide the ZendPHP repository credentials to your image.
Custom Dockerfile
Zend provides you with a customizable Dockerfile you can use to create a custom image. You can download the file from this page or copy it from here:
# OS=one of ubuntu, centos, or debian ARG OS=ubuntu # OS_VERSION=OS version to use; e.g., 20.04, 8, and 10 ARG OS_VERSION=20.04 # PHP version to use; can be 5.6, 7.1, 7.2, 7.3, 7.4, or 8.0. # Ability to build a version is dependent on having ZendPHP credentials that # authorize use of that version. ARG ZENDPHP_VERSION=7.4 # BASE_IMAGE=cli or fpm ARG BASE_IMAGE=fpm # Append -arm after $BASE_IMAGE if you wish to use the ARM variant FROM cr.zend.com/zendphp/${ZENDPHP_VERSION}:${OS}-${OS_VERSION}-${BASE_IMAGE} # CUSTOMIZATIONS # # TIMEZONE=timezone the OS should use; UTC by default ARG TIMEZONE=UTC # INSTALL_COMPOSER=true or false/empty (whether or not Composer will be installed in # the image) ARG INSTALL_COMPOSER= # ZENDPHP_REPO_USER and ZENDPHP_REPO_PASSWORD: provide your ZendPHP repository # credentials using these build arguments to ensure you can install additional # extensions. ARG ZENDPHP_REPO_USER ARG ZENDPHP_REPO_PASSWORD # SYSTEM_PACKAGES=space- or comma-separated list of additional OS-specific # packages to install (e.g., cron, dev libraries for building extensions, etc.) ARG SYSTEM_PACKAGES # ZEND_EXTENSIONS_LIST=space- or comma-separated list of packaged ZendPHP # extensions to install and enable. These should omit the prefix # php{VERSION}-zend (DEB) or php{VERSION}zend (RPM); e.g., mysqli, pdo-pgsql, etc. ARG ZEND_EXTENSIONS_LIST # PECL_EXTENSIONS_LIST=space- or comma-separated list of PECL extensions to # compile, install, and enable. You will need to install the dev/devel package # for the ZendPHP version you are using, and any additional devel libraries # that may be required. ARG PECL_EXTENSIONS_LIST # POST_BUILD_BASH=full path to a bash script or name of a bash script under # /usr/local/sbin to execute following build tasks. You will need to ADD or COPY # these to the image before calling ZendPHPCustomizeWithBuildArgs.sh, and ensure # they are executable. Such scripts can be used to further customize your image. ARG POST_BUILD_BASH # Prepare tzdata ENV TZ=$TIMEZONE \ YUM_y='-y' RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # ADD or COPY any files or directories needed in your image here. # Customize PHP runtime according to the given building arguments. # Generally, this should be the last statement of your custom image. RUN ZendPHPCustomizeWithBuildArgs.sh
For more information on specific customizations for RHEL customers, see Using RHEL-based-Docker-containers.
s6 overlay
All ZendPHP images contain the s6 overlay, which is a container-specific version of the s6 process supervision suite that provides additional features such as hooks for changing file system permissions on initialization, firing arbitrary initialization scripts, and firing finalization scripts on container shutdown.
CLI images do not use s6 by default, but FPM images do. However, CLI images do install the s6 overlay, so you can make use of it in your custom CLI images, which can be useful when writing daemons in PHP, particularly when using an async runtime such as OpenSwoole, ReactPHP, or AMPHP.
Using the s6 overlay provides the following benefits:
-
Process segregation. This provides an extra layer of security that helps prevent malicious actors from breaking out of the container.
-
Process supervision. s6 restarts daemons when they die, automatically.
-
Ability to run daemons as non-privileged users.
-
Ability to run multiple daemons in a single container, and supervise each.
We recommend reading the s6 overlay documentation to understand the full range of features that are available to you.
Running additional daemons
Writing a daemon involves the following:
-
Creating a directory for the daemon under
/etc/services.d
. -
Adding an executable file named
run
under your new directory.
For example, if you want to run cron on your system you would create the directory /etc/services.d/crond/
, place a file named run
inside it, and make the file executable (chmod 755 /etc/services.d/crond/run
).
The contents of that file might look like:
#!/bin/sh set -e echo "Starting cron daemon" if [ -x /usr/sbin/crond ];then # RHEL systems crond -n elif [ -x /usr/sbin/cron ];then # DEB systems cron -f else echo "FAILED TO FIND CRON DAEMON!" exit 1 fi
Note that these start the cron daemon in foreground mode and not background mode; this is done so that s6 can monitor them and restart them if necessary.
You could also use the s6 execlineb
shell to write this script:
#!/usr/bin/execlineb -P # Debian system cron cron -f
Running daemons as non-privileged users
In order for a daemon to run as a non-privileged user, you can make use of the s6-setuidgid
utility in your /etc/services.d
run scripts. We recommend using execlineb
for such applications.
As an example, to run the previous crond example as the zendphp user:
#!/usr/bin/execlineb -P s6-setuidgid zendphp cron -f
services.d
run script that does the above for the php-fpm process.Entrypoint.d and cont-init.d
Previous versions of ZendPHP images provided an /entrypoint.d/
directory for both executable and non-executable shell scripts. On container start, executable shell scripts in this directory are executed and non-executable shell scripts are sourced.
Since the addition of the s6 overlay to ZendPHP images, we recommend using the /etc/cont-init.d
directory for executable shell scripts, while keeping any sourced shell scripts (e.g., those used to set environment variables and/or define globally available shell functions) in /entrypoint.d/
.
Using ZendPHP Docker images
After building a ZendPHP docker image, you need to run it. In the case of a CLI image, this is a no-op; you need to specify a command in the container to run. For FPM images, running the image starts a php-fpm pool.
CLI images
CLI images specify the PHP binary as the container entrypoint. As such, you can run the image with arguments to the binary.
-
Report the PHP version:
docker run cr.zend.com/zendphp/7.4:debian-10-cli -v
-
Run the PHP REPL:
docker run cr.zend.com/zendphp/7.4:debian-10-cli -a
-
Run a PHP script. The following example runs the script
test.php
from the current working directory:docker run -it -v $(realpath ${PWD}):/var/www -w /var/www cr.zend.com/zendphp/7.4:debian-10-cli test.php
-
Run Composer:
docker run -it -v $(realpath ${PWD}):/var/www -w /var/www cr.zend.com/zendphp/7.4:debian-10-cli composer install
Consuming an FPM image
By default, Zend's FPM images configure a PHP-FPM pool that runs on port 9000, and the image exposes port 9000. This is true even of images you create using Zend's custom Dockerfile. For this, you need an Apache, nginx, or other webserver sitting in front of your ZendPHP FPM image(s) in order to consume them. These can be their own Docker images or be standalone servers running elsewhere and connecting to your instances.
The ZendPHP FPM containers contain a healthcheck script, /usr/local/bin/fpm-healthcheck.sh
. Your container orchestration can use this script to verify that the container is healthy and capable of receiving requests.
The following example demonstrates a docker-compose.yml
healthcheck configuration for a PHP-FPM service:
healthcheck: test: /usr/local/bin/fpm-healthcheck.sh interval: 30s timeout: 10s retries: 5 start_period: 30s
Via Apache
Your Apache instance needs the mpm_event
module enabled (and the mpm_prefork
module disabled). Additionally, it requires the proxy
and proxy_fcgi
modules. Refer to the Apache manual and/or your distribution's tooling for information on how to accomplish these tasks.
From there, create a configuration file in the conf.d
subdirectory of your Apache configuration; generally, this is /etc/httpd/conf.d
, /etc/apache2/conf-enabled
, or similar. Name the file php-fpm.conf
and create it with the following contents:
# Allow php to handle Multiviews # AddType text/html .php # Add index.php to the list of files that will be served as directory # indexes. DirectoryIndex index.php # Enable http authorization headers SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1 # Redirect to php-fpm <FilesMatch \.(php|phar)$> SetHandler "proxy:fcgi://{HOSTNAME}:9000" </FilesMatch>
Substitute {HOSTNAME}
with the name of the server or container exposing php-fpm.
Once your changes are made, restart your web server, or fire up your container with the Apache instance, and verify that you can reach PHP pages in your application.
Via nginx
In an nginx virtual host definition, you can tell the server to proxy to the php-fpm instance by matching any .php
file. As an example:
location ~ \.php$ { include /etc/nginx/fastcgi_params; fastcgi_pass {HOSTNAME}:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; }
Substitute {HOSTNAME}
with the name of the server or container exposing php-fpm.
Once your changes are made, restart your web server, or start your container with the nginx instance, and verify that you can reach PHP pages in your application.
Using the custom Dockerfile with docker-compose
One benefit to the custom Dockerfile is that you can use the same file with multiple services, which is particularly useful when using orchestration via docker-compose.
As an example, the following sets up a ZendPHP PHP-FPM container with a development profile for PHP 7.4 using Debian 10; the container installs the mbstring, zip, and mysql extensions and includes Composer. It also includes an nginx image that depends on the PHP image and proxies to it using a custom vhost definition specified in the application itself.
version: '3' services: php: build: context: . dockerfile: Dockerfile args: OS: 'debian' OS_VERSION: '10' ZENDPHP_VERSION: '7.4' BASE_IMAGE: 'fpm' TIMEZONE: 'UTC' ZEND_EXTENSIONS_LIST: 'mbstring,zip,mysql' ZEND_PROFILE: 'dev' INSTALL_COMPOSER: 1 env_file: - .env volumes: - .:/var/www nginx: image: nginx:latest depends_on: - php ports: - "8080:80" env_file: - .env volumes: - .:/var/www - ./data/nginx/dev.conf:/etc/nginx/conf.d/default.conf
If you needed another PHP service, you could add it to your docker-compose.yml
and have it reference the same Dockerfile, but with different build arguments:
version: '3' services: php: build: context: . dockerfile: Dockerfile args: OS: 'debian' OS_VERSION: '10' ZENDPHP_VERSION: '7.4' BASE_IMAGE: 'fpm' TIMEZONE: 'UTC' ZEND_EXTENSIONS_LIST: 'mbstring,zip,mysql' ZEND_PROFILE: 'dev' INSTALL_COMPOSER: 1 env_file: - .env volumes: - .:/var/www api: build: context: . dockerfile: Dockerfile args: OS: 'debian' OS_VERSION: '10' ZENDPHP_VERSION: '8.0' BASE_IMAGE: 'fpm' TIMEZONE: 'UTC' ZEND_EXTENSIONS_LIST: 'mbstring,zip,intl' ZEND_PROFILE: 'dev' env_file: - .env volumes: - ./api:/api nginx: image: nginx:latest depends_on: - php ports: - "8080:80" env_file: - .env volumes: - .:/var/www - ./data/nginx/dev.conf:/etc/nginx/conf.d/default.conf
Environment Variables
You can provide environment variables when starting the container, or in your compose or kubernetes configuration.
The following optional environment variables are supported:
- ZENDHQD_URI
Define this variable to indicate the URI to the ZendHQ service.
Examples:- tcp://zendhq:10900
- tcp://10.0.1.250:10900
ZendPHP User and Group
The Docker images create the following users and groups:
-
A “zendphp” user with UID 10000
-
A “zendphp” group with GID 10001
Use this user and group to set permissions of directories and files mounted through volumes, for example, a cache directory in the application.