Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Containerization Tips and Tricks for PHP apps

Containerization Tips and Tricks for PHP apps

API Platform was one of the first PHP frameworks to provide native support for Docker, Docker Compose, Kubernetes, and, more recently, Skaffold.

The API Platform skeleton also served as the basis for Symfony Docker, the most popular solution for containerizing Symfony projects.

These years of developing skeletons compatible with dev environments, continuous integration chains, and production environments, as well as running containerized PHP applications of all sizes in prod, have enabled us to accumulate a wealth of experience in containerizing PHP applications.

In this conference, I share several tips and tricks I've learned over the last ten years!

Kévin Dunglas

May 28, 2024

More Decks by Kévin Dunglas

Other Decks in Programming


  1. Kévin Dunglas ➔ Co-founder of Les-Tilleuls.coop ➔ Symfony Core Team

    ➔ Creator of API Platform, FrankenPHP and Mercure @dunglas
  2. Web and Cloud Experts ➔ Development: PHP, JS, Go, Rust...

    ➔ DevOps and SRE ➔ Consultancy and maintenance ➔ Agile management, UX and UI design… ➔ [email protected] 💌
  3. ➔ Docker, an OS-level virtualization platform • Build: Dockerfile •

    Share: Registry (hub) • Run: Engine ➔ Software is packaged in containers • Embedded binaries, libs, config files... • Run in isolation • Share a single OS kernel: ⬆performance Docker and Containers
  4. Why should you use Docker? ➔ Easy onboarding • docker

    compose up • No more cumbersome and outdated README files ➔ Versioned, same software and configs everywhere: git pull • locally, for the entire team • CI • staging • prod ➔ Standardized, industry standard ➔ Great tooling, large ecosystem (Kubernetes, Swarm, GitHub...) ➔ (Almost) cross platform: Linux, Windows, Mac @dunglas
  5. Minimal Dockerfile FROM php COPY . /usr/src/my-app CMD [ "php",

    "/usr/src/my-app/my-script.php" ] # docker build -t my-php-app . # docker run my-php-app
  6. Debian vs Alpine ➔ Official PHP images use Debian •

    Typical GNU C library (glibc) ➔ Variants using Alpine Linux are also provided, and popular • Smaller image size 👍 • musl libc, a simple and lightweight C standard library
  7. Issues with PHP and musl ➔ musl is not officially

    tested/supported by the PHP project (WIP) ➔ Known crashes and issues ➔ Significantly slower (WIP) ➔ Some features aren’t supported or behave differently (ex: glob() with GLOB_BRACE)
  8. Tip #1: Hadolint ➔ Dockerfile linter ➔ Also checks shell

    scripts ➔ Detailed description of each rule ➔ Available as: ◆ CLI tool ◆ VSCode plugin ◆ GitHub Action ◆ Part of Super Linter ◆ Not in PHPStorm yet 😕
  9. Installing PHP Extensions 😱 FROM php:8.3-cli RUN apt-get update \

    && apt-get install -y --no-install-recommends \ libfreetype-dev libjpeg62-turbo-dev libpng-dev \ && docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-install -j"$(nproc)" gd \ && apt-get purge -y libfreetype-dev libjpeg62-turbo-dev libpng-dev \ && apt-get autoremove -y \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*
  10. The Easy Way: PHP Extension Installer FROM php:8.3-cli RUN curl

    -sSL https://github.com/mlocati/docker-php-extension-installer/releases/latest/downloa d/install-php-extensions -o - | sh -s \ gd
  11. Michele Locati’s PHP Extension Installer ➔ Installs system and build

    dependencies ➔ Compiles the PHP extensions ➔ Configures properly the OS ➔ Removes the no-more needed packages ➔ Can install Composer
  12. Our Base Dockerfile FROM php:8.3-cli # Change the working directory

    WORKDIR /usr/src/myapp # Install Composer using PHP Extension Installer RUN curl -sSL https://github.com/mlocati/docker-php-extension-installer/releases/latest/downloa d/install-php-extensions -o - | sh -s \ @composer
  13. Composer Install, The Docker Way # … # Copy all

    project files COPY . ./ # Install dependencies RUN composer install --no-cache --no-dev --classmap-authoritative
  14. Composer Flags Only in the Docker context: ➔ --no-cache: disables

    Composer’s own cache (reduce image size) Not Docker-specific: ➔ --no-dev: skips installing "require-dev" dependencies ➔ --classmap-authoritative: autoloads classes from the classmap only and optimizes the autoloader
  15. A Naive Ordering # … # Copy all project files

    COPY . ./ # Install dependencies RUN composer install --no-cache --no-dev --classmap-authoritative
  16. Problem With This Naive Approach ➔ When a file is

    modified, all the following steps are executed again (no cache) ➔ Every time a file is modified, layers after the COPY step are trashed • Composer's dependencies are reinstalled • This is slow and consumes resources for nothing
  17. A Smarter Dockerfile # ... # Install dependencies COPY composer.*

    ./ RUN composer install --no-cache --no-dev --no-autoloader --no-scripts # Copy project files and generate the autoloader COPY . ./ RUN composer dump-autoload --no-dev --classmap-authoritative
  18. COPY --link / ADD --link ➔ Copies files in an

    independent layer ➔ This independent layer is then merged ➔ Prevents invalidation when a previous layer changes ➔ Relies on BuildKit ➔ Super useful for PHP as source files are always independent from previous layers
  19. # syntax=docker/dockerfile:1 # ... COPY --link composer.* ./ RUN composer

    install --no-cache --no-dev --no-autoloader --no-scripts COPY --link . ./ RUN composer dump-autoload --no-dev --classmap-authoritative COPY --link
  20. Only Copy What You Need ➔ Faster and cheaper builds

    ➔ Smaller images ➔ Smaller attack surface ➔ You likely want to exclude: ◆ Development tools (already done) ◆ Tests ◆ Docs ◆ CI/DevOps config
  21. Ignore list # .dockerignore **/*.log **/*.md **/*.php~ **/*.dist.php **/*.dist **/*.cache

    **/._* **/.dockerignore **/.DS_Store **/.git/ **/.gitattributes **/.gitignore **/.gitmodules **/compose.*.yaml **/compose.yaml **/Dockerfile **/Thumbs.db .github/ docs/ public/bundles/ tests/ var/ vendor/ .editorconfig .env
  22. Containers: Which SAPI? Server Application Programming Interface: interface between the

    PHP interpreter and web servers ➔ FPM (FastCGI Process Manager) • Most popular • Requires an external web server ◦ 2 images, 2+ containers ➔ FrankenPHP 🧟 • Designed for containers & performance ➔ Alternatives (1 image) • NGINX Unit (not the NGINX you’re used to) • PHP module for Apache (deprecated)
  23. FrankenPHP ➔ Only one Docker image ➔ Based on official

    PHP images (with PHP Extension Installer) ➔ Worker mode (3x faster than FPM) ➔ 103 Early Hints (can reduce websites latency by 30%) ➔ Brotli/Zstandard/GZip compression ➔ Built-in Mercure hub (real-time) ➔ Automatic HTTPS, HTTP/3 and HTTP/2 ➔ Prometheus/OpenMetrics/OpenTelemetry metrics ➔ All Caddy features
  24. FrankenPHP FROM dunglas/frankenphp COPY . /app/public # docker build -t

    my-php-app . # docker run \ # -p 80:80 -p 443:443 -p 443:443/udp \ # my-php-app
  25. The web server and FPM must talk together ➔ Two

    options: ◆ TCP socket ◆ UNIX socket ➔ UNIX sockets are faster ➔ To use UNIX sockets: ◆ A directory must be shared between the web server container and the FPM container ◆ Both containers must be on the same physical (and virtual) host
  26. Share UNIX Sockets Through Volumes services: # compose.yaml php: #

    ... volumes: - php_socket:/var/run/php server: # ... depends_on: - php volumes: - php_socket:/var/run/php volumes: php_socket:
  27. Use Docker Bake to Cache Layers # .github/workflows/build.yaml jobs: build:

    steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/bake-action@v4 with: push: true set: | *.cache-from=type=gha *.cache-to=type=gha,mode=max
  28. Use Multistage Images To Add Dev Tools FROM dunglas/frankenphp:1-php8.3 AS

    frankenphp_base # Steps of your prod image # … # Dev image FROM frankenphp_base AS frankenphp_dev ENV APP_ENV=dev RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" RUN install-php-extensions xdebug
  29. ➔ Free and Open Source container orchestration system ➔ Unlike

    Docker Compose: native cluster support ➔ Allows to scale deployments (with autoscaling support) ➔ Industry standard ➔ Managed clusters offered by AWS, GCP, Azure, DO, OVH, Scaleway… ➔ Battery included, but complex Kubernetes
  30. ➔ The Kubernetes package manager ➔ Like apt but for

    Kubernetes ➔ Charts: packages for Kubernetes ➔ Graphical tools (Lens IDE, Ahoy) ➔ Templates for Kubernetes YAML manifests Helm
  31. ➔ Containers and Kubernetes development ➔ Write code, push, deploy

    ➔ Run your app locally using Minikube as in prod ➔ Reuse your existing Helm charts and Kubernetes manifests ➔ Natively supported by API Platform ➔ Deploy in seconds Dev With Skaffold Instead of Compose
  32. Symfony Docker Most of these tips have been extracted from

    “Symfony Docker” (co-maintained by Maxime Helias and I) and the API Platform distribution. Take a look at these projects for more high-quality examples!