Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions .github/workflows/test-httpserver-perl5.42-base-distroless.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: Test Perl5.42 HTTP Server Distroless

on:
workflow_dispatch:
push:
paths:
- "examples/httpserver-perl5.42-base-distroless/**"
- ".github/workflows/test-httpserver-perl5.42-base-distroless.yml"
pull_request:
paths:
- "examples/httpserver-perl5.42-base-distroless/**"
- ".github/workflows/test-httpserver-perl5.42-base-distroless.yml"

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build-benchmark-test:
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- name: Checkout repository
# v7.0.1
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1

- name: Install KraftKit (Version Pinned & Verified)
env:
KRAFT_VERSION: "0.12.15"
run: |
set -euo pipefail

wget -q "https://github.com/unikraft/kraftkit/releases/download/v${KRAFT_VERSION}/kraft_${KRAFT_VERSION}_linux_amd64.tar.gz"
wget -q "https://github.com/unikraft/kraftkit/releases/download/v${KRAFT_VERSION}/kraftkit_${KRAFT_VERSION}_checksums.txt"

grep " kraft_${KRAFT_VERSION}_linux_amd64.tar.gz\$" "kraftkit_${KRAFT_VERSION}_checksums.txt" | sha256sum -c -

tar -xzf "kraft_${KRAFT_VERSION}_linux_amd64.tar.gz"
chmod +x kraft
sudo install -o root -g root -m 0755 kraft /usr/local/bin/kraft
kraft version

- name: Build Unikernel
working-directory: examples/httpserver-perl5.42-base-distroless
run: kraft build --plat qemu --arch x86_64 .

- name: Measure RootFS Size
working-directory: examples/httpserver-perl5.42-base-distroless
run: |
set -euo pipefail

mapfile -t cpio_files < <(find .unikraft/build/ -type f -name "*.cpio")
if [ "${#cpio_files[@]}" -eq 0 ]; then
echo "No .cpio artifacts found under .unikraft/build/" >&2
exit 1
fi

du -h "${cpio_files[@]}"

- name: Build Distroless rootfs for Trivy
working-directory: examples/httpserver-perl5.42-base-distroless
run: docker build --pull -t httpserver-perl5.42-base-distroless:ci .

- name: Run Trivy Vulnerability Scanner
# v0.36.0
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25
with:
scan-type: "image"
scan-ref: "httpserver-perl5.42-base-distroless:ci"
format: "table"
exit-code: "1"
severity: "CRITICAL,HIGH"
trivyignores: "examples/httpserver-perl5.42-base-distroless/.trivyignore"

- name: Install QEMU and Grant Permissions
run: |
set -euo pipefail

sudo apt-get update
sudo apt-get install -y qemu-system-x86

if [ -e /dev/kvm ]; then
sudo chown "$(id -u):$(id -g)" /dev/kvm
sudo chmod 660 /dev/kvm
fi

- name: Run Unikernel in Background
working-directory: examples/httpserver-perl5.42-base-distroless
run: kraft run -d --plat qemu --arch x86_64 -M 512M -p 8080:8080 --name httpserver-test .

- name: Test Network Connectivity
run: |
set -euo pipefail

for i in $(seq 1 15); do
if curl -s --max-time 2 http://127.0.0.1:8080/ | grep -q "Bye, World!"; then
echo "Server responded correctly."
exit 0
fi
sleep 1
done

echo "Server did not respond as expected within 15s" >&2
exit 1

- name: Dump Unikernel Logs on Failure
if: failure()
working-directory: examples/httpserver-perl5.42-base-distroless
run: kraft ps -a; kraft logs httpserver-test || true

- name: Cleanup
if: always()
run: kraft rm --force httpserver-test || true
1 change: 1 addition & 0 deletions examples/httpserver-perl5.42-base-distroless/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/.unikraft/
1 change: 1 addition & 0 deletions examples/httpserver-perl5.42-base-distroless/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/.unikraft/
4 changes: 4 additions & 0 deletions examples/httpserver-perl5.42-base-distroless/.trivyignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Pending upstream Debian fix for libssl3 DoS
# This example isn't concerned with OpenSSL QUIC servers
CVE-2026-14456 exp:2026-11-30

14 changes: 14 additions & 0 deletions examples/httpserver-perl5.42-base-distroless/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM perl:5.42.0-trixie AS build

RUN set -xe ; \
cpanm HTTP::Daemon

FROM gcr.io/distroless/cc-debian13@sha256:9b615fff20e1a4fad29c2b30562580b212c7dd5e2225236735cca0070ed11c78

COPY --from=build /usr/local/bin/perl /usr/bin/perl
COPY --from=build /usr/local/lib/perl5 /usr/local/lib/perl5

COPY --from=build /lib/x86_64-linux-gnu/libcrypt.so.1 /lib/x86_64-linux-gnu/libcrypt.so.1

COPY ./server.pl /usr/src/server.pl

10 changes: 10 additions & 0 deletions examples/httpserver-perl5.42-base-distroless/Kraftfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
spec: v0.6

name: httpserver-perl5.42-base-distroless

runtime: base:latest

rootfs: ./Dockerfile

cmd: ["/usr/bin/perl", "/usr/src/server.pl"]

76 changes: 76 additions & 0 deletions examples/httpserver-perl5.42-base-distroless/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Perl5.42 Web Server - Distroless

This directory contains a [Perl](https://www.perl.org/) web server running on Unikraft.
It utilizes a Distroless container image (`gcr.io/distroless/cc-debian13`) to provide a minimal, secure root filesystem containing only the required runtime dependencies.

## Distroless Build

For this example's needs, the chosen distroless image provides:

- CA certificates (`ca-certificates`)
- Timezone data (`tzdata`)
- Standard `/tmp` and `/etc` directories
- The dynamic linker `ld-linux-x86-64`
- The libraries `libc`, `libm` and `libperl`

## Set Up

To run this example, [install Unikraft's companion command-line toolchain `kraft`](https://unikraft.org/docs/cli), clone this repository and `cd` into this directory.

## Run and Use

Use `kraft` to run the image and start a Unikraft instance:

```bash
kraft run --rm -p 8080:8080 --plat qemu --arch x86_64 -M 512M .
```

If the `--plat` argument is left out, it defaults to `qemu`.
If the `--arch` argument is left out, it defaults to your system's CPU architecture.

Once executed, it will open port `8080` and wait for connections.
To test it, you can use `curl`:

```bash
curl localhost:8080
```

You should see a "Bye, World!" message.

## Inspect and Close

To list information about the Unikraft instance, use:

```bash
kraft ps
```

```text

NAME KERNEL ARGS CREATED STATUS MEM PORTS PLAT
elastic_pocketswarhol oci://unikraft.org/base:latest /usr/bin/perl /usr/src/server.pl 5 seconds ago running 512M 0.0.0.0:8080->8080/tcp qemu/x86_64
```

The instance name is `elastic_pocketswarhol`.
To close the Unikraft instance, close the `kraft` process (e.g., via `Ctrl+c`) or run:

```bash
kraft rm elastic_pocketswarhol
```

Note that depending on how you modify this example your instance **may** need more memory to run.
To do so, use the `kraft run`'s `-M` flag, for example:

```bash
kraft run --rm -p 8080:8080 --plat qemu --arch x86_64 -M 1024M .
```

## `kraft` and `sudo`

Mixing invocations of `kraft` and `sudo` can lead to unexpected behavior.
Read more about how to start `kraft` without `sudo` at [https://unikraft.org/sudoless](https://unikraft.org/sudoless).

## Learn More

- [How to run unikernels locally](https://unikraft.org/docs/cli/running)
- [Building `Dockerfile` Images with `BuildKit`](https://unikraft.org/guides/building-dockerfile-images-with-buildkit)
21 changes: 21 additions & 0 deletions examples/httpserver-perl5.42-base-distroless/server.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/perl

use warnings;
use strict;

use HTTP::Daemon;
use HTTP::Status;
use HTTP::Response;

my $daemon = HTTP::Daemon->new(
LocalAddr => '0.0.0.0',
LocalPort => 8080,
) or die;

while (my $client_connection = $daemon->accept) {
my $request = $client_connection->get_request;
my $response = HTTP::Response->new(200);
$response->content("Bye, World!\n");
$client_connection->send_response($response);
}