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)
php:cli (Debian)
php:cli (debian 11.6)
=====================
Total: 368 (UNKNOWN: 6, LOW: 267, MEDIUM: 51, HIGH: 43, CRITICAL: 1)
php:alpine
php:alpine (alpine 3.16.2)
==========================
Total: 32 (UNKNOWN: 0, LOW: 1, MEDIUM: 11, HIGH: 16, CRITICAL: 4)
cgr.dev/chainguard/php (Wolfi)
cgr.dev/chainguard/php (wolfi 20230201)
=======================================
Total: 18 (UNKNOWN: 0, LOW: 0, MEDIUM: 4, HIGH: 12, CRITICAL: 2)
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
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);
Save the file. Then, set its permissions as an executable with:
chmod +x randomizer
Now you can test that it works as expected:
./randomizer get
And you'll get a random name combination such as:
ludicrous-turtle
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"]
After saving the file, you can finally build your application runtime image with:
docker build . -t wolfi-php-demo
Finally, run the image with:
docker run --rm wolfi-php-demo
And you should get a similar result as the previous time you run the script.
ravishing-seahorse
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)
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)
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 :)