Slide 1

Slide 1 text

Containerization Tips and Tricks For PHP applications Photo by Pat Whelen

Slide 2

Slide 2 text

Kévin Dunglas ➔ Co-founder of Les-Tilleuls.coop ➔ Symfony Core Team ➔ Creator of API Platform, FrankenPHP and Mercure @dunglas

Slide 3

Slide 3 text

Web and Cloud Experts ➔ Development: PHP, JS, Go, Rust... ➔ DevOps and SRE ➔ Consultancy and maintenance ➔ Agile management, UX and UI design… ➔ [email protected] 💌

Slide 4

Slide 4 text

Containers… …and Docker

Slide 5

Slide 5 text

➔ 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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Getting Started

Slide 8

Slide 8 text

A simple CLI script

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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)

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

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 😕

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

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/*

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Composer Install, The Docker Way # … # Copy all project files COPY . ./ # Install dependencies RUN composer install --no-cache --no-dev --classmap-authoritative

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

Understanding Docker Layers

Slide 25

Slide 25 text

A Naive Ordering # … # Copy all project files COPY . ./ # Install dependencies RUN composer install --no-cache --no-dev --classmap-authoritative

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

# 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

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

Allow list # ... COPY --link public/ src/ config/ ./

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Web Servers

Slide 36

Slide 36 text

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)

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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:

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Kubernetes

Slide 48

Slide 48 text

➔ 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

Slide 49

Slide 49 text

➔ The Kubernetes package manager ➔ Like apt but for Kubernetes ➔ Charts: packages for Kubernetes ➔ Graphical tools (Lens IDE, Ahoy) ➔ Templates for Kubernetes YAML manifests Helm

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

➔ 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

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

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!

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

Thank you! Want to improve your Docker or Kubernetes worklows? Contact us! les-tilleuls.coop [email protected] @coopTilleuls