We recently released the v2 of thecodingmachine/php docker images. It is a set of "general purpose" PHP images for Docker, with a big emphasize on developer experience.
TL;DR
Have a look at thecodingmachine/php. This project contains a set of Docker images containing PHP with:
- 3 variants: Apache, CLI or PHP-FPM
- NodeJS (optional, version 8 or 10)
- The most common PHP extensions (use environment variables to enable them)
- Cron (configurable via environment variables)
- ... and much more!
What's new?
thecodingmachine/php v1 was a set of fat images. Fat? They contain almost all the PHP extensions available in PHP. This was very useful for a development environment, but of course, it's not optimal: the image ships with a number of PHP extensions you will never use.
thecodingmachine/php v2 brings new "slim" images. They are designed to be used as a base image for your production
images. They come with no extensions pre-loaded, but you can enable any PHP extension with a single "ARG" in your Dockerfile
.
Also, PHP 7.3 is now available in the supported versions.
v2 also comes with a number of new extensions, and in particular Swoole, php-ds and mailparse.
Image type | Fat | Slim (new) |
---|---|---|
Typical usage | Development environment, CI | Optimized production image |
Extensions enabled by default | apcu mysqli opcache pdo pdo_mysql redis zip soap | none |
Extensions available (compiled) | Virtually all popular extensions | none |
Enabling an extension | Use an environment variable. i.e.: PHP_EXTENSIONS="xdebug swoole" |
Add it at build time: ARG PHP_EXTENSIONS="xdebug swoole" |
Fat images
The usage of "fat" images is unchanged. They are designed to be used in docker-compose.
docker-compose.yml
version: '3'
services:
my_app:
image: thecodingmachine/php:7.3-v2-apache-node10
environment:
# Enable the PostgreSQL extension
PHP_EXTENSION_PGSQL: 1
# Disable the Mysqli extension (otherwise it is enabled by default)
PHP_EXTENSION_MYSQLI: 0
Slim images
Slim images are new. They are meant to be used as a base image in your Dockerfiles
.
Slim images come with no extension, but you can compile any of them at build time:
Dockerfile
ARG PHP_EXTENSIONS="apcu mysqli opcache pdo pdo_mysql redis zip soap"
FROM thecodingmachine/php:7.3-v2-slim-apache
# The build will automatically trigger the download and compilation
# of the extensions (thanks to a ONBUILD hook in the slim image)
The build process does not only take care of running pecl install
. It does also install all the required dependencies and it
takes care of removing the unneeded development files once the build is done.
But also...
thecodingmachine/php v2 images come with all the goodies from v1. It is a "batteries included" PHP image:
- Composer is installed
nano
editor is installed- the
xdebug
extension, if enabled, will configure the remote host automatically (and correctly whether you use Linux, Windows or MacOS!) - ...
Variants
Images are tagged according to PHP version, image version, variant and Node version.
For instance:
thecodingmachine/php:7.3-v1-[slim]-apache-node10
^ ^ ^ ^ ^
| | | | |
PHP version | | | |
| | | |
Image version | | |
| | |
Slim / Fat | |
| |
Variant (apache, cli or fpm) |
|
Node version (empty, 8 or 10)
We have images for PHP 7.1, 7.2 and PHP 7.3.
Extremely configurable
Using only environment variables you can:
- Edit any php.ini setting
- Add/remove PHP extensions
- Add/remove Apache extensions (Apache variant only)
- Add startup scripts (for instance "
composer install
") - Add Cron jobs
docker-compose.yml
version: '3'
services:
my_app:
image: thecodingmachine/php:7.3-v2-apache-node8
environment:
# Let's enable the PostgreSQL extension and disable the Mysqli extension!
PHP_EXTENSION_PGSQL: 1
PHP_EXTENSION_MYSQLI: 0
# set the parameter memory_limit=1g
PHP_INI_MEMORY_LIMIT: 1g
# set the parameter error_reporting=EALL
PHP_INI_ERROR_REPORTING: E_ALL
# Let's enable Webdav and SSL on Apache
APACHE_EXTENSION_DAV: 1
APACHE_EXTENSION_SSL: 1
# Apache document root is the "web" directory in your project.
APACHE_DOCUMENT_ROOT: web/
# On startup, let's run composer install and apply DB migrations
STARTUP_COMMAND_1: composer install
STARTUP_COMMAND_2: vendor/bin/doctrine orm:schema-tool:update
# Finally, let's set-up cron jobs
CRON_USER_1: root
CRON_SCHEDULE_1: * * * * *
CRON_COMMAND_1: vendor/bin/console do:stuff
Other changes
The structure of the project has been deeply modified to make it more manageable.
All extensions have now a dedicated installation script.
It is way easier to create a pull request for v2 than it was for v1.
Miscellaneous changes:
- PHP 7.1 base image is now Debian Stretch
- We dropped support for Node6. Node 8 and Node 10 are available.
Usage in continuous integration environments
Those images can be tremendously useful in continuous integration environments.
At TheCodingMachine, we are pretty fond of Gitlab CI. Our CI file now looks like this:
test:
image: thecodingmachine/php:7.3-v2-cli
variables:
PHP_EXTENSIONS=gd event
before_script:
- composer install
script:
- vendor/bin/phpunit
Woot! So easy!
Actually, I can even replace vendor/bin/phpunit
by phpunit
alone, because ./vendor/bin
is part of the PATH.
That's right, anything in vendor/bin
directory can be accessed from your project's root directory... Did I say developer friendly? :)
File permissions management
Permissions management is a tricky issue when it comes to Docker. Depending on your use case (development or production), and depending on the OS you are using, you can have a wide range of issues to solve. We really tried to do our best to simplify this, without sacrificing security.
File permissions on a development environment
In a development environment, you mount your working directory into the container's /var/www/html
directory.
For a good development workflow:
- your IDE must have write access to the files
- your web-server should have write access to the files (for caching or upload purposes)
- scripts executed in the container (like
composer install
orphp-cs-fixer
) should have write access too
If you are using MacOS or Windows, Docker does not really enforce any permissions in the file system. For instance, any user can modify any files owned by root on a OSX docker mount point.
For Linux users, things are more secure... and more tricky! Docker will
enforce the permissions across the mount points. If your run a composer install
in the Docker container as root
, your files
will belong to root on the host file system. This is something you want to avoid because your IDE won't be able to touch those files!
The thecodingmachine/php image solves this problem by taking the following steps:
- Out of the box, it has a
docker
user (whose ID is 1000 by default) - Apache is run by this
docker
user (and not bywww-data
) - On container startup, a script will first try to detect if the
/var/www/html
directory is mounted or not, and whether it is a Windows, MacOS, or Linux mount. - If this is a Linux mount, it will look at the owner of the
/var/www/html
directory. Let's assume the directory belongs to the user "foobar" whose ID is 1001. Dynamically, the container will change thedocker
ID to be 1001 (instead of 1000) by default. This is done using the-u
flag of theusermod
command. - Therefore, the ID of the docker user (that is running Apache) and the ID of the mounted directory owner on the host are matching. No more permission issues while developing (Hooray!)
File permissions on a production environment
Of course, on a production environment, you don't want this. On a production environment, you will typically not use
any mount. Instead, you will copy your PHP files inside the container's /var/www/html
directory.
By default, the /var/www/html
directory belongs to www-data
. The container will detect this and act accordingly.
You should still give back ownership of the Apache processes to the www-data
user. This can be done easily using
one more environment variable:
ENV APACHE_RUN_USER=www-data \
APACHE_RUN_GROUP=www-data
Is this following Docker best practices?
thecodingmachine/php v1 was violating a number of Docker best-practices. In particular, it contained most of the PHP extensions, even if you did not use them. thecodingmachine/php v2 images with the slim variant are fixing this issue, so they are really better tailored for production usage.
Are they 100% state-of-the-art Docker images? Probably not.
So you want to be state-of-the-art?
Instead of using thecodingmachine/php, you should do this:
Avoid installing unnecessary packages
thecodingmachine/php v2 contains a lot of pretty useful packages for development, but that are not needed for production (like the nano editor, or Composer).
If you want to be state of the art, you should write your own Dockerfile
and install the bare-minimum in the container.
Of course, you should store the image in your own registry.
Use multi-stage builds
Some variants of thecodingmachine/php come with NodeJS installed. The expectation is that you will need NodeJS to build
your JS/CSS assets (probably using webpack
).
But the image will run in production with NodeJS installed, while it is absolutely not necessary
(it is used only at build time).
Starting with Docker 17.05, Docker added this wonderful feature named multi-stage builds.
From your Dockerfile, you can call another container to perform build stages. From your PHP container, you could call a NodeJS container to perform a build and copy the built files back to your PHP container. The PHP container never stores NodeJS runtime. Useful.
Each container should have only one concern
thecodingmachine/php images bundle cron. So strictly speaking, they have 2 concerns:
- one is to answer HTTP requests
- one is to trigger events at regular intervals
If you want to be state of the art, you should delegate the scheduling of events to a separate container like Tasker or one of the other alternatives.
Image size
Here are a few image size comparison:
Image | Size (uncompressed) | Variation |
---|---|---|
php:7.2-apache |
377 MB | |
7.2-v2-slim-apache |
460 MB | +83 MB (+22%) |
7.2-v2-apache |
606 MB | +229 MB (+60%) |
So should I use the thecodingmachine/php images?
Give it a try, you won't regret it!
thecodingmachine/php general purpose images help you starting quickly, while ensuring a pretty decent quality.
About the author
David is CTO and co-founder of TheCodingMachine and WorkAdventure. He is the co-editor of PSR-11, the standard that provides interoperability between dependency injection containers. He is also the lead developper of GraphQLite, a framework-agnostic PHP library to implement a GraphQL API easily.