PHP Docker Images: Three Tiers, One Philosophy
Every team builds their own PHP Docker images. Most end up with 800MB images full of build tools. Here is how I designed three tiers (Slim, Standard, Full) that cover 95% of use cases.
Every PHP team I've worked with has their own custom Docker image. A Dockerfile that started as 10 lines two years ago is now 150 lines of apt-get install, pecl install, and build flags that nobody fully understands. The image takes 12 minutes to build, weighs 800MB, and includes gcc, make, and autoconf in production because someone needed to compile the Redis extension and never added a multi-stage build.
I've seen this pattern enough times to believe there's a better way. That's why I built the Cbox PHP Base Images: three opinionated tiers that cover the vast majority of PHP workloads without requiring a custom Dockerfile.
The Problem with Official PHP Images
The official php:8.3-fpm image from Docker Hub is intentionally minimal. It includes PHP with a handful of core extensions and nothing else. This is philosophically correct (minimal base, add what you need) but practically painful:
No common extensions. You need
pdo_mysql,redis,gd,intl,zip,bcmath, andopcachefor virtually every Laravel application. None are included.No process manager. PHP-FPM runs as PID 1, which means no signal handling, no zombie reaping, and no multi-process support.
No sensible defaults.
opcache.enableis off.memory_limitis 128MB (too low for many applications, too high for microservices).pm.max_childrenis 5 regardless of available memory.No nginx. You either run nginx in a separate container (more operational complexity) or add it yourself (more Dockerfile complexity).
The result is that every team writes their own Dockerfile layering extensions, configuration, and tooling on top of the official image. This duplicated effort is a waste of engineering time.
Three Tiers
The base images come in three variants. Each builds on the previous one:
Slim
The smallest practical image for PHP workloads. It includes PHP-FPM with the extensions that virtually every application needs:
Core:
opcache,pcntl,pdo_mysql,pdo_pgsql,pdo_sqliteData:
bcmath,intl,mbstring,xml,zipNetwork:
curl,sockets
OPcache is enabled with sane production defaults. memory_limit is set to 256MB. The image is based on Debian 12.
Use Slim when: you are running a microservice, a queue worker, or an API that doesn't need image processing or nginx.
Standard
Slim plus the extensions and tooling needed for full web applications:
Everything in Slim
Image processing:
gd(with freetype, jpeg, webp)Caching:
redis,apcuCbox Init as PID 1 with a default configuration
Nginx with a preconfigured PHP-FPM upstream
Cron daemon for scheduled tasks
The Standard image ships with a default cbox-init.yaml that manages PHP-FPM, nginx, and cron with health checks and dependency ordering. Nginx is configured to serve from /var/www/html/public and proxy PHP requests to FPM over a unix socket.
Use Standard when: you are running a Laravel or Symfony application that needs a complete web server stack in one container.
Full
Standard plus additional extensions for CMS platforms and heavy workloads:
Everything in Standard
Image processing:
imagickConcurrency:
swoole,pcntlNetworking:
sockets
Use Full when: you are running a CMS platform (WordPress, Statamic, Magento) or an application with heavy extension requirements.
The Philosophy
The three tiers follow a single principle: each tier should work out of the box for its target use case without any additional Dockerfile instructions.
For the Standard tier, this means you should be able to write:
FROM cboxdk/php:8.3-standard
COPY . /var/www/html
RUN composer install --no-dev --optimize-autoloader
That's the entire Dockerfile. Cbox Init manages PHP-FPM and nginx. OPcache is configured for production. The health check endpoint is ready. Graceful shutdown works.
If you need to customize, you extend rather than replace. Override the cbox-init.yaml, add an nginx config include, or set environment variables for PHP INI settings:
FROM cboxdk/php:8.3-standard
ENV PHP_MEMORY_LIMIT=512M
ENV PHP_OPCACHE_MAX_FILES=20000
ENV PHP_FPM_MAX_CHILDREN=50
COPY nginx/custom.conf /etc/nginx/conf.d/app.conf
COPY . /var/www/html
Init Integration
The Standard and Full images ship with Cbox Init as their entrypoint. This gives you all the PID 1 benefits without any additional setup:
PHP-FPM starts first with a TCP health check on port 9000
Nginx starts only after FPM is healthy
Cron runs as a managed child process
SIGTERM triggers graceful shutdown: FPM drains connections, nginx stops accepting, cron finishes current jobs
The default configuration is designed for a typical Laravel application. Most teams never need to modify it.
Image Sizes
Keeping images small is a constant battle. All three tiers use aggressive multi-stage builds to minimize image size. Extensions like gd and imagick need build dependencies (development headers, compilers) during installation but not at runtime. The build stage installs, compiles, and copies only the resulting .so files into the final image.
Compare any Cbox tier with the typical custom Dockerfile that includes build tools: 300-400MB compressed, 800MB+ uncompressed. The difference matters for pull times, especially in autoscaling scenarios where new nodes need to pull images quickly.
Versioning
Images are tagged with both the PHP version and a release date: cboxdk/php:8.3-standard-20241120. The 8.3-standard tag always points to the latest release, while dated tags are immutable. This lets you pin to a specific image for reproducibility while still being able to pull updates when you choose.
I publish images for PHP 8.2, 8.3, and 8.4 across all three tiers. Each release runs through a test suite that verifies extension loading, Init process management, nginx proxying, and OPcache configuration.
Full documentation, configuration options, and available extensions for each tier are in the PHP Base Images docs.
// Sylvester Damgaard