A teeny-weeny, itty-bitty, pocket-sized Chiselled NGINX image

NGINX (“engine X”) is a high-performance web and reverse proxy server created by Igor Sysoev. It can be used both as a standalone web server and as a proxy to reduce the load on back-end HTTP or mail servers.

… but you knew that already :yawning_face:

You probably also know that NGINX is one of the most popular container applications out there, with over 8 billion pulls on Docker Hub and starred by more than 19 thousand users. Even the Ubuntu-based NGINX image is one of the most popular container images within our Ubuntu namespace.

Let’s now talk about a few other interesting facts you might have not paid enough attention to yet:

Image Size (compressed) Size (decompressed) Vulnerabilities
nginx:1.24-alpine-slim 5.2MB 11.5MB
nginx:1.22 (Debian) 56MB 142MB 45 LOW
ubuntu/nginx:1.22-23.04_beta 53MB 142MB 3 MEDIUM, 3 LOW

* The aforementioned values have been collected for amd64 only.

A key takeaway from this table is that the image’s size is correlated with the amount of vulnerabilities it has. And this sounds obvious! I mean, it is only logical that the more stuff you have inside your image the more likely it is for some of that stuff to be vulnerable.

Furthermore, if we need an application container, then we should only containerize the application! We don’t need extra software and utilities we’ll never use at runtime!

Chiselled NGINX

At the risk of sounding like a broken record, Chisel is great! It allows us to breakdown packages from the Ubuntu archives, in order to create minimal filesystems that contain only the necessary bits and pieces for the target packages to be functional.

We can do that with NGINX, and we have!

Can we build our own Chiselled NGINX container image? Sure…

FROM ubuntu:mantic as build-base
ARG TARGETARCH
# NGINX needs the www-data user
ARG UID=33 GID=33
WORKDIR /opt
# Get the Chisel binary into the build stage
ADD https://github.com/canonical/chisel/releases/download/v0.8.1/chisel_v0.8.1_linux_${TARGETARCH}.tar.gz chisel.tar.gz
RUN apt update && apt install -y ca-certificates \
        && tar -xvf chisel.tar.gz -C /usr/bin/ \
        && mkdir rootfs \
        && install -d -m 0755 -o $UID -g $GID rootfs/var/www \
        && chisel cut --release ubuntu-23.10 --root $PWD/rootfs \
                base-files_var \
                base-passwd_data \
                nginx_bins \
                nginx-common_index

FROM scratch
COPY --from=build-base /opt/rootfs /
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

And voilà! Get this into a Dockerfile and then run:

docker buildx build -t ubuntu/chiselled-nginx:1.24 --load .

If all goes well, your image will be loaded into the local Docker daemon and you can then test it with:

docker run --rm -p 8080:80 ubuntu/chiselled-nginx:1.24

Check localhost:8080 on your browser to ensure it’s working :smiley:

image

Rocking it…

Is the aforementioned Dockerfile a mouthful? Fear no more :superhero: here’s your rockcraft.yaml:

name: nginx
base: bare
build-base: ubuntu:22.04
version: "1.24"
summary: A high-performance reverse proxy & web server, based on Ubuntu
description: |
    Nginx ("engine X") is a high-performance web and reverse proxy server
    created by Igor Sysoev. It can be used both as a standalone web server
    and as a proxy to reduce the load on back-end HTTP or mail servers.
license: BSD-2-Clause
platforms:
    amd64:

services:
    nginx:
        override: replace
        command: nginx -g 'daemon off;'
        startup: enabled
        on-failure: shutdown

parts:
    nginx:
        plugin: nil
        build-packages:
            - git
        build-environment:
            - WWW_UID: 33
            - WWW_GID: 33
        override-build: |
            install -d -m 0755 -o $WWW_UID -g $WWW_GID $CRAFT_PART_INSTALL/var/www
            chisel cut --release ubuntu-23.10 \
                --root $CRAFT_PART_INSTALL \
                base-files_var \
                base-passwd_data \
                nginx_bins \
                nginx-common_index

NOTE: Rockcraft only supports LTS Ubuntu bases, so the above file is overriding the build to force the installation of slices from a different Ubuntu release. Once NGINX is sliced for an LTS release, the above rockcraft.yaml can be simplified, as you’ll only need to use stage-packages and list the slices without having to worry about sourcing the Chisel release and installing it manually.

With the above, get your Chiselled NGINX rock by running:

rockcraft -v

Wanna make sure it’s working? Convert the rock’s OCI archive to the local Docker daemon and run the container as in the previous example:

skopeo copy oci-archive:nginx_1.24_amd64.rock  docker-daemon:ubuntu/chiselled-nginx-rock:1.24

docker run --rm -p 8080:80 ubuntu/chiselled-nginx-rock:1.24

You’ll see Pebble initializing the NGINX service, and your webpage will once more be available at localhost:8080 :partying_face:.

So what?

Well, let’s reuse the table from above and compare the existing NGINX images with ours:

Image Size (compressed) Size (decompressed) Vulnerabilities
nginx:1.24-alpine-slim 5.2MB 11.5MB
nginx:1.22 (Debian) 56MB 142MB 45 LOW
ubuntu/nginx:1.22-23.04_beta 53MB 142MB 3 MEDIUM, 3 LOW
ubuntu/chiselled-nginx:1.24 5.4MB 13.7MB
ubuntu/chiselled-nginx-rock:1.24 8.8MB 21MB

Pretty cool right? We can now create Ubuntu-based container images for NGINX, that are amongst the smallest and most secure, while also providing additional capabilities like Pebble!

Challenge: could NGINX be sliced even further to make for a smaller image? If you think so, please make a proposal in chisel-releases :rocket:

4 Likes