Note
This document describes the routine usage of the Mezzotint tool.
Detailed Usage
Beyond the rapid and automated execution of Mezzotint, there exists a more sophisticated and precise approach utilizing Profiles. Profiles come to the rescue when automated data collection is simply not sufficient and may not always yield the most optimal results, leaving residual artifacts that still could safely be removed.
Every container (or intended App Bundle) necessitates a corresponding Profile. These Profiles can delineate specific container details, thereby reducing the number of command-line arguments required.
Provisioning a Base Container
Warning
This is just an example usage, where Buildah was chosen as a container builder. You can use any other container builders, like Docker or Kiwi etc. To get more familiar with Buildah, please refer to the documentation.
There is no limitations with what kind of tooling one should provision a container. However, in current workflow it is recommended to use Buildah — a tool that facilitates building Open Container Initiative (OCI) container images.
Provisioning a container from a package-based distribution can vary across different distributions. This specific document outlines the process using Ubuntu 22.04 LTS, yet the procedure should remain largely consistent across other versions of Ubuntu or Debian.
To provision a base root filesystem, Debian is using debootstrap utility withing a container from scratch, using Buildah as following:
# Allocate a name for a container
# and create an empty one
C_NAME=$(buildah from scratch)
# Mount a new container and catch the mount point
C_MNT=$(buildah mount $C_NAME)
Note
The particular example above should be called as root (UID 0) user.
In this case C_NAME will contain a container name, usually defaults to “working-container”. The variable C_MNT contains the full path to the mount point. At this point it is a time to provision that container, using debootstrap utility, passing it the mount point path as following:
# Run debootstrap, installing the most minimal base system
debootstrap --variant=minbase jammy $C_MNT
It will require some time to complete. Once finished, you’ll be able to use this mount point essentially as a container.
Adding Software
The root filesystem setup is now finished, but it’s quite minimal, only including the main repository. To install applications like Emacs editor, additional repositories need to be configured:
echo -e "deb http://de.archive.ubuntu.com/ubuntu jammy main universe multiverse restricted\n" \
>> $C_MNT/etc/apt/sources.list
Although it’s possible to run this root filesystem as a container and begin installations within it, this document employs an old-school approach of simply changing the root (which is also functional) as follows:
chroot $C_MNT apt update
chroot $C_MNT apt install -y emacs-nox
At this stage, the Emacs editor will be installed onto the target image. Following these steps, all essential customizations, including the addition of extra scripts, need to be completed.
We can now refer to the container as the “original” or “source.” To expedite processes, it’s recommended to push this container to the local registry on localhost:
buildah commit $C_NAME my_emacs
So now this is the situation where:
There exists a functional Emacs container in the local registry, enabling restoration without repeating provisioning, bootstrapping, or configurations.
Additionally, the mount point remains accessible through the variable
$C_MNT.
We will proceed using the mount point for further actions.
Profile Definition
To minimize a container’s artifacts, defining a Profile is essential. These Profiles are YAML files with any chosen name, passed to Mezzotint using --profile or -p option.
Targets
Targets consist of a list of absolute paths pointing to the executables within a container. They are defined as follows:
targets:
- /usr/bin/vim
- /usr/bin/my-other-app
Packages
Packages section is a list of known packages, those content should be preserved. This is for the situation when a package has no direct link to the software package, because software package assumes the artifacts are always there anyway.
Attention
The content of those packages will be still examined for a possible “junk”, such as text files, manpages and similar content.
An example of packages section:
packages:
- bash
- apt
- binutils
Configuration
Filtering configuration contains various flags of their types, determining what needs to be left on the disk and what needs to be removed. This section also contains list of what files needs to be removed or explicitly preserved, even they are marked as unnecessary.
Filters
filters:
- <NAME>
This is the list of filter names:
- l10n
Match any kind of localisation files
- i18n
Match internationalisation files
- doc
Matches all possible documentation, licenses, howtos etc
- man
Match all manpages on the file system
- log
Matches logfiles
- dir
Matches empty directories or directories with empty subdirectories
- pic
Matches any graphics data (images, pictures, pictograms, vector data etc)
- arc
Matches any kind of archives (tarballs, zip archives etc)
- all
Replaces all above. If you want to use all the filters listed above, simply use this one instead
Data removal
Some specific paths that were not automatically detected as not needed, still can be explicitly scheduled for the removal. This is used in the section prune, which is just a list of paths with (optionally) Unix globbing:
prune:
- /usr/share/bug/*
- /usr/share/lintian/*
Data preservation
Data preservation works the same way as in the chapter “Date removal”, just in the section keep. For example:
keep:
- /etc
- /usr/bin/*
Scripting Hooks
Hooks are basic commands, but can be also a proper shell scripts with the shebang. There are two types of hooks:
Before
After
Example:
hooks:
before: |
echo "Hello"
after: |
echo "Bye"
In a nutshell, one can run a script before and after calculation of what junk is.
Attention
In both cases before and after, hooks are always called before the actual data removal, because there is no guarantee that the very runtime of the script will not be removed and thus fail to run the script. That is, before hook is running right before Mezzotint is calculating what data needs to be removed etc.
Profile Example
This would be a basic profile for Emacs without X11 support (terminal only):
targets:
- /usr/bin/emacs-nox
packages:
- ncurses-base
- emacs-common
config:
filters:
- all
hooks:
# Vim users will enjoy this for sure
after: |
ln -s /usr/bin/emacs-nox /usr/bin/vim
Running Mezzotint with a Profile
If the profile is ready, first it would be a very good idea to see what will be at the end and gather some statistics. To do so, first let’s run it in dry-run without applying the changes, using --dry-run or -t flags:
mezzotint --dry-run --profile <PROFILE> -r <PATH_TO_ROOTFS>
For example, if your working container is currently mounted as /var/tmp/mycontainer then:
mezzotint -t -p mycontainer.yaml -r /var/tmp/mycontainer
This operation will calculate what can be classified as a “junk” and will remove it, displaying only what will be staying in your container in a future.
Reviewing “dry-run” Results
This will perform an excessive output to the terminal, listing all directories and files that will be preserved, calculating their size etc. At the end Mezzotint will print the total results, like how much disk space will be freed and how much space preserved etc. Also Mezzotint will print the list of preserved packages.
For example:
/usr/share/emacs/27.1/site-lisp
──┬──┄┄╌╌ ╌ ╌
╰─ subdirs.el
Files: 1, Size: 19.4 KB
/usr/share/emacs/site-lisp
──┬──┄┄╌╌ ╌ ╌
╰─ subdirs.el
Files: 1, Size: 106 B
/usr/share/lintian/overrides
──┬──┄┄╌╌ ╌ ╌
├─ emacs-nox
╰─ ncurses-base
Files: 2, Size: 229 B
/usr/share/tabset
──┬──┄┄╌╌ ╌ ╌
├─ std
├─ stdcrt
├─ vt100
╰─ vt300
Files: 4, Size: 628 B
Removed 6781 files, releasing 346.2 MB of a disk space
Preserved 1902 files, taking 182.4 MB of a disk space
Potentially 22 junk files, taking 2.2 MB of a disk space
Kept 34 packages as follows:
emacs-common, emacs-nox, libacl1, libasound2, libc6, libcap2,
libdbus-1-3, libffi8, libgcc-s1, libgcrypt20, libgmp10,
libgnutls30, libgpg-error0, libgpm2, libhogweed6, libicu70,
libidn2-0, libjansson4, liblcms2-2, liblz4-1, liblzma5,
libnettle8, libp11-kit0, libpcre2-8-0, libselinux1,
libstdc++6, libsystemd0, libtasn1-6, libtinfo6, libunistring2,
libxml2, libzstd1, ncurses-base, zlib1g
[23/09/2023 12:40:17] - WARN: This was a dry-run. Changes were not applied.
If you compare with the output captured in a chapter “Quick Start”, you will notice that automatic resolver still left some more data on the disk, which might be not the most optimal solution.
The output also found additional 22 junk files. Scrolling this output, they are revealed with alert icon, like so:
/usr/share/emacs/27.1/etc
──┬──┄┄╌╌ ╌ ╌
├─ ⚠️ CALC-NEWS
├─ ⚠️ ERC-NEWS
├─ ⚠️ ETAGS.EBNF
├─ ⚠️ HELLO
├─ ⚠️ MACHINES
├─ ⚠️ MH-E-NEWS
├─ ⚠️ NEWS
├─ ⚠️ NEWS.1-17
├─ ⚠️ NEWS.18
├─ ⚠️ NEWS.19
├─ ⚠️ NEWS.20
├─ ⚠️ NEWS.21
├─ ⚠️ NEWS.22
├─ ⚠️ NEWS.23
├─ ⚠️ NEWS.24
├─ ⚠️ NEWS.25
├─ ⚠️ NEWS.26
├─ ⚠️ NEXTSTEP
├─ ⚠️ NXML-NEWS
├─ ⚠️ ORG-NEWS
╰─ ⚠️ PROBLEMS
Files: 21, Size: 12.2 MB
We are still losing 12 megabytes. Let’s get rid of them too.
Tighting It All Up
These files are occupying 12 megabytes without any kind of practical need. You can choose either to keep these or you can do something about it. One way is to let Mezzotint deal with it, using --autodeps option with flag tight. This flag tells Mezzotint to actually not to look for dependencies, but only make a container “tight”, i.e. remove all data that is considered not important:
mezzotint -a tight -t -p mycontainer.yaml -r /var/tmp/mycontainer
This operation will additionally shave off 12Mb from this container. Now it is a time to actually apply the changes.
Danger
Once changes are applied, the operation cannot be undone!
For the reasons that the operation cannot be undone while the entire container might be permanently damaged for different reasons like wrong/incomplete profile, missing packages etc, it is recommended to pre-commit “fat” finished container to the local storage and restore working container from it, using Buildah.
Applying the Changes Permanently
To apply the changes, simply remove --dry-run or -t flag, and re-run Mezzotint:
mezzotint -a tight -p mycontainer.yaml -r /var/tmp/mycontainer
You should be seeing the following output:
[23/09/2023 13:01:30] - INFO: Launching scanner and data processor
[23/09/2023 13:01:30] - INFO: Getting profile at profile.yaml
[23/09/2023 13:01:31] - INFO: Automatically removing potential junk resources
[23/09/2023 13:01:31] - INFO: Finished. Hopefully it even works :-)
Hopefully yes. Now it is time to review and test it.
Reviewing the Result
At this point, if you navigate to /var/tmp/mycontainer and list it, you will see that the root filesystem is significantly smaller than usual:
-rw-r--r-- 1 root root 0 Sep 23 13:01 .tinted.lock
lrwxrwxrwx 1 root root 7 Sep 9 15:28 bin -> usr/bin
drwxr-xr-x 1 root root 79 Sep 23 13:01 dev
drwxr-xr-x 1 root root 4.0K Sep 23 13:01 etc
lrwxrwxrwx 1 root root 7 Sep 9 15:28 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Sep 9 15:28 lib64 -> usr/lib64
drwxr-xr-x 1 root root 147 Sep 23 13:01 usr
You can also notice .tinted.lock file. This file is zero size and only there to tell Mezzotint that the job is done. So if you repeat the action, this will happens:
[23/11/2023 13:05:13] - INFO: Launching scanner and data processor
[23/11/2023 13:05:13] - INFO: Getting profile at profile.yaml
[23/11/2023 13:05:13] - ERROR: This container seems already tinted.
If you list /usr/bin of the container, it will contain only binaries for the editor and nothing else:
lrwxrwxrwx 1 root root 24 Sep 9 15:29 editor -> /etc/alternatives/editor
lrwxrwxrwx 1 root root 23 Sep 9 15:29 emacs -> /etc/alternatives/emacs
-rwxr-xr-x 1 root root 5.4M Jan 24 2022 emacs-nox
lrwxrwxrwx 1 root root 18 Sep 23 13:01 vim -> /usr/bin/emacs-nox
The last symlink to Vim comes from the hook command after and is our “Hello, friends!” to the Vim users. 😊
Hint
As /usr/bin no longer contains anything but your application, this also means that the container is no longer “debuggable” the way it is normally done by running its shell.
If you still need bash and a minimal system to be present, consider installing busybox and keeping that package in your profile.
Test it!
Now it is time to test our container. Since container is constructed using Buildah, we can also run it in test mode. Since this is Emacs editor, we need TTY and also define terminal:
buildah run --tty --env TERM=xterm working-container /usr/bin/emacs
If we run it, Emacs appears:
Next Steps
Congratulations on reducing the size of your container! Now, as your container is much smaller than it usually would be, you can proceed with the following actions:
Publish your application on an OCI registry.
Convert your app-bundle container into a Flake package for distribution it via any package manager available for a Linux distribution of your choice.
Please note, however, that this document does not provide instructions on how to perform these tasks.