Getting Started

Migrating from official PHP images

You already have a Dockerfile that works. It installs extensions, copies in Composer, and tweaks php.ini. Most of those lines go away when you switch. We compile PHP the same way the official images do, and your app code doesn't change.

Most of your Dockerfile goes away

Here's a typical Laravel image built on the official Apache image:

Dockerfile (before)
FROM php:8.5-apache

RUN apt-get update && apt-get install -y libzip-dev \
    && docker-php-ext-install pcntl pdo_mysql zip \
    && pecl install redis && docker-php-ext-enable redis \
    && a2enmod rewrite

COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
COPY php.ini /usr/local/etc/php/conf.d/app.ini
COPY . /var/www/html

Here's the same app on ours:

Dockerfile (after)
FROM serversideup/php:8.5-fpm-apache

COPY --chown=www-data:www-data . /var/www/html

pcntl, pdo_mysql, redis, and zip are already installed. So are Composer and Apache's rewrite module. The php.ini settings move to environment variables in your Compose file. The container runs as www-data instead of root.

Switch in eight steps

Work through these in development first.

Change the image

Official imageReplace with
php:8.5-cliserversideup/php:8.5-cli
php:8.5-fpmserversideup/php:8.5-fpm
php:8.5-apacheserversideup/php:8.5-fpm-apache
php:8.5-fpm plus an NGINX containerserversideup/php:8.5-fpm-nginx

Not sure which fits? Choosing an image compares all five.

Map the new ports

Web server variations listen on 8080 and 8443 instead of 80 and 443. fpm still uses 9000.

compose.yml
services:
  php:
    image: serversideup/php:8.5-fpm-apache
    ports:
      - 80:8080
      - 443:8443

Update anything else that connects to the container directly, such as a load balancer's health check.

Serve from public/, or change the web root

fpm-nginx, fpm-apache, and frankenphp serve /var/www/html/public, not /var/www/html. Laravel and Symfony already use public/.

For an app that serves from the project root, such as WordPress, set NGINX_WEBROOT, APACHE_DOCUMENT_ROOT, or CADDY_SERVER_ROOT to /var/www/html.

Give www-data your files

Containers run as www-data, not root. The user ID is 33 on Debian and 82 on Alpine. That user must own anything your app writes to, such as Laravel's storage/:

Dockerfile
FROM serversideup/php:8.5-fpm-nginx
COPY --chown=www-data:www-data . /var/www/html

For mounted volumes, see Understanding file permissions.

Install extras as root

Package and extension installs need root. Switch to root for those steps, then back to www-data:

Dockerfile
FROM serversideup/php:8.5-fpm-nginx

USER root
RUN install-php-extensions intl bcmath
USER www-data

install-php-extensions installs each extension's system libraries for you. docker-php-ext-install still works too. See Installing additional PHP extensions.

Swap php.ini for environment variables

Most settings you'd copy in a php.ini have an environment variable, such as PHP_MEMORY_LIMIT. Set them in your Compose file and drop the file.

If you keep an ini file, name it to load after ours, such as zzz-custom-php.ini. OPcache is off by default. Set PHP_OPCACHE_ENABLE=1 in production. See Changing php.ini settings.

Move php_value out of .htaccess (Apache only)

The official Apache image runs PHP as mod_php. Our fpm-apache runs PHP-FPM. Apache returns a 500 error for php_value and php_flag lines in .htaccess. Move those settings to environment variables or an ini file.

fpm-apache also blocks xmlrpc.php. WordPress features that use it, such as Jetpack and the mobile app, stop working. See Using WordPress with Docker.

Keep our entrypoint and default command

Remove ENTRYPOINT and CMD lines you carried over, such as CMD ["apache2-foreground"]. Our entrypoint runs the start-up scripts, then the default command. On fpm-nginx and fpm-apache that's /init, which starts the web server and PHP-FPM together. A different command skips the web server.

Our PHP-FPM pool replaces the stock www.conf and zz-docker.conf. Set pool options with PHP_FPM_* variables instead of copying those files in.

Test it, then ship it

Run your app and its tests against the new image in development or staging. Watch the container logs for permission errors.

Once it's running, two features are worth turning on:

Default configurations lists everything else the image does for you.