In this repository, I try to collect various examples of how a PHP-centric web application can be developed and shipped in a container-centric way.
My wishlist items are:
Working on the project should not require complex VM setups (Vagrant or similar). It should be easy to switch between projects, and each project should be able to use different tools and/or different versions of the same tools.
Probably the best way to make this work is when the languages and tools (php
, node
, composer
, yarn
, php-cs-fixer
, ...) are run in containers or during Docker builds FROM
suitable base images.
Avoid building "fat" Docker images where a lot of things (tools) are installed into a single image. I consider it an antipattern to have a development
Docker image where PHP, Apache, Node.js, yarn, ... get installed. Effectively, this is a Vagrant VM disguised as a Docker image.
Instead, (re)use (pre-)exisiting images. Use a node
or node
-based container for Node-related tasks, and a php
-based one for PHP. That also seems to follow the "one process per container" Docker philosophy.
It does not mean you cannot have your own "base" images. It's about having dedicated containers.
Keeping things up to date is much easier if you don't have to rebuild a "fat" image whenever a package or tool in it needs to be updated. Instead, use and update e. g. the PHP container independently of the Node container.
Also, mixing different versions (e. g. Node 14 and PHP 7.4 in one project, Node 16 and PHP 8.1 in another) is much easier to maintain when you don't build images that are cross-combinations of all needed versions, but single images per version.
Use the same script/tool/workflow definition for the same task, everywhere.
For example, don't put docker run ... -v $(pwd):$(pwd) ... composer install
into a ./go script (see here, there) to fetch vendors during development, while using FROM composer:latest... RUN composer install ...
in a Dockerfile
on CI.
There should be one definition of how to perform a particular task, and it should be used during development, CI runs and for the final image builds in the same way.
For example, do not maintain a Dockerfile.dev
to build an image used during development (or CI test runs), and a different Dockerfile
to build the final production image.
This is somewhat a corollary of the preceding item, since there should be no two places that define the same task or step, or even variations of it.
In the best case, tests (at least on CI) are being run on, on top of or against the Docker image that will be shipped to production.
When the image used for testing or development requires additional software or tools installed (say, xdebug
for PHP), it seems preferrable to add that to the production (base) image, instead of building completely separate images. Of course, it would be even better if such tools could be run from dedicated, independent containers against the existing image.
During development, you will often need to run tasks in an "ad hoc" manner that is not necessary during CI runs or during builds of the final image.
For example, it should (easily) be possible to run composer why-not foo/bar
or yarn add some-dependency
using the appropriate containers/images. The build process itself probably only needs composer install
, yarn
or gulp
to be run.
I've mentioned ./go
scripts before. To improve DX, a common (similar) entry point for all these tasks would be nice.
At least for PHP-centric applications, it is necessary to use filesystem mounts at development time so that you can change code in the IDE and immediately reload a page in the browser, even when the webserver is running in a Docker container.
Vendors possibly being fetched by a containerized tool (vendor
directory for composer
, node_modules
for yarn
) need to be visible to the IDE to support code inspection and completion.
For artifacts being built (e. g. Webpack results), this is not strictly necessary, but probably helpful.
It must be possible to fetch vendors from private repositories/registries.
This probably means one or several of the following:
- Build secrets can be used
- Multi-stage builds make sure no sensitive data leaks into final images
- Things like the SSH auth socket or necessary tokens mounted into containers only at run-time
There are good reasons why the need for multi-platform builds increases in the future.
The approach taken to build the application must not prevent the use of buildx
or prevent multi-platform builds for final images.
For PHP/Node-centric web applications, the application code itself (PHP, [compiled/transpiled] JavaScript) is architecture-independent. The PHP interpreter or the webserver used are not.
When using specialized tooling (beyond shell scripts or Makefiles) to orchestrate the build process, do these tools have a healthy ecosystem?
- A reasonable base of other people using them
- Enough maintainers or contributors so we can assume they will be maintained for a longer time
- Sponsors/backing organisations
- No (complicated) software dependencies on their own
Some tools under consideration (#3, #12) allow to share configuration (e. g. Dockerfiles or configuration to spin up PHP and a webserver) via GitHub URLs.
Might be a nice-to-have feature when dealing with a multitude of projects that all share a similar structure and tech stack.
To understand, document and illustrate the different approaches to building a dockerized application, I plan to maintain different branches in this repository. Hopefully, that allows to judge the solutions based on the criteria outlined above.
Working with the sample application in this repo requires:
- Run
composer install
to fetch PHP dependencies - Run
yarn
to install JS dependencies - Run
node_modules/.bin/gulp
to build front-end assets (creates thehtml/time
file as an example)
During development, it should be possible to start a webserver and see the application's output at http://localhost:...
or similar, while being able to modify the html/index.php
file. The container image used should include xdebug.
It should be possible to run ad-hoc commands like composer show
or yarn list
, or re-build the assets by running Gulp.
A simple task/command should be available to build the production image.