BreadcrumbHomeResourcesBlog PHP Docker Images Tips and Tricks March 1, 2022 PHP Docker Images Tips and TricksPHP DevelopmentBy Matthew Weier O’PhinneyIn a previous post, I detailed PHP Docker orchestration, and noted that Zend now offers its own container registry with ZendPHP images. We also offer a downloadable, flexible, customizable Dockerfile for ZendPHP images (found here) that can greatly simplify image creation, particularly when using orchestration technologies.In this post, I'll detail that Dockerfile, showing how you can use the same file to create different custom PHP Docker images, as well as demonstrate some additional techniques you can use.Table of ContentsWhat Is a Dockerfile?Can PHP Run on Docker?Exploring The DockerfileCustomizing PHP Docker ImagesBuilding PHP Docker Images for ProductionPHP Docker Images: Multi-Stage BuildsThe Benefits of PHP Docker Images From ZendTable of Contents1 - What Is a Dockerfile?2 - Can PHP Run on Docker?3 - Exploring The Dockerfile4 - Customizing PHP Docker Images5 - Building PHP Docker Images for Production6 - PHP Docker Images: Multi-Stage Builds7 - The Benefits of PHP Docker Images From ZendBack to topWhat Is a Dockerfile?A Dockerfile is a text file that describes how to build the initial state of a container or image. Most consumers will extend an existing Docker image and add additional instructions to it to provide settings specific to their own application. Base images can be an operating system (e.g., Ubuntu 20.04, Alpine Linux, or CentOS 8), or another image built on these.Back to topCan PHP Run on Docker?Yes, PHP runs on Docker. Often, a base image will provide a language runtime on top of a base operating system image, ensuring that you can run applications that target that language within that operating system. PHP is no different, and there are a number of different PHP images you can target in your application Dockerfile. We supply our own base images for ZendPHP, and provide extensive tooling to make configuring your PHP runtime easier, reducing the amount of knowledge you need to be successful with PHP in Docker.Back to topExploring The DockerfileAn uncommented version of the Dockerfile looks like the following:ARG OS=ubuntu ARG OS_VERSION=20.04 ARG ZENDPHP_VERSION=7.4 ARG BASE_IMAGE=fpm FROM cr.zend.com/zendphp/${ZENDPHP_VERSION}:${OS}-${OS_VERSION}-${BASE_IMAGE} ARG TIMEZONE=UTC ARG INSTALL_COMPOSER= ARG SYSTEM_PACKAGES ARG ZEND_EXTENSIONS_LIST ARG PECL_EXTENSIONS_LIST ARG POST_BUILD_BASH 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. RUN ZendPHPCustomizeWithBuildArgs.shThe above Dockerfile will build a PHP-FPM 7.4 image using Ubuntu 20.04, using the base extension list.Dockerfile Build ArgumentsYou'll notice that you can customize it using a variety of build arguments:OSThe operating system. We have Ubuntu, CentOS, and Debian builds available currently in our container registry.OS_VERSIONThe version of the operating system. We support Ubuntu 20.04, Debian 10, and CentOS 7 and 8 currently in our container registry.ZENDPHP_VERSIONWhich ZendPHP version you want to install: 5.6, 7.1, 7.2, 7.3, 7.4, 8.0, or 8.1 are all available, though LTS version (all prior to 7.4) require some additional setup in order to work, as they require Zend licenses.BASE_IMAGEOne of "cli" or "fpm". CLI versions provide a build with only the PHP CLI binary, while FPM versions provide both the CLI and PHP-FPM binaries. The CLI versions are often useful in CI/CD pipelines for things such as running PHPUnit, PHPCS, or static analysis. PHP-FPM binaries can be used behind any web server that supports FastCGI.TIMEZONEThe system timezone to use. We recommend using UTC, but if you need to use a local timezone, it can be set via a build argument.INSTALL_COMPOSERIf a "truthy" value, the image will install Composer during build.SYSTEM_PACKAGESA comma- or space-separated list of additional system packages to install, if any.ZEND_EXTENSIONS_LISTA comma- or space-separated list of additional PHP extensions available in the ZendPHP repository to install; these can be specified using just the extension name (e.g. "mysqli") instead of the full package name.PECL_EXTENSIONS_LISTA comma- or space-separated list of PECL extensions to build and install. These can be just the PECL extension name (e.g., "inotify"), the name and version (e.g., "inotify-0.1.6"), and/or a prefix indicating the initialization priority once installed (e.g., "30-swoole"; default priority is 20, with lower priorities initializing earlier).POST_BUILD_BASHA shell script or other executable to execute as the last build step. This can be useful for doing things such as moving files around, setting permissions, or other tasks required to ensure the container is ready to run. These are a lot of build arguments, and you likely won't want to specify them on the command line regularly:$ docker build \ > --build-arg OS=debian \ > --build-arg OS_VERSION=10 \ > --build-arg ZENDPHP_VERSION=8.1 \ > --build-arg ZEND_EXTENSIONS_LIST=mysqli,gd,intl \ > --build-arg POST_BUILD_BASH=setup.sh \ > -f Dockerfile.custom \ > -t mybiz/php-fpm-8.1Because you will have no application files present, just an empty PHP install, let's figure out ways to customize the image.Back to topCustomizing PHP Docker ImagesWhen performing PHP Docker, you will generally use another technology such as Compose, Swarm, or Kubernetes. These technologies each allow you to describe services and (generally speaking) these descriptions allow you to specify either an image to consume and customize with volumes, networks, and environment variables, or a Dockerfile and a build context (which can include things such as build arguments). It's this latter that I want to look at now.To keep the examples simple, I'm going to use Compose here. However, they can be extrapolated to Swarm, Kubernetes, Helm, and other solutions.We'll use the base Dockerfile, and provide configuration for the build.services: php: build: context: . dockerfile: Dockerfile args: OS: debian OS_VERSION: 10 ZENDPHP_VERSION: 8.1 INSTALL_COMPOSER: 'true' ZEND_EXTENSIONS_LIST: 'bz2 curl intl mbstring opcache xsl zip' POST_BUILD_BASH: '/var/www/scripts/setup.sh' volumes: - .:/var/www/The above will:Create a PHP-FPM 8.1 image using Debian 10Install ComposerAdd and enable the bz2, curl, intl, mbstring, opcache, xsl, and zip extensionsMap the current directory to /var/wwwRun a post-build script relative to the mapped directoryThis sort of approach allows you to become immediately productive in development. You can tweak the extensions list, change PHP versions, and more with minor edits.Here's a Compose file that describes a container utilizing OpenSwoole instead:version: '3.3' services: php: build: context: . dockerfile: Dockerfile args: OS: ubuntu OS_VERSION: 20.04 ZENDPHP_VERSION: 8.1 BASE_IMAGE: cli INSTALL_COMPOSER: 'true' ZEND_EXTENSIONS_LIST: 'bz2 curl intl mbstring opcache xsl zip' PECL_EXTENSIONS_LIST: '30-openswoole-4.9.0' POST_BUILD_BASH: '/var/www/scripts/setup.sh' volumes: - .:/var/www/ Be aware that when using PECL_EXTENSIONS_LIST, you may need to also specify development packages in the SYSTEM_PACKAGES build argument required to build the extension. When performing PECL installs, the ZendPHP development package (e.g., php-7.4-zend-dev or php74-zend-php-devel) and/or PEAR package will also be installed during build time.The beauty of each of these is that I can use the same Dockerfile over and over, without changes. By using build arguments and environment variables, I am able to customize the image I build.Back to topBuilding PHP Docker Images for ProductionThe previous examples were a good start. However, in production, we generally want to ensure that application files are copied into the image, and not mounted via a volume. This is particularly true when we consider multi-container and/or multi-server orchestrations.As such, we'll need to customize the Dockerfile to do so.If you pay close attention to the basic Dockerfile, you'll note that it has very few directives; the bulk of it is defining build arguments. In most cases, you can provide your customizations in one of two locations:Immediately before the call to ZendPHPCustomizeWithBuildArgs.sh.Immediately following the call to ZendPHPCustomizeWithBuildArgs.sh.As an example, I can copy the application files in:ARG OS=ubuntu ARG OS_VERSION=20.04 ARG ZENDPHP_VERSION=7.4 ARG BASE_IMAGE=fpm FROM cr.zend.com/zendphp/${ZENDPHP_VERSION}:${OS}-${OS_VERSION}-${BASE_IMAGE} ARG TIMEZONE=UTC ARG INSTALL_COMPOSER= ARG SYSTEM_PACKAGES ARG ZEND_EXTENSIONS_LIST ARG PECL_EXTENSIONS_LIST ARG POST_BUILD_BASH 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. COPY . /var/www/ RUN ZendPHPCustomizeWithBuildArgs.sh A huge benefit to this is that we can likely still re-use the Dockerfile with multiple applications. We can assume that at both build and runtime, the application is installed to /var/www/, allowing us to specify post-build scripts, or configure our FastCGI-enabled webserver to pass requests to that location. In development, we can use Compose, where we can map a volume so that as we make changes, they're reflected in the running application.Back to topPHP Docker Images: Multi-Stage BuildsIn a previous example, I noted that when specifying PECL_EXTENSIONS_LIST, I may need to specify system development packages in order to build the extension. In production, I likely don't want those lingering.One way to deal with those is to add another build step that removes the development packages:RUN apt-get remove {list of development packages here}Another way is to ensure they're never present in the first place, which can be done with a multi-stage build. As an example, I recently wanted to build OpenSwoole for a ZendPHP container. I have one stage that performs the build, which requires additional packages. The production image then copies files from that stage into itself.# DOCKER-VERSION 1.3 # Build Swoole FROM cr.zend.com/zendphp/8.1:ubuntu-20.04-cli as swoole ## Prepare image ARG SWOOLE_VERSION=4.9.0 ARG TIMEZONE=UTC ENV TZ=$TIMEZONE RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN set -e; \ apt-get update; \ apt-get install -y php8.1-zend-dev libcurl4-openssl-dev; \ mkdir /workdir; \ cd /workdir; \ curl -L -o swoole-src-${SWOOLE_VERSION}.tgz https://github.com/openswoole/swoole-src/archive/refs/tags/v${SWOOLE_VERSION}.tar.gz; \ tar xzf swoole-src-${SWOOLE_VERSION}.tgz; \ cd swoole-src-${SWOOLE_VERSION}; \ phpize; \ ./configure \ --enable-swoole \ --enable-sockets \ --enable-http2 \ --enable-openssl \ --enable-swoole-json \ --enable-swoole-curl; \ make; \ make install # Build the PHP container FROM cr.zend.com/zendphp/8.1:ubuntu-20.04-cli ## Install Swoole COPY --from=swoole /usr/lib/php/8.1-zend/openswoole.so /usr/lib/php/8.1-zend/openswoole.so COPY --from=swoole /usr/include/php/8.1-zend/ext/openswoole /usr/include/php/8.1-zend/ext/openswoole RUN set -e; \ echo "extension=openswoole.so" > /etc/zendphp/cli/conf.d/60-swoole.ini ## Customizations ARG TIMEZONE=UTC ARG INSTALL_COMPOSER=false ARG SYSTEM_PACKAGES ARG ZEND_EXTENSIONS_LIST ARG PECL_EXTENSIONS_LIST ARG POST_BUILD_BASH ## Prepare tzdata ENV TZ=$TIMEZONE RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone ## Customize PHP runtime according ## to the given building arguments RUN ZendPHPCustomizeWithBuildArgs.sh COPY . /var/www/ ## Set working directory WORKDIR /var/wwwThis approach ensures that I have no build tools in my final PHP Docker image, only the results of building (the extension file and its libraries). The Dockerfile can still be re-used for any OpenSwoole-based images I want to expose, and I now get the benefit of a Docker build caching whenever I create such images, as they will cache the "swoole" stage.Back to topThe Benefits of PHP Docker Images From ZendThe primary benefit of ZendPHP Docker images is the ease of customization: you can use the same Dockerfile and create custom images via build arguments, which can be specified in orchestration files and kept in your version control repositories. This approach allows you to create standard images re-usable in many contexts, with minimal effort.And this article just scratches the surface of what you can do with ZendPHP Docker images! In addition to the features shown here, we also have tools to allow running multiple services within your containers safely, as well as mechanisms for creating running rootless, or non-privileged, containers. Stay tuned for more on that in upcoming blogs.Get Started NowSo, what are you waiting for? Get started with ZendPHP Docker images today!TRY ZENDPHP FREEVisit Our Container Registry Additional ResourcesBlog - Building Rootless Docker Images With ZendPHPBlog - Rootless Containers and Why They MatterWebinar - Orchestrating Your PHP ApplicationsBlog - Cloud Images With ZendPHPBlog - PHP Docker Images: Tips and TricksBlog - PHP Orchestration With ZendPHP Docker ImagesBlog - How to Orchestrate Applications With ZendPHPBack to top
Matthew Weier O’Phinney Senior Product Manager, OpenLogic and Zend by Perforce Matthew began developing on Zend Framework (ZF) before its first public release, and led the project for Zend from 2009 through 2019. He is a founding member of the PHP Framework Interop Group (PHP-FIG), which creates and promotes standards for the PHP ecosystem — and is serving his second elected term on the PHP-FIG Core Committee.