Custom Extensions
Custom PHP Extensions
Cbox includes 40+ extensions, but you may need others. This guide covers adding extensions via PECL, compiling from source, and version management.
Quick Reference
# PECL extension (most common)
RUN pecl install extension-name && docker-php-ext-enable extension-name
# Core extension (bundled with PHP)
RUN docker-php-ext-install extension-name
# Extension with dependencies
RUN apt-get update && apt-get install -y dependency-package \
&& pecl install extension-name \
&& docker-php-ext-enable extension-name
PECL Extensions
Basic Installation
FROM ghcr.io/cboxdk/php-baseimages/php-fpm-nginx:8.5-bookworm-v1
# Install build dependencies, extension, enable, cleanup
RUN apt-get update && apt-get install -y $PHPIZE_DEPS \
&& pecl install swoole \
&& docker-php-ext-enable swoole \
&& apt-get purge -y --auto-remove $PHPIZE_DEPS \
&& rm -rf /var/lib/apt/lists/*
With Version Pinning
FROM ghcr.io/cboxdk/php-baseimages/php-fpm-nginx:8.5-bookworm-v1
# Pin specific version for reproducibility
RUN apt-get update && apt-get install -y $PHPIZE_DEPS \
&& pecl install swoole-5.1.1 \
&& docker-php-ext-enable swoole \
&& apt-get purge -y --auto-remove $PHPIZE_DEPS \
&& rm -rf /var/lib/apt/lists/*
Common PECL Extensions
Swoole (Async/Coroutines)
RUN apt-get update && apt-get install -y $PHPIZE_DEPS libssl-dev libcurl4-openssl-dev \
&& pecl install swoole \
&& docker-php-ext-enable swoole \
&& apt-get purge -y --auto-remove $PHPIZE_DEPS \
&& rm -rf /var/lib/apt/lists/*
gRPC + Protobuf
FROM ghcr.io/cboxdk/php-baseimages/php-fpm-nginx:8.5-bookworm-v1
RUN apt-get update && apt-get install -y \
libgrpc-dev libprotobuf-dev protobuf-compiler \
&& pecl install grpc protobuf \
&& docker-php-ext-enable grpc protobuf \
&& rm -rf /var/lib/apt/lists/*
Memcached
RUN apt-get update && apt-get install -y $PHPIZE_DEPS libmemcached-dev zlib1g-dev \
&& pecl install memcached \
&& docker-php-ext-enable memcached \
&& apt-get purge -y --auto-remove $PHPIZE_DEPS \
&& rm -rf /var/lib/apt/lists/*
SSH2
RUN apt-get update && apt-get install -y $PHPIZE_DEPS libssh2-1-dev \
&& pecl install ssh2 \
&& docker-php-ext-enable ssh2 \
&& apt-get purge -y --auto-remove $PHPIZE_DEPS \
&& rm -rf /var/lib/apt/lists/*
AMQP (RabbitMQ)
RUN apt-get update && apt-get install -y $PHPIZE_DEPS librabbitmq-dev \
&& pecl install amqp \
&& docker-php-ext-enable amqp \
&& apt-get purge -y --auto-remove $PHPIZE_DEPS \
&& rm -rf /var/lib/apt/lists/*
Event (libevent)
RUN apt-get update && apt-get install -y $PHPIZE_DEPS libevent-dev libssl-dev \
&& pecl install event \
&& docker-php-ext-enable event \
&& apt-get purge -y --auto-remove $PHPIZE_DEPS \
&& rm -rf /var/lib/apt/lists/*
Core Extensions
Extensions bundled with PHP source (use docker-php-ext-install):
# Single extension
RUN docker-php-ext-install sockets
# Multiple extensions
RUN docker-php-ext-install pdo_mysql mysqli
# Extension requiring configuration
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install gd
Available Core Extensions
Already included in Cbox:
- bcmath, calendar, exif, gettext, intl, opcache, pcntl, pdo_mysql, pdo_pgsql, sockets, zip
Can be added if needed:
- dba, enchant, ffi, ftp, oci8, odbc, pdo_odbc, pspell, shmop, snmp, sysvmsg, sysvsem, sysvshm, tidy
Compiling from Source
For extensions not on PECL or needing custom options:
Example: OpenSwoole with Custom Options
FROM ghcr.io/cboxdk/php-baseimages/php-fpm-nginx:8.5-bookworm-v1
RUN apt-get update && apt-get install -y $PHPIZE_DEPS git libssl-dev libcurl4-openssl-dev \
&& git clone https://github.com/openswoole/swoole-src.git \
&& cd swoole-src \
&& phpize \
&& ./configure \
--enable-openssl \
--enable-http2 \
--enable-mysqlnd \
--enable-sockets \
&& make -j$(nproc) \
&& make install \
&& docker-php-ext-enable openswoole \
&& cd .. && rm -rf swoole-src \
&& apt-get purge -y --auto-remove $PHPIZE_DEPS git \
&& rm -rf /var/lib/apt/lists/*
Example: Xhprof (Facebook's Profiler)
RUN apt-get update && apt-get install -y $PHPIZE_DEPS git \
&& git clone https://github.com/longxinH/xhprof.git \
&& cd xhprof/extension \
&& phpize \
&& ./configure \
&& make \
&& make install \
&& docker-php-ext-enable xhprof \
&& cd ../.. && rm -rf xhprof \
&& apt-get purge -y --auto-remove $PHPIZE_DEPS git \
&& rm -rf /var/lib/apt/lists/*
Extension Configuration
php.ini Settings
# Create custom ini file
RUN echo "extension_setting=value" > /usr/local/etc/php/conf.d/99-extension.ini
Example: Swoole Configuration
RUN echo "swoole.use_shortname=Off" > /usr/local/etc/php/conf.d/99-swoole.ini
Example: OPcache for Production
RUN echo "opcache.enable=1" > /usr/local/etc/php/conf.d/99-opcache.ini \
&& echo "opcache.memory_consumption=256" >> /usr/local/etc/php/conf.d/99-opcache.ini \
&& echo "opcache.validate_timestamps=0" >> /usr/local/etc/php/conf.d/99-opcache.ini
Using FFI
FFI is compiled into the standard, chromium and dev tiers (not slim) and
shipped with ffi.enable=preload. That is upstream's own default and the right
one: FFI loads arbitrary shared libraries and calls into them, so an image that
enabled it for request code would hand every request in every application a way
out of PHP.
Under FPM that means request code cannot call FFI::cdef(). Preloaded code
can, and PHP_OPCACHE_PRELOAD is how you get a preload script.
The pattern that works
Preloading persists declarations, not runtime state - a static property
assigned during preload is empty again by the time a request runs. Use
FFI::load() on a header with an FFI_SCOPE, then look the scope up per
request:
/* /opt/ffi/libc.h */
#define FFI_SCOPE "LIBC"
#define FFI_LIB "libc.so.6"
long time(long *t);
int getpid(void);
<?php // /opt/ffi/preload.php - runs once at startup
FFI::load(__DIR__ . '/libc.h');
<?php // request code - no cdef() anywhere
$libc = FFI::scope('LIBC');
echo $libc->getpid();
services:
app:
image: ghcr.io/cboxdk/php-baseimages/php-fpm-nginx:8.5-bookworm
environment:
PHP_OPCACHE_PRELOAD: /opt/ffi/preload.php
volumes:
- ./ffi:/opt/ffi:ro
Verified on php-cli:8.5-bookworm: without the preload, FFI::scope('LIBC')
fails with Failed loading scope 'LIBC'; with it, the call returns.
Two things that will bite you
A static property set in the preload script is not there at request time.
FFI::cdef() assigned to Libc::$ffi during preload looks like it should work
and fails with "Typed static property must not be accessed before
initialization". Scopes survive; values do not.
The CLI is not gated. ffi.enable=preload still allows FFI::cdef() in the
CLI SAPI - that is PHP's behaviour, not ours. The restriction protects web
requests, so a CLI script that works proves nothing about what FPM will allow.
A preload path that does not exist fails the container at boot with a
message naming the path, rather than PHP's startup fatal that names only the ini
setting. That is deliberate - see
PHP_OPCACHE_PRELOAD.
Version Pinning Best Practices
Pin Versions in Production
# Good: Pin specific versions
RUN pecl install redis-6.0.2 \
&& pecl install msgpack-3.0.0
# Bad: Use latest (unpredictable)
RUN pecl install redis msgpack
Use Build Arguments
ARG REDIS_VERSION=6.0.2
ARG SWOOLE_VERSION=5.1.1
RUN pecl install redis-${REDIS_VERSION} \
&& pecl install swoole-${SWOOLE_VERSION}
Override at Build Time
docker build \
--build-arg REDIS_VERSION=6.1.0 \
--build-arg SWOOLE_VERSION=5.2.0 \
-t myapp .
Testing Extensions
Verify Installation
# Check extension loaded
docker run --rm myapp php -m | grep swoole
# Check extension version
docker run --rm myapp php -r "echo phpversion('swoole');"
# Check extension config
docker run --rm myapp php -i | grep swoole
Automated Testing
# Add health check for extension
HEALTHCHECK --interval=30s --timeout=3s \
CMD php -r "if (!extension_loaded('swoole')) exit(1);" || exit 1
Troubleshooting
Extension Won't Compile
# Check build logs
docker build --progress=plain -t test .
# Interactive debugging
docker run --rm -it ghcr.io/cboxdk/php-baseimages/php-fpm-nginx:8.5-bookworm-v1 sh
apt-get update && apt-get install -y build-essential
pecl install extension-name
Missing Dependencies
# Search for Debian packages
apt-cache search libname
# Or use apt-file to find which package provides a file
apt-file search library-name
Common Dependency Mappings (Debian)
| Extension | Debian Package |
|---|---|
| gd | libfreetype6-dev libpng-dev |
| imagick | libmagickwand-dev |
| memcached | libmemcached-dev |
| mongodb | libssl-dev |
| ssh2 | libssh2-1-dev |
| amqp | librabbitmq-dev |
| event | libevent-dev |
| curl | libcurl4-openssl-dev |
| zip | libzip-dev |
Extension Conflicts
# Load order matters for some extensions
# msgpack should load before redis (for serialization)
RUN pecl install msgpack \
&& docker-php-ext-enable msgpack \
&& pecl install --configureoptions 'enable-redis-msgpack="yes"' redis \
&& docker-php-ext-enable redis
Complete Examples
Laravel with Swoole + Redis
FROM ghcr.io/cboxdk/php-baseimages/php-fpm-nginx:8.5-bookworm-v1
# Build dependencies
RUN apt-get update && apt-get install -y $PHPIZE_DEPS libssl-dev libcurl4-openssl-dev \
&& pecl install swoole-5.1.1 \
&& docker-php-ext-enable swoole \
&& echo "swoole.use_shortname=Off" > /usr/local/etc/php/conf.d/99-swoole.ini \
&& apt-get purge -y --auto-remove $PHPIZE_DEPS \
&& rm -rf /var/lib/apt/lists/*
COPY . /var/www/html
RUN composer install --no-dev --optimize-autoloader
API with gRPC + Protobuf
FROM ghcr.io/cboxdk/php-baseimages/php-fpm-nginx:8.5-bookworm-v1
RUN apt-get update && apt-get install -y \
libgrpc-dev libprotobuf-dev protobuf-compiler \
&& pecl install grpc-1.60.0 protobuf-3.25.1 \
&& docker-php-ext-enable grpc protobuf \
&& rm -rf /var/lib/apt/lists/*
COPY . /var/www/html
RUN composer install --no-dev --optimize-autoloader
Next Steps
- Extending Images - Complete customization guide
- Custom Initialization - Startup scripts
- Performance Tuning - Optimize your extensions