How to chisel an existing rock¶
Having additional utilities inside a rock can be useful for development and testing purposes. However, when moving to production, you want to make your rock as lean and secure as possible, getting rid of all the unnecessary bits and thus reducing its attack surface, while retaining its functionality.
For this, you’ll want to ensure that your rock has a bare
base and that its contents are
chiselled.
For this guide, let’s take the example of a Python runtime rock.
A Python rock¶
Our starting point is an Ubuntu 22.04-based Python rock, described by the following rockcraft.yaml file:
name: python
base: [email protected] # based on Ubuntu Jammy
version: "3.11"
summary: Python3.11 rock
description: Example of a Python rock
platforms:
amd64:
run-user: _daemon_ # setting a non-root user for the rock
parts:
install-python:
plugin: nil
# install Python3.11
stage-packages:
- python3.11
This rock can be built by running:
rockcraft pack
The resulting rock (python_3.11_amd64.rock
) will have approximately
42MB and have a functional Python3.11 interpreter. You can verify that by
running a very simply “Hello, world” Python script in it, with Docker:
sudo rockcraft.skopeo --insecure-policy copy oci-archive:python_3.11_amd64.rock docker-daemon:python-rock:3.11-22.04
docker run --rm python-rock:3.11-22.04 exec python3.11 -c 'print("Hello, world")'
This rock will also have other utilities like bash
and apt
which come
from the rock’s underlying Ubuntu 22.04 base.
Rebuild the rock with a bare
base¶
When starting to prepare the rock for production, the main goal is to get rid
of all the software that is not necessary at runtime, and the first step
towards achieving that goal is to use a bare
base.
In a separate directory, copy the rockcraft.yaml from above and replace
base: ubuntu@22.04
with base: bare
. With this change, you must also
remember to tell Rockcraft which Ubuntu release to use for its build
environment. Do this by adding build-base: ubuntu@22.04
to the
rockcraft.yaml file, which should now look like this:
name: bare-python
base: bare # using a bare base
build-base: [email protected] # using Ubuntu Jammy only for the build environment
version: "3.11"
summary: Python3.11 rock with a bare base
description: Example of a distroless-like Python rock
platforms:
amd64:
run-user: _daemon_ # setting a non-root user for the rock
parts:
install-python:
plugin: nil
# install Python3.11
stage-packages:
- base-files # also install base-files to ensure a basic filesystem structure
- python3.11
Pack this rock with the same rockcraft
command as above:
rockcraft pack
This new rock (bare-python_3.11_amd64.rock
) will now have about 28MB -
a ~33% size reduction - and also have a functional Python3.11 interpreter.
Run the same “Hello, world” Python script as before to confirm:
sudo rockcraft.skopeo --insecure-policy copy oci-archive:bare-python_3.11_amd64.rock docker-daemon:python-rock:3.11-bare
docker run --rm python-rock:3.11-bare exec python3.11 -c 'print("Hello, world")'
The question then is: how is rockcraft able to produce an equally functional Python rock with such a drastic reduction in size?
And the answer is: the rock no longer has the Ubuntu base as its first layer, and thus no longer has utilities like ``bash`` and ``apt`` (which aren’t needed for this use case anyway)!
Chisel the Python rock¶
The last step in your way to production is to chisel the rock in order to further reduce its size and attack surface, by stripping down the internal Python components that are not needed for the final application.
In this example, the Python application is a very simple “Hello, world”, which
means we need nothing else but the core Python modules. For that, copy the
previous rockcraft.yaml file to a new directory and simply append the desired
package slice names to the list of stage-packages
, like this:
name: chiselled-python
base: bare # using a bare base
build-base: [email protected] # using Ubuntu Jammy only for the build environment
version: "3.11"
summary: Chiselled Python3.11 rock
description: Example of a chiselled Python rock
platforms:
amd64:
run-user: _daemon_ # setting a non-root user for the rock
parts:
install-python-slices:
plugin: nil
stage-packages:
- base-files_release-info # we only want the release information
- python3.11_core # we just need the Python core functionality
Pack it with:
rockcraft pack
And the end result will be an astoundingly small Python rock with 13MB! And the “Hello, world” script still works:
sudo rockcraft.skopeo --insecure-policy copy oci-archive:chiselled-python_3.11_amd64.rock docker-daemon:python-rock:3.11-chiselled
docker run --rm python-rock:3.11-chiselled exec python3.11 -c 'print("Hello, world")'
To conclude, you’ve just created a general-purpose Python rock with just a
few YAML lines and no code whatsoever! Then, by changing a couple of YAML
fields (the base
), you’ve achieved a ~33% size reduction while
maintaining functionality. Finally, by appending two words (literally, just
the slice names) to the rockcraft.yaml, you were able to reduce the rock’s
size even further by an additional ~37% of its original size! In short:
Original rock |
w/ |
chiselled |
---|---|---|
42MB |
28MB |
13MB |