DEV Community

Cover image for Creating Safer Containerized PHP Runtimes with Wolfi
Erika Heidi
Erika Heidi

Posted on • Updated on

Creating Safer Containerized PHP Runtimes with Wolfi

Introduction

Wolfi is a minimal open source Linux distribution created specifically for cloud workloads, with emphasis on software supply chain security. Using apk (just as Alpine), Wolfi has its own package repository and build ecosystem providing nightly builds and quick patch turnaround times. The main difference here is that Wolfi is glib-c based, while Alpine uses Musl.

The combination of a smaller attack surface and up-to-date, patched packages in Wolfi results in less (always aiming for ZERO) CVEs. This can be demonstrated in the results obtained from Trivy when scanning the most popular PHP images on Docker Hub (with data from March 2, 2023) and comparing them with the Wolfi-based PHP image maintained by Chainguard:

php

php (debian 11.4)
=================
Total: 483 (UNKNOWN: 7, LOW: 272, MEDIUM: 103, HIGH: 94, CRITICAL: 7)
Enter fullscreen mode Exit fullscreen mode

php:cli (Debian)

php:cli (debian 11.6)
=====================
Total: 368 (UNKNOWN: 6, LOW: 267, MEDIUM: 51, HIGH: 43, CRITICAL: 1)
Enter fullscreen mode Exit fullscreen mode

php:alpine

php:alpine (alpine 3.16.2)
==========================
Total: 32 (UNKNOWN: 0, LOW: 1, MEDIUM: 11, HIGH: 16, CRITICAL: 4)
Enter fullscreen mode Exit fullscreen mode

cgr.dev/chainguard/php (Wolfi)

cgr.dev/chainguard/php (wolfi 20230201)
=======================================
Total: 18 (UNKNOWN: 0, LOW: 0, MEDIUM: 4, HIGH: 12, CRITICAL: 2)
Enter fullscreen mode Exit fullscreen mode

Results from Trivy on March 2, 2023

Although the Wolfi PHP image still has some CVEs, it's not nearly the same number as the popular Debian-based php:cli image. Because patches are quickly applied and images rebuilt, it is likely that these few CVEs will be back to zero in just a few days.

In this article, we'll see how to leverage Wolfi to create safer PHP application environments based on containers. To demonstrate Wolfi usage in a Dockerfile workflow (using a Dockerfile to build your image), we'll create an image based on the wolfi-base image maintained by Chainguard. The goal is to have a final runtime image able to execute a PHP command-line script. By definition, this image won't be completely distroless, because it will require APK to be present in order to install system dependencies described in the Dockerfile. For building pure distroless images, you should have a look at apko.

Requirements:

You'll need PHP 8.1+ and Composer to create the demo app, and Docker to build and run the image.

Creating a Demo App

Let's create a simple demo app using Minicli, to demonstrate dependency management with Composer. The app will output a random combination of adjective + noun.

First, bootstrap a new Minicli app with:

mkdir wolfi-php-demo && cd $_
composer require minicli/minicli
Enter fullscreen mode Exit fullscreen mode

Next, create the executable PHP script that will be the entry point for this image. You can call it randomizer, without the .php extension.

This script follows the Minicli example for simple apps. It defines a single command "get" that will output a random name combination based on two arrays.

#!/usr/bin/php
<?php

require __DIR__ . '/vendor/autoload.php';

use Minicli\App;

$app = new App();

$app->registerCommand('get', function () use ($app) {

    $animals = [ 'turtle', 'seagull', 'octopus', 'shark', 'whale', 'dolphin', 'walrus', 'penguin', 'seahorse'];
    $adjectives = [ 'ludicrous', 'mischievous', 'graceful', 'fortuitous', 'charming', 'ravishing', 'gregarious'];

    $app->getPrinter()->info($adjectives[array_rand($adjectives)] . '-' . $animals[array_rand($animals)]);
});

$app->runCommand($argv);


Enter fullscreen mode Exit fullscreen mode

Save the file. Then, set its permissions as an executable with:

chmod +x randomizer
Enter fullscreen mode Exit fullscreen mode

Now you can test that it works as expected:

./randomizer get
Enter fullscreen mode Exit fullscreen mode

And you'll get a random name combination such as:

ludicrous-turtle
Enter fullscreen mode Exit fullscreen mode

Creating the Dockerfile

Now we'll create the Dockerfile to run the application. This Dockerfile will set up a new WORKDIR, copy relevant files, and install dependencies with Composer. It will also define the entry point and command that will be executed when we run this image with docker run.

FROM cgr.dev/chainguard/wolfi-base

WORKDIR /app
COPY composer.json composer.lock randomizer /app/
RUN apk add php composer && \
    composer install --no-progress --no-dev --prefer-dist

ENTRYPOINT ["php", "/app/randomizer"]
CMD ["get"]

Enter fullscreen mode Exit fullscreen mode

After saving the file, you can finally build your application runtime image with:

docker build . -t wolfi-php-demo 
Enter fullscreen mode Exit fullscreen mode

Finally, run the image with:

docker run --rm wolfi-php-demo
Enter fullscreen mode Exit fullscreen mode

And you should get a similar result as the previous time you run the script.

ravishing-seahorse

Enter fullscreen mode Exit fullscreen mode

Using apko for Improved CVE Scanning

The caveat for scanning images generated with the previous method is that CVE scanners will most likely only evaluate your base image, so if you run trivy on your wolfi-php-demo now you should only get results for CVEs affecting wolfi-base:

wolfi-php-demo (wolfi 20220914)

Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 1)
Enter fullscreen mode Exit fullscreen mode

This can be misleading since the final image adds new packages (php and composer), each with its own dependency tree. To tighten your setup even more, you can build a base image with apko and include all system dependencies there (php, composer). This will assure that the base image you use in your Dockerfile has all system dependencies accounted for in an SBOM (Software Bill of Materials), which can be scanned or monitored.

Check the getting started with apko tutorial if you want to learn more about apko. Check also the public PHP Chainguard Images which are all based on Wolfi and offer a few different variants for general use cases.

Top comments (1)

Collapse
 
rafavschneider profile image
Rafael Vinícius Schneider

I got some errors using this Dockerfile (29 apr. 2024), about some PHP Extensions missing. So I created one Dockerfile using cgr.dev/chainguard/php:latest-dev as base image. Open to contributions. Enjoy :)

FROM cgr.dev/chainguard/php:latest-dev

WORKDIR /app
COPY composer.json composer.lock randomizer /app/
RUN composer install --no-progress --no-dev --prefer-dist

ENTRYPOINT ["php", "/app/randomizer"]
CMD ["get"]
Enter fullscreen mode Exit fullscreen mode
# Build
docker build . -t wolfi-php-demo

# Run
docker run --rm wolfi-php-demo
Enter fullscreen mode Exit fullscreen mode